Java 多线程 volitile 和 atomic

volitile关键字

public class MTester {
public static class TestKey{
int x = 0;
}
public static TestKey key0 = new TestKey();
public static void main(String[] args) {
Thread thread = new Thread(()->{
while (key0.x == 0){
}
System.out.println("key0"+key0.x);
});
Thread thread1 = new Thread(()->{
try {
Thread.sleep(1000);
key0.x=1;
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("over");
}
});
thread.start();
thread1.start();
}
}

尝试运行以上代码,发现thread永远也无法发现key0的x被改变

所以这个时候需要加上volitile关键字

具体原因是java中每个线程都有工作内存,以及主存

我的理解就是不加volitile,线程读写变量是先在自己的工作内存中处理,然后再写回主存,但是有的线程处理的是工作内存,但是并没有从主存里面读取,加上volitile关键字之后,会通知其他线程,让他们强制从主存中读取数据

https://www.cnblogs.com/zhengbin/p/5654805.html

volatile还有一个特性:禁止指令重排序优化。

可以见这个文章

https://www.cnblogs.com/chengxiao/p/6528109.html

但是volitile只能保证可见性,不能保证原子性,也就是说如果多线程操作 i++还是无法保证正确

ExecutorService service = Executors.newCachedThreadPool();
for (int i = 0;i<10;i++){
service.submit(()->{
for (int j = 0;j <10;j++){
key0.x++;
System.out.println(key0.x);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
service.awaitTermination(10, TimeUnit.SECONDS);
System.out.println(key0.x);

还是无法保证原子性

这个时候可以考虑使用 java.util.concurrent.atomic;中的类

这些类里面的类大部分都是使用CAS算法进行操作的

CAS compare and set

public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

这个 unsafe是unsafe类,里面的方法都是native方法

补充:

这个valueOffset可以理解为一个AtomicInteger的偏移量然后native方法然后传入native方法,这样就相当于获取了CAS需要比较对象的地址了

CAS其实就是期望的值进行比较,如果不相等,就证明有其他线程更改过了,然后不执行操作然后返回失败,CAS看起来很麻烦,但是却可以映射一些CPU指令,实际上执行起来还是很快的(参考java核心技术)

unsafe里面的方法大部分都是native方法

比如说我们想要对AtomicInteger执行一个 increase操作,就先比较自己跟期望的值,如果不等,那就在下次循环接着尝试更改,直到更改成功

//AtomicInteger.java
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
//Unsafe.class
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5;
}

不过这种不断尝试比较,对CPU开销还是比较大,不过相对于synchronized来说更轻量级,因为synchronized需要不断尝试获取锁释放锁,而且只能独占

在并发量不是特别大的情况下,效率相对于synchronized还是很高的,当自选严重冲突的时候synchronized还是效率更高一些

CAS 算法属于自旋

不过CAS算法也有其他的缺点,常见的就是ABA问题

举个例子

线程1:从内存位置 1 取回A

线程2:从内存位置 1取到 A

线程2:做了一些操作

线程2:从内存位置 1 写入A

线程1:发现位置1还是A CAS成功但是却不知道线程2做了什么操作,可能引发一些后果

解决办法

AtomicStampedReference

可以用一个timestamp 或者mask来判断是否有其他操作

自旋锁的简单实现:

思路:每次只有一个线程进入临界区

import java.util.concurrent.atomic.AtomicReference;

public class MSpinLock {
AtomicReference<Thread> reference = new AtomicReference<>();
public void lock(){
do { }while (!this.reference.compareAndSet(null,Thread.currentThread()));
}
public void unlock(){
do { }while (!this.reference.compareAndSet(Thread.currentThread(),null));
} public static void main(String[] args) {
MSpinLock lock = new MSpinLock();
Thread thread = new Thread(()->{
while (true){
try{
lock.lock();
System.out.println("thread had lock");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("thread will unlock");
lock.unlock();
}
}
});
Thread thread1 = new Thread(()->{
while (true){
try{
lock.lock();
System.out.println("thread1 had lock");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("thread1 will unlock");
lock.unlock();
}
} }); thread.start();
thread1.start();
}
}

每次都是成对出现的

如果注释掉lock

显然不对

不过我们做的自选锁不可重入

假如有个函数需要递归,那么自旋锁就会发生死锁

所以我们需要一个Integer来判断一下

    public void lock(){
if(Thread.currentThread().equals(reference.get())){
atomicInteger.incrementAndGet();
return ;
}
do {
}while (!this.reference.compareAndSet(null,Thread.currentThread()));
atomicInteger.incrementAndGet();
}
public void unlock(){
if(Thread.currentThread().equals(reference.get())){
int n = atomicInteger.decrementAndGet();
if(n>0){
return;
}
}
do {
}while (!this.reference.compareAndSet(Thread.currentThread(),null)); }

这样就可重入了

https://www.cnblogs.com/qjjazry/p/6581568.html

Java 多线程 volitile 和 atomic的更多相关文章

  1. java多线程-cas及atomic

    大纲: cas atomic 一.cas cas:compareAndSwap,一种乐观锁. cas思想:cas需要三个值,v是内存值,e是期望值,n是要修改的值.当内存中的值v等于预期值e(说明内存 ...

  2. Java多线程系列九——Atomic类

    参考资料:https://fangjian0423.github.io/2016/03/16/java-AtomicInteger-analysis/http://www.cnblogs.com/54 ...

  3. java多线程系列5 atomic简介

    先看一个例子,AtomicInteger 实现的线程安全的累加器 public class AtomicIntTest { public static void main(String[] args) ...

  4. java多线程系列 目录

    Java多线程系列1 线程创建以及状态切换    Java多线程系列2 线程常见方法介绍    Java多线程系列3 synchronized 关键词    Java多线程系列4 线程交互(wait和 ...

  5. java多线程系列(五)---synchronized ReentrantLock volatile Atomic 原理分析

    java多线程系列(五)---synchronized ReentrantLock volatile Atomic 原理分析 前言:如有不正确的地方,还望指正. 目录 认识cpu.核心与线程 java ...

  6. Java 多线程:锁(一)

    Java 多线程:锁(一) 作者:Grey 原文地址: 博客园:Java 多线程:锁(一) CSDN:Java 多线程:锁(一) CAS 比较与交换的意思 举个例子,内存有个值是 3,如果用 Java ...

  7. 40个Java多线程问题总结

    前言 Java多线程分类中写了21篇多线程的文章,21篇文章的内容很多,个人认为,学习,内容越多.越杂的知识,越需要进行深刻的总结,这样才能记忆深刻,将知识变成自己的.这篇文章主要是对多线程的问题进行 ...

  8. Java多线程系列--“JUC锁”03之 公平锁(一)

    概要 本章对“公平锁”的获取锁机制进行介绍(本文的公平锁指的是互斥锁的公平锁),内容包括:基本概念ReentrantLock数据结构参考代码获取公平锁(基于JDK1.7.0_40)一. tryAcqu ...

  9. Java多线程系列--“JUC锁”04之 公平锁(二)

    概要 前面一章,我们学习了“公平锁”获取锁的详细流程:这里,我们再来看看“公平锁”释放锁的过程.内容包括:参考代码释放公平锁(基于JDK1.7.0_40) “公平锁”的获取过程请参考“Java多线程系 ...

随机推荐

  1. Delphi XE7功能之TMultiView

    TMultView,做为一个TPanel来显示控件,可通过属性Mode来控制TMultView的显示效果,如下拉或者以抽屉方式.从屏一侧象抽屉一样显示TMultView,但不会转换主屏,也就是说在主窗 ...

  2. 使用UILabel实现滚动字幕移动效果

    使用UILabel实现滚动字幕移动效果 这个链接中的代码也实现了这种效果 https://github.com/cbpowell/MarqueeLabel 最终效果如下: 原理如下: 1. 获取文本 ...

  3. RESTful的理解与设计【PHP】

    RESTful 就是一种软件架构的风格,以资源为中心定位,运用http的请求方式(动词)来划定操作.这样的设定优点简单易理解,方便人员对接,形成规范. 资源作为唯一标识,使用相关动词取获取操作.举例, ...

  4. [BZOJ 3441]乌鸦喝水

    3441: 乌鸦喝水 Time Limit: 20 Sec  Memory Limit: 128 MBSubmit: 374  Solved: 148[Submit][Status][Discuss] ...

  5. python使用SQLAlchemy对mysql操作

    安装SQLAlchemy pip install sqlalchemy 在MySQL的test数据库中创建的user表,用SQLAlchemy来试试 数据库连接 第一步,导入SQLAlchemy,并初 ...

  6. FOR YOU

    给你 作者:余秀华 一家朴素的茶馆, 面前目光朴素的你皆为我喜欢 你的胡子,昨夜辗转的面色让我忧伤 我想带给你的,一路已经丢失得差不多 除了窗外凋谢的春色 遇见你以后,你不停地爱别人,一个接一个 我没 ...

  7. 在android工程中添加图片资源(转加)

    在Android工程中,每添加一个资源,就会在gen目录下的R.java中自动生成一个新的静态整型变量来指向这个资源.程序文件中调用资源的时候,先在R.java中找到变量名,然后根据变量值查找资源. ...

  8. Spring的IoC与AOP的理解

    1.Spring它到底是什么? Spring是一个开源的Java应用程序开发框架,为了解决企业应用开发的复杂性而创建的.   在spring中,它会认为一切Java类都是资源,而资源就是Bean,容纳 ...

  9. Linux中安装Nginx

    1.安装编译文件及库文件 yum -y install make zlib zlib-devel gcc-c++ libtool  openssl openssl-devel 2.安装PCRE,Ngi ...

  10. 「CF1025D Recovering BST」

    题目 郑州讲过的题了 发现这是一个二叉搜索树,给出的还是中序遍历,我们很自然的想到我们需要可以用一个\(f[i][j][k](k\in[i,j])\)来表示区间\([i,j]\)能不能形成以\(k\) ...