Java 多线程 volitile 和 atomic

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方法

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

原文地址:https://www.cnblogs.com/stdpain/p/10659449.html

时间: 2024-08-30 11:21:11

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

Java多线程系列一——Atomic类

参考资料:https://fangjian0423.github.io/2016/03/16/java-AtomicInteger-analysis/http://www.cnblogs.com/549294286/p/3766717.html 最近面试遇到一道编程题,要求两个线程交替打印[0,100]的数字,其中一个只打印奇数,另一个只打印偶数,并且给出特别明显的提示AtomicInteger,当时我在想简直是送分题啊,但事后回想由于手写又没有记得所有API,很多地方不完美,所以面试官最后让我

JAVA多线程和并发基础面试问答(转载)

原文链接:http://www.cnblogs.com/dolphin0520/p/3932934.html 多线程和并发问题是Java技术面试中面试官比较喜欢问的问题之一.在这里,从面试的角度列出了大部分重要的问题,但是你仍然应该牢固的掌握Java多线程基础知识来对应日后碰到的问题. Java多线程面试问题 1. 进程和线程之间有什么不同? 一个进程是一个独立(self contained)的运行环境,它可以被看作一个程序或者一个应用.而线程是在进程中执行的一个任务.Java运行环境是一个包含

JAVA多线程和并发基础面试问答

原文链接:http://ifeve.com/java-multi-threading-concurrency-interview-questions-with-answers/ 多线程和并发问题是Java技术面试中面试官比较喜欢问的问题之一.在这里,从面试的角度列出了大部分重要的问题,但是你仍然应该牢固的掌握Java多线程基础知识来对应日后碰到的问题.(校对注:非常赞同这个观点) Java多线程面试问题 1. 进程和线程之间有什么不同? 一个进程是一个独立(self contained)的运行环

Java多线程同步方法

一.同步方法 即有synchronized关键字修饰的方法. 由于java的每个对象都有一个内置锁,当用此关键字修饰方法时, 内置锁会保护整个方法.在调用该方法前,需要获得内置锁,否则就处于阻塞状态. 注: synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类. 二.同步代码块 即有synchronized关键字修饰的语句块. 被synchronized关键字修饰的语句块会自动被加上内置锁,从而实现同步 代码如: synchronized(object){ }

java多线程系类:JUC原子类:03之AtomicLongArray原子类

概要 AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray这3个数组类型的原子类的原理和用法相似.本章以AtomicLongArray对数组类型的原子类进行介绍.内容包括:AtomicLongArray介绍和函数列表AtomicLongArray源码分析(基于JDK1.7.0_40)AtomicLongArray示例 转载请注明出处:http://www.cnblogs.com/skywang12345/p/3514604.html

40个Java多线程问题总结

前言 Java多线程分类中写了21篇多线程的文章,21篇文章的内容很多,个人认为,学习,内容越多.越杂的知识,越需要进行深刻的总结,这样才能记忆深刻,将知识变成自己的.这篇文章主要是对多线程的问题进行总结的,因此罗列了40个多线程的问题. 这些多线程的问题,有些来源于各大网站.有些来源于自己的思考.可能有些问题网上有.可能有些问题对应的答案也有.也可能有些各位网友也都看过,但是本文写作的重心就是所有的问题都会按照自己的理解回答一遍,不会去看网上的答案,因此可能有些问题讲的不对,能指正的希望大家不

【转】 Java 多线程之一

转自   Java 多线程 并发编程 一.多线程 1.操作系统有两个容易混淆的概念,进程和线程. 进程:一个计算机程序的运行实例,包含了需要执行的指令:有自己的独立地址空间,包含程序内容和数据:不同进程的地址空间是互相隔离的:进程拥有各种资源和状态信息,包括打开的文件.子进程和信号处理. 线程:表示程序的执行流程,是CPU调度执行的基本单位:线程有自己的程序计数器.寄存器.堆栈和帧.同一进程中的线程共用相同的地址空间,同时共享进进程锁拥有的内存和其他资源. 2.Java标准库提供了进程和线程相关

JAVA多线程面试题

1.Thread 类中的start() 和 run() 方法有什么区别? Thread.start()方法(native)启动线程,使之进入就绪状态,当cpu分配时间该线程时,由JVM调度执行run()方法. 当你调用start()方法时你将创建新的线程,并且执行在run()方法里的代码.但是如果你直接调用run()方法,它不会创建新的线程也不会执行调用线程的代码. 2.Java中Runnable和Callable有什么不同? Runnable和Callable都代表那些要在不同的线程中执行的任

JAVA多线程和并发基础面试问答【转】

JAVA多线程和并发基础面试问答 多线程和并发问题是Java技术面试中面试官比较喜欢问的问题之一.在这里,从面试的角度列出了大部分重要的问题,但是你仍然应该牢固的掌握Java多线程基础知识来对应日后碰到的问题.(校对注:非常赞同这个观点) Java多线程面试问题 1. 进程和线程之间有什么不同? 一个进程是一个独立(self contained)的运行环境,它可以被看作一个程序或者一个应用.而线程是在进程中执行的一个任务.Java运行环境是一个包含了不同的类和程序的单 一进程.线程可以被称为轻量