相关阅读

彻底搞懂 CPU 中的内存结构

Java 内存模型 ,一篇就够了!

多线程实现原理

之前已经说过了,我们在保证线程安全的时候主要就是保证线程安全的 3 大特性,原子性、可见性、有序性、而在 JMM 的层面也已经做了相关的操作,比方说 JMM 定义的 8 种原子操作以及相关的规则,happens-before 原则。

今天主要就来看看 Java 中实现线程安全的方法之二,使用 atomic 包,synchronized 关键字。

首先说说 AtomicInteger 这个类,我们来看一个例子,计数器。实现很简单,就是每个线程都过来加 1,我们期待的结果是 999,但是若不保证线程安全,结果往往不对。

import java.util.concurrent.atomic.AtomicInteger;
public class AtomicInt {
public static void main(String[] args) {
for(int i=0;i < 1000;i ++){
Thread thread = new Thread(new Counter());
thread.start();
}
System.out.println(Counter.getCount());
}
} class Counter implements Runnable{ // private static int count = 0;
private static AtomicInteger count = new AtomicInteger(0); public static int getCount(){
// return count;
return count.get();
} @Override
public void run() {
// count++;
count.incrementAndGet();
}
}

下面就来分析一下 incrementAndGet 方法的具体实现

public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
} // var1 :count 对象;
// var2 :加数,count 中封装的整数;
// var4 :被加数 1;
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
// 该方法的主要目的取出主内存中的加数,【即为当前 count.get() 值】
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5;
} public native int getIntVolatile(Object var1, long var2); public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

compareAndSwapInt 方法既是我们经常看到的 CAS 的简写,但更多的是代表了一种思想。具体在这里该方法主要目的是比较我们从主存取出的整数值和我们从 count 中传过来的是否一致,count 中的整数也就是从线程的工作内存传过来的。若一致,则计算结果并返回。若不一致,为 var5 赋值【即修改主存中的值】,并继续从主存中取出 var5 ,继续比较,直到返回。

这中间的 CAS 的思想在其它的类中也很常用,理解其核心思想即可。比较工作内存和主存中的数据,使其一致再进行计算。

与上面类似的还有 AtomicBoolean 和 AtomicLong 对象,底层的实现还是 CAS。但是在 JDK1.8 中,出现了与 AtomicLong 类似的 LongAdder 对象。

我们知道,在 CAS 的实现中,主体部分在一个 while 循环中,会一直找到工作内存和主存一致的情况,若是竞争不激烈,这是没问题的,但是当竞争非常激烈的时候,一直返回不了结果,性能就会很差。CAS 也是一种乐观锁的表现,以为可以很快的找到并返回结果。

对于普通的 long 和 double 变量,JVM 必须将 64 位的读写操作拆成 2 个 32 位读写操作。而 LongAdder 这个类的实现基于的思想就是将【热点数据分离】,比方说可以将 AtomicLong 内部的 value 分离成一个数组,不同的线程根据 hash 可以操作不同的 cell ,最后再将整个数组中所有 cell 中的数累加。

这样做的结果就相当于在 AtomicLong 的基础上,将单点的压力,分散到不同的 cell 中。在低并发的时候可以不分离热点数据,使用 base 数据, 在高并发的分离数据,这样就保证了性能。缺点是 LongAdder 在统计的时候如果有并发更新,可能导致统计的数据有误差。

我们知道在 CAS 中,我们会持续的判断内存中的数和工作内存中是否一致,以此来判断有没有其它的线程修改了工作内存中的数据,但是存在一种情况,共享变量是 1 被修改为 2 ,而后又被修改为 1 ,此时已经线程不安全了。这个问题就是常说的 ABA 问题。

Java 中提供了 AtomicStampedReference<T>,这个类主要解决的就是 CAS 中 ABA 问题,为了解决 ABA 问题,引入了一个‘变量版本号’的概念,即每次修改版本号都会加 1。使用一个变量 stamp 来记录变量版本号。

AtomicBoolean 这个类中的 compareAndSet 方法还是比较常用的。用在标识变量 flag。若是某段代码只需要执行一次可以使用这个方法来做。

public class AtomicBooleanTest {
private static AtomicBoolean flag = new AtomicBoolean(false); public static void main(String[] args) { // 不管有多少个线程在执行,都能保证只有会一个线程执行下面这段代码
// 如果 flag 是 false,修改为 true
if(flag.compareAndSet(false, true)){
System.out.println("the flag has been changed ~");
// Work();
}
}
}

Java 中的锁有两种,一种是 synchronized 关键字,依赖于 JVM ,还有一种是代码层面的 Lock,依赖于特殊的 CPU 指令,常用的实现类有 ReentrantLock。

synchronized 可以用来修饰代码块和方法,而锁住的对象又可分为当前对象和类对象。当修饰静态方法和代码块(类.class)时为类锁。修饰一般方法和代码块(this)时为对象锁。

举例看看对象锁

public class SynTest {

     public static void main(String args[]) {
SyncThread s = new SyncThread();
Thread t1 = new Thread(s);
Thread t2 = new Thread(s);
t1.start();
t2.start();
}
}
class SyncThread implements Runnable { private static int count = 0; public void run() {
// 修饰代码块,锁住当前对象:一个线程访问一个对象中的 synchronized(this) 同步代码块时,其他试图访问该对象的线程将被阻塞
synchronized (this) {
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {}
}
}
}
}

另外 JMM 关于 synchronized 有两条规定:1 线程解锁前,必须把共享变量的最新值刷新到主存。2 线程解锁前,将清空工作内存中共享变量的值,从而使用共享变量时需要从主存中重新读取最新的值(注意,加锁与解锁是同一把锁)这也就保障了可见性。

atomic 包、synchronized | Java 中线程安全的更多相关文章

  1. java中线程机制

    java中线程机制,一开始我们都用的单线程.现在接触到多线程了. 多线性首先要解决的问题是:创建线程,怎么创建线程的问题: 1.线程的创建: 四种常用的实现方法 1.继承Thread. Thread是 ...

  2. 沉淀再出发:java中线程池解析

    沉淀再出发:java中线程池解析 一.前言 在多线程执行的环境之中,如果线程执行的时间短但是启动的线程又非常多,线程运转的时间基本上浪费在了创建和销毁上面,因此有没有一种方式能够让一个线程执行完自己的 ...

  3. JAVA中线程同步方法

    JAVA中线程同步方法 1  wait方法:         该方法属于Object的方法,wait方法的作用是使得当前调用wait方法所在部分(代码块)的线程停止执行,并释放当前获得的调用wait所 ...

  4. 多线程(三) java中线程的简单使用

    java中,启动线程通常是通过Thread或其子类通过调用start()方法启动. 常见使用线程有两种:实现Runnable接口和继承Thread.而继承Thread亦或使用TimerTask其底层依 ...

  5. Java中线程同步的理解 - 其实应该叫做Java线程排队

    Java中线程同步的理解 我们可以在计算机上运行各种计算机软件程序.每一个运行的程序可能包括多个独立运行的线程(Thread). 线程(Thread)是一份独立运行的程序,有自己专用的运行栈.线程有可 ...

  6. Java中线程和线程池

    Java中开启多线程的三种方式 1.通过继承Thread实现 public class ThreadDemo extends Thread{ public void run(){ System.out ...

  7. java中线程同步的理解(非常通俗易懂)

    转载至:https://blog.csdn.net/u012179540/article/details/40685207 Java中线程同步的理解 我们可以在计算机上运行各种计算机软件程序.每一个运 ...

  8. 面试官:Java中线程是按什么顺序执行的?

    摘要:Java中多线程并发的执行顺序历来是面试中的重点,掌握Java中线程的执行顺序不仅能够在面试中让你脱颖而出,更能够让你在平时的工作中,迅速定位由于多线程并发问题导致的"诡异" ...

  9. java中线程分两种,守护线程和用户线程。

    java中线程分为两种类型:用户线程和守护线程. 通过Thread.setDaemon(false)设置为用户线程: 通过Thread.setDaemon(true)设置为守护线程. 如果不设置次属性 ...

随机推荐

  1. ACM-ICPC 2018 沈阳赛区网络预赛 K题

    题目链接: https://nanti.jisuanke.com/t/31452 AC代码(看到不好推的定理就先打表!!!!): #include<bits/stdc++.h> using ...

  2. 实例详析ImageView的adjustViewBonds和scaleType

    android:adjustViewBounds是否保持宽高比.需要与maxWidth.MaxHeight一起使用,否则单独使用没有效果. 设置View的最大高度,单独使用无效,需要与setAdjus ...

  3. 一:对程序员来说CPU是什么?

    0.开篇    (1)程序是什么?          指示计算机每一步动作的一组指令     (2)程序是由什么组成的?          指令和数据     (3)什么是机器语言?         ...

  4. android fragment解析

    1.fragment加载到Activity (1).添加fragment到Activity的布局文件 (2).动态在activity中添加fragment 例子: // 步骤1:获取FragmentM ...

  5. 使用Idea初始化SpringMvc项目

    (1) (2) (3) (4) (5)感谢http://www.cnblogs.com/feiyujun/p/6537510.html (6)

  6. ARMV8 datasheet学习笔记3:AArch64应用级体系结构之Synchronization and semapores

    1.前言 本文主要介绍原子变量的实现原理,对原子变量的修改有一套特殊的机制 2. Local monitor和Global monitor UP时执行Load EX和Store EX时仅需关注Loca ...

  7. Linux Kernel 代码艺术——编译时断言【转】

    转自:http://www.cnblogs.com/hazir/p/static_assert_macro.html 本系列文章主要写我在阅读Linux内核过程中,关注的比较难以理解但又设计巧妙的代码 ...

  8. jenkins jar包上传maven仓库

    1      Jenkins 编译后部署至 Maven 仓库 jenkins编译后构件(如:jar包)部署至maven仓库需修改以下内容:maven 仓库配置:项目 pom 文件:本地仓库的 sett ...

  9. js 将图片连接转换称base64格式

    我们把图像文件的内容直接写在了HTML 文件中,这样做的好处是,节省了一个HTTP 请求.坏处呢,就是浏览器不会缓存这种图像.现在我们提供一个js: function convertImgToBase ...

  10. 学习笔记_Cocos Creator_继承组件单例

    官方文档:https://docs.cocos.com/creator/manual/zh/scripting/reference/class.html 前言 单例,在游戏开发中是比较常用的功能,全局 ...