相关阅读

彻底搞懂 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. linux相关设置

    mysql开机自启: [root@workstudio system]# systemctl enable mysqld

  2. 论文笔记系列-Efficient Neural Architecture Search via Parameter Sharing

    Summary 本文提出超越神经架构搜索(NAS)的高效神经架构搜索(ENAS),这是一种经济的自动化模型设计方法,通过强制所有子模型共享权重从而提升了NAS的效率,克服了NAS算力成本巨大且耗时的缺 ...

  3. 【windows核心编程】注入DLL时BUG排除与调试

    DLL注入排除bug的思路步骤. 1.在VS中监视输入err,hr检查DLL是否注入成功 2.OD断点loadlibraryW,loadlibraryA是否已经注入成功,eax是否有值. 3.检查路径 ...

  4. python学习之argparse模块

    python学习之argparse模块 一.简介: argparse是python用于解析命令行参数和选项的标准模块,用于代替已经过时的optparse模块.argparse模块的作用是用于解析命令行 ...

  5. MIPI协议学习总结(一)

    一.MIPI 简介: MIPI(移动行业处理器接口)是Mobile Industry Processor Interface的缩写.MIPI是MIPI联盟发起的为移动应用处理器制定的开放标准. 已经完 ...

  6. openssl版本升级操作记录【转】

    需要部署nginx的https环境,之前是yum安装的openssl,版本比较低,如下:   [root@nginx ~]# yum install -y pcre pcre-devel openss ...

  7. 提高delete效率方法

    1 open c_1;   loop     fetch c_1 bulk collect       into t2 limit 100000;     exit when c_1%notfound ...

  8. zabbix3.0.4关于java服务端程序内存溢出的处理

    关于java服务端程序内存溢出的处理 java服务端程序内存溢出会产生jvm.log文件,此时程序会挂掉,无法正常处理业务,需要重启服务 思路: 当存在jvm.log这个文件的时候则触发clean_j ...

  9. centos 6.5环境下分布式文件系统MogileFS工作原理及分布式部署实现过程

    MogileFS是一套高效的文件自动备份组件,由Six Apart开发,广泛应用在包括LiveJournal等web2.0站点上 MogileFS由3个部分组成:    第1个部分:是server端, ...

  10. Ex 6_16 旧货销售问题_第七次作业

    即可 子问题定义:定义数组B(S,j),其中 B(S,j)表示在子集S中结束位置为j的子问题的最大收益值,其中j的前一个地点有两种情况,第一种情况是某个拍卖会 另一种情况是从家里出发. 递归关系: 初 ...