多线程之volatile关键字

简介: 多线程之volatile关键字

轻量级同步机制:volatile关键字

volatile的作用

关键作用是使变量在多个线程之间可见

public class VolatileText {
    public static void main(String[] args) throws InterruptedException {
        Student student=new Student();
        new Thread(new Runnable() {
            @Override
            public void run() {
                student.GetMethon();
            }
        }).start();
        Thread.sleep(1000);//睡眠之后修改布尔值
        student.GetName(false);//改变布尔值以结束程序运行
    }
    static  class Student
    {
        public boolean flag=true;
        public Student GetName(boolean flag)
        {
            this.flag=flag;
            return  this;
        }
        public  void  GetMethon()
        {
            System.out.println("开始");
            while (flag){//死循环
            }
            System.out.println("结束");
        }
    }
}

程序并没有因为我修改之后结束运行,因为线程对共享变量具有不可见性,main线程修改布尔值之后,子线程看不到值的修改。因此要想实现线程的可见性这里可以加上volatile关键字修饰公共变量

volatile关键字的作用:使线程在强制公共内存中读取变量值,保证可见性

volatile非原子特性

public class Text10 extends  Thread {
    private volatile static int count;
    @Override
    public void run() {
       Addcount();
    }
    public   static void  Addcount()
    {
        for (int i = 0; i < 1000; i++) {
            count++;
        }
        System.out.println(Thread.currentThread().getName()+"-->"+count);
    }
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            Text10 text10=new Text10();
            text10.start();
        }
    }
}

按道理输出1000的整数倍数才对,但是变量在自增的过程中没有更新到又被读取再修改,因此volatile不具备原子性,正确办法将方法加上synchronized关键字

volatile与synchronized比较

  • volatile关键字是线程同步的轻量级实现,所以volatile性能肯定比synchronized要好,volatile只能修饰变量,而synchronized可以修饰方法代码块,在开发中使用synchronized比例还是挺大的。
  • 多线程访问volatile变量不会发生阻塞,而synchronized可能会阻塞。
  • volatile能保证数据的可见性,但是不能保证原子性,而synchronized可以保证原子性,也可以保证可见性,因为
  • synchronized会将线程的工作内存和主内存进行同步
  • volatile关键字保证多个线程之间的可见性,synchronized关键字解决线程访问公共资源的同步性。
区别 synchronized volatile
使用上 只能用于修饰方法、代码块 只能修饰实例变量或者类关键字
原子性保证 能保证,锁可以保护数据不被打断 无法保证
可见性保证 能保证,排它方式使同步代码串行 能保证,可以读取公共变量
有序性保证 能保证,在同步串行的时候 能保证,禁止JVM以及处理器进行排序
阻塞情况 会发生阻塞 不会发生阻塞

常用原子类进行自增自减操作

i++不是原子操作,除了使用synchronized进行同步,也可以使用AtomicInteger/AtomicLong进行实现

import java.util.concurrent.atomic.AtomicInteger;
public class Text10 extends  Thread {
    private   static AtomicInteger count=new AtomicInteger();
    @Override
    public void run() {
       AddCount();
    }
    public   static void  AddCount()
    {
        for (int i = 0; i < 1000; i++) {
            count.getAndIncrement();//相当于i++
            //count.incrementAndGet();//相当于++i
        }
        System.out.println(Thread.currentThread().getName()+"-->"+count.get());
    }
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            Text10 text10=new Text10();
            text10.start();
        }
    }
}

CAS

CAS(Compare And Swap)是由硬件实现的,

CAS可以将read(读)-modify(修改)-write(写)转化为原子操作

i++自增过程:

从主内存调取i变量值

对i值加1

再把加1过后的值保存到主内存

CAS原理:在把数据更新到主内存时,再次读取主内存变量的值,如果现在变量的值与期望的值一样就更新。

使用CAS原理实现线程安全计数器

public class CASText {
    public static void main(String[] args) {
        CASControl casControl=new CASControl();
        for (int i = 0; i <10000 ; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"==>"+casControl.incrementAndGet());
                }
            }).start();
        }
    }
}
  class CASControl
{
    volatile  static  long value;//使用volatile修饰线程可见性
    public  long getValue()
    {
        return  value;
    }
    private  boolean Expect(long oldValue,long newValue)
    {
        synchronized (this)
        {
            if(value==oldValue)
            {
                value=newValue;
                return  true;
            }
            else
                return  false;
        }
    }
    public  long incrementAndGet()
    {
        long oldvalue;
        long newValue;
        do {
            oldvalue=value;
            newValue=oldvalue+1;
        }while (!Expect(oldvalue, newValue));
        return  newValue;
    }
}

CAS中的ABA问题

CAS实现原子操作背后有一个假设:共享变量当前值与当前线程提供的期望值相同,就认为变量没有被其他线程过。

实际上这种假设不一定成立,假如count=0

A线程对count值修改为10

B线程对count值修改为30

C线程对count值修改为0

当前线程看到count=0,不能认为count没有被其他线程更新,这种结果是否能被接受?

这就是CAS中的ABS问题即共享变量经过了A=》B=》A的更改

是否能够接受ABA问题跟算法实现有关

如果想要规避ABA问题,可以为共享变量引入一个修订号,或者时间戳,每次修改共享变量值,相应的修订号加1.,就会变更为[A,0]=>[B,1]=>[A,2],每次对共享变量的修改都会导致共享变量的增加,通过这个标识就可以判断。AtomicStampedReference类就是基于这个思想产生的。

原子变量类

原子类变量是基于CAS实现的,当对共享变量进行read(读)-modify(修改)-write(写)操作时,通过原子类变量可以保障原子性与可见性,对变量的read(读)-modify(修改)-write(写)操作不是指一个简单的赋值,而是变量的新值,依赖变量的旧值,如自增操作i++,由于volatile只能保证原子的可见性,而不能保证原子性,原变量类内部就是一个借助volatile变量,并且保障了该变量的read-modify-wirte操作的原子性,有时把原子变量看作一个增强的volatile变量,原子变量类有12个

分组 原子变量类
基础数据型 AtomicInteger、AtomicLong、AtomicBoolean
数组型 AtomicIntegerArry、AtomicLongArry、AtomicReferenceArry
字段更新器 AtomocIntegerFiledUpdater、AtomicLongFieldUpdate、AtomicReferenceFiledUpdater
引用型 AtomicReference、AtomicStampedReference、AtomicMarkableReference

使用AtomicLong定义计数器

import java.util.Random;
import java.util.concurrent.atomic.AtomicLong;
public class Text15 {
    //构造方法私有化
    private  Text15(){}
    //私有静态对象
    private  static final Text15 text=new Text15();
    //公共静态方法返回该类的实例
    public  static  Text15 getInstance()
    {
        return  text;
    }
    //使用原子类记录保存请求总数 成功数 失败数
    private  final AtomicLong RequestCount=new AtomicLong(0);
    private  final AtomicLong SuccessCount=new AtomicLong(0);
    private  final AtomicLong FailCount=new AtomicLong(0);
    //进行自增
    public  void  RequestCount()
    {
        RequestCount.incrementAndGet();
    }
    public  void  SuccessCount()
    {
        SuccessCount.incrementAndGet();
    }
    public  void  FailCount()
    {
        FailCount.incrementAndGet();
    }
    //查看总数
    public  long GetRequestCount()
    {
        return  RequestCount.get();
    }
    public  long  GetSuccessCount()
    {
        return SuccessCount.get();
    }
    public  long  GetFailCount()
    {
        return FailCount.get();
    }
}
class  Text16
{
    public static void main(String[] args) throws InterruptedException {
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i <1000 ; i++) {
                    Text15.getInstance().RequestCount();//请求数量
                    int num=new Random().nextInt();
                    if(num%2==0)//如果是偶数就成功
                    {
                        Text15.getInstance().SuccessCount();
                    }
                    else
                        Text15.getInstance().FailCount();
                }
            }
        }).start();
        Thread.sleep(1000);
        System.out.println("请求总数:"+Text15.getInstance().GetRequestCount());
        System.out.println("请求成功"+Text15.getInstance().GetSuccessCount());
        System.out.println("请求失败"+Text15.getInstance().GetFailCount());
    }
}


相关文章
|
3月前
|
存储 SQL 缓存
揭秘Java并发核心:深度剖析Java内存模型(JMM)与Volatile关键字的魔法底层,让你的多线程应用无懈可击
【8月更文挑战第4天】Java内存模型(JMM)是Java并发的核心,定义了多线程环境中变量的访问规则,确保原子性、可见性和有序性。JMM区分了主内存与工作内存,以提高性能但可能引入可见性问题。Volatile关键字确保变量的可见性和有序性,其作用于读写操作中插入内存屏障,避免缓存一致性问题。例如,在DCL单例模式中使用Volatile确保实例化过程的可见性。Volatile依赖内存屏障和缓存一致性协议,但不保证原子性,需与其他同步机制配合使用以构建安全的并发程序。
71 0
|
4月前
|
缓存 安全 算法
Java面试题:如何通过JVM参数调整GC行为以优化应用性能?如何使用synchronized和volatile关键字解决并发问题?如何使用ConcurrentHashMap实现线程安全的缓存?
Java面试题:如何通过JVM参数调整GC行为以优化应用性能?如何使用synchronized和volatile关键字解决并发问题?如何使用ConcurrentHashMap实现线程安全的缓存?
46 0
|
3月前
|
缓存 Java
【多线程面试题二十三】、 说说你对读写锁的了解volatile关键字有什么用?
这篇文章讨论了Java中的`volatile`关键字,解释了它如何保证变量的可见性和禁止指令重排,以及它不能保证复合操作的原子性。
|
4月前
|
设计模式 缓存 安全
Java面试题:工厂模式与内存泄漏防范?线程安全与volatile关键字的适用性?并发集合与线程池管理问题
Java面试题:工厂模式与内存泄漏防范?线程安全与volatile关键字的适用性?并发集合与线程池管理问题
58 1
|
4月前
|
微服务
多线程内存模型问题之在单例模式中,volatile关键字的作用是什么
多线程内存模型问题之在单例模式中,volatile关键字的作用是什么
|
4月前
线程可见性和关键字volatile
线程可见性和关键字volatile
|
4月前
|
存储 安全 Java
Java面试题:请解释Java内存模型,并说明如何在多线程环境下使用synchronized关键字实现同步,阐述ConcurrentHashMap与HashMap的区别,以及它如何在并发环境中提高性能
Java面试题:请解释Java内存模型,并说明如何在多线程环境下使用synchronized关键字实现同步,阐述ConcurrentHashMap与HashMap的区别,以及它如何在并发环境中提高性能
39 0
|
4月前
|
算法 安全 Java
Java面试题:解释JVM中的堆内存分代收集策略,并讨论年轻代和老年代的特点,描述Java中的线程池,并解释线程池的优点,解释Java中的`volatile`关键字的作用和使用场景
Java面试题:解释JVM中的堆内存分代收集策略,并讨论年轻代和老年代的特点,描述Java中的线程池,并解释线程池的优点,解释Java中的`volatile`关键字的作用和使用场景
47 0
|
4月前
|
安全 Java 开发者
Java多线程:synchronized关键字和ReentrantLock的区别,为什么我们可能需要使用ReentrantLock而不是synchronized?
Java多线程:synchronized关键字和ReentrantLock的区别,为什么我们可能需要使用ReentrantLock而不是synchronized?
59 0
|
5月前
|
Java
synchronized关键字在Java中为多线程编程提供了一种简便的方式来管理并发,防止数据竞争和死锁等问题
Java的`synchronized`关键字确保多线程环境中的数据一致性,通过锁定代码段或方法防止并发冲突。它可修饰方法(整个方法为临界区)或代码块(指定对象锁)。例如,同步方法只允许一个线程执行,同步代码块则更灵活,可锁定特定对象。使用时需谨慎,以避免性能影响和死锁。
34 0

热门文章

最新文章