volatile关键字和synchronize关键字

xiaoxiao2021-02-28  19

在java多线程中估计会经常见到volatile和synchronize这两个关键字,今天做个笔记记录下两个关键字的区别:

synchronize:

synchronize关键字我们遇到的概率很大(虽然现在基本上都用重用锁ReentrantLock),它可以去修饰方法或者代码块,可以用一个对象去做锁或者用类class做锁,也就是对象锁和类锁,最好不要使用常量做锁的关键字例如字符串,这样很可能会造成锁的不释放问题(常量池只有一个引用),并且加锁代码块里面不要修改这个锁,不然锁就会失效,其他方法会获得锁了 (对象属性发生改变了不会影响,只是引用),他会让一堆线程对一个资源进行抢占,抢占到的线程就可以执行,没抢占到的线程就会等待,直到占用资源的线程执行完成(互斥性),它具有原子性。

volatile:

volatile关键字,它不具有原子性,它只是对其他线程具有可见性。


其中原子性和可见性可以拿一个很经典的线程安全的例子来说明(多个线程对同一个数字类型变量进行加1),当有多个线程操作同一个共享变量的时候,java中有一个逻辑上面的内存模型(JMM)。每个线程都是独立的且他们都有一个工作内存和一个主内存(共享内存),为了增加运行速度,jdk1.5之后 执行线程的时候会单独分配内存空间,会将主内存的副本copy到自己的工作内存空间中,所以当多个线程对同一个共享变量进行加1的操作就会造成线程安全问题,比如3个线程对初始值等于0的i进行加1操作,很有可能三个线程将i=0这个初始值都拿到了自己的工作内存中,然后进行加1,然后刷新到主内存中,得到最后的结果可能是1,当然也可能是2或者3,这样就不具有原子性了。当对操作用synchronize关键字来修饰的话,就只运行从主内存进行操作,并且操作是互斥的,一个线程对i进行加1,其他线程只有等待,当该线程执行完成后,其他线程才能执行这个方法,这就具有原子性了。 而如果加了volatile只是让变量具有了可见性,该关键字通过CPU的内存屏障指令让所有线程操作都在主内存中进行,这样每个线程都能获取到最新的值,但是可能会有多个线程同时对这个i进行加1,所以volatile只是有可见性不具有原子性。(当然这种情况可以使用Atomic系列的对象,他是原子性的)

其中上面JMM只是一个逻辑模型,他可能发生计算机物理内存模型的寄存器内、高速缓存(cpu一级缓存、二级缓存等)、内存条任意一个里面。

现在的计算机都是多核心多线程的所以为了提高性能而采取乱序执行,所以才有了CPU的内存屏障指令 ,就是让内存屏障之前的所有写操作都要写入内存中。

其中synchronize和volatile关键字能防止指令的重排序(还包括比如join或者CountDownLatch等等都能防止指令重排序)

重排序就是为了程序的执行效率而产生的,之前也说了现在的电脑都是多核心且多线程的,如果有两个操作:

(1) String str = read(磁盘) (2) String a = 1+1;

在上面的两个操作中,(1)需要去读取磁盘东西的非常耗时,而(2)操作很快,如果没有指令重排序的机制,那么(2)操作只能等(1)操作执行完成后才能执行,这样就降低了计算机的性能(导致IO很忙,而cpu却很闲)。如果有指令重排序的机制,该机制会判断这两个操作是否是相互依赖的,如果不是那就可以让(1)和(2)操作同时执行。

其中重排序可能发生在:

1)编译器优化的重排序(编译级别的重排序 编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序)

2)指令级并行的重排序(机器指令级重排序 现代处理器采用了指令级并行技术来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序)

3)内存系统的重排序 (处理器缓存和读/写缓冲区读导致的重排序)

转载请注明原文地址: https://www.6miu.com/read-1950263.html

最新回复(0)