atomic 包、synchronized | Java 中线程安全
相关阅读
彻底搞懂 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 中线程安全的更多相关文章
- java中线程机制
java中线程机制,一开始我们都用的单线程.现在接触到多线程了. 多线性首先要解决的问题是:创建线程,怎么创建线程的问题: 1.线程的创建: 四种常用的实现方法 1.继承Thread. Thread是 ...
- 沉淀再出发:java中线程池解析
沉淀再出发:java中线程池解析 一.前言 在多线程执行的环境之中,如果线程执行的时间短但是启动的线程又非常多,线程运转的时间基本上浪费在了创建和销毁上面,因此有没有一种方式能够让一个线程执行完自己的 ...
- JAVA中线程同步方法
JAVA中线程同步方法 1 wait方法: 该方法属于Object的方法,wait方法的作用是使得当前调用wait方法所在部分(代码块)的线程停止执行,并释放当前获得的调用wait所 ...
- 多线程(三) java中线程的简单使用
java中,启动线程通常是通过Thread或其子类通过调用start()方法启动. 常见使用线程有两种:实现Runnable接口和继承Thread.而继承Thread亦或使用TimerTask其底层依 ...
- Java中线程同步的理解 - 其实应该叫做Java线程排队
Java中线程同步的理解 我们可以在计算机上运行各种计算机软件程序.每一个运行的程序可能包括多个独立运行的线程(Thread). 线程(Thread)是一份独立运行的程序,有自己专用的运行栈.线程有可 ...
- Java中线程和线程池
Java中开启多线程的三种方式 1.通过继承Thread实现 public class ThreadDemo extends Thread{ public void run(){ System.out ...
- java中线程同步的理解(非常通俗易懂)
转载至:https://blog.csdn.net/u012179540/article/details/40685207 Java中线程同步的理解 我们可以在计算机上运行各种计算机软件程序.每一个运 ...
- 面试官:Java中线程是按什么顺序执行的?
摘要:Java中多线程并发的执行顺序历来是面试中的重点,掌握Java中线程的执行顺序不仅能够在面试中让你脱颖而出,更能够让你在平时的工作中,迅速定位由于多线程并发问题导致的"诡异" ...
- java中线程分两种,守护线程和用户线程。
java中线程分为两种类型:用户线程和守护线程. 通过Thread.setDaemon(false)设置为用户线程: 通过Thread.setDaemon(true)设置为守护线程. 如果不设置次属性 ...
随机推荐
- html 速查表
HTML 速查列表 HTML 速查列表. 你可以打印它,以备日常使用. HTML 基本文档 <!DOCTYPE html> <html> <head> <ti ...
- 2017-2018-2 165X 『Java程序设计』课程每周成绩公布
2017-2018-2 165X 『Java程序设计』课程 每周成绩公布 本博客将跟随教学进度不定期更新,每次更新后将在课程群公布.如对成绩有疑问,请于公布成绩后的1天之内联系助教,进行审核确认. - ...
- v4l2功能列表大全【转】
一,功能参考 目录 V4L2 close() - 关闭一个V4L2设备 V4L2 ioctl() - 创建的V4L2设备 ioctl VIDIOC_CROPCAP - 视频裁剪和缩放功能信息 ioct ...
- WCF错误远程服务器返回了意外响应: (413) Request Entity Too Large。解决方案
这个问题出现的原因是 调用wcf服务的时候传递的参数 长度太大 wcf数据传输采用的默认的大小是65535字节. ---------------------------------------- ...
- Linux 查看CPU信息,机器型号,内存等信息
- 通过本地yum源安装软件报错[Errno 14] PYCURL ERROR 56 - "Failure when receiving data from the peer"
通过本地yum源安装软件报错 http://192.168.3.85/centos/6/os/x86_64/Packages/php-pdo-5.3.3-47.el6.x86_64.rpm: [Err ...
- linux POSIX 信号量介绍
信号量一.什么是信号量信号量的使用主要是用来保护共享资源,使得资源在一个时刻只有一个进程(线程)使用.多线程可以同时运行多个线程函数完成功能,但是对于共享数据如果不加以锁定,随意改变共享数据的值会发生 ...
- C++ code:指针类型(pointer types)
#include <iostream> using namespace std; int main() { float f = 34.5; int *ip = reinterpret_ca ...
- java 文件读取的一些方法
web项目读取日志文件 //得到路径 String appPath = filterConfig.getServletContext().getRealPath("/"); // ...
- uva12436 回头再做一次
线段树维护等差数列,结点维护首项+公差即可 #include <cstdio> #include <cstring> #include <algorithm> us ...