多线程之volatile与synchronized(二)

简介: JMM中主要是围绕并发过程中如何处理原子性,可见性和有序性三个特性来建立的。最终可以保证线程安全性,volatile和synchronized两个关键字又是我们最常碰到与最容易提到的关键字,这次放在一起来讲。

JMM中主要是围绕并发过程中如何处理原子性,可见性和有序性三个特性来建立的。最终可以保证线程安全性,volatile和synchronized两个关键字又是我们最常碰到与最容易提到的关键字,这次放在一起来讲。

img_e454baf956b7ed3884a62f66c94f25f2.png
与文无关

线程安全性:当多个线程访问某个类的时候,不管运行环境采用何种调度方式或这些线程如何交替执行,并且在主调代码中不需要额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的。

原子性、可见性与有序性

首先来看一下这几个特性代表的具体含义。

  • 原子性(Atomicity):原子性是指,一个操作是不可中断的。即使是多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。

    JDK的包中提供了专门的原子包java.util.concurrent.atomic,synchronized关键字还有Lock来让程序在并发环境下具有原子性的特点。

  • 可见性(Visibility):可见性是指当一个线程修改了共享变量的值,其它线程能立即得知这个修改。

    volatile,synchronized和final关键字能实现可见性。使用final关键字需要注意对象逃逸

  • 有序性:如果再本线程内观察,所有操作都是有序的,如果再一个线程中观察另外一个线程,那么所有操作都是无序的。前半句是指“线程内表现为串行”,后半句是指“指令重排序”现象和“工作内存与主内存同步延迟现象”

    volatile和synchronized关键字可以线程之间操作的有序性。

Volatile

一个变量定义为volatile之后,它将具有两种特性:

  1. 保证次变了对所有线程的可见性,一条线程修改了这个值,新值对其它线程是可以立即得知的。
  2. 禁止指令重排优化。

volatile变量在写操作时候,会在写操作后加上store屏障指令,将本地内存刷新到主内存。
volatile变量读操作的时候,会在读操作之前加入一条load屏障指令,从主内存中读取共享变量。

关于JMM的8大操作指令,可以查看我的上篇文章,java内存模型。

volatile变量为什么在并发下不安全?

volatile变量在各个线程的工作内存中也可以存在不一致的情况,但由于每次使用之前都要刷新,执行引擎看不到不一致的情况,因此可以认为不存在一致性问题,但是Java里面的运算并非原子操作。

假如说一个写入值操作不需要依赖依赖这个值的原先值,那么在进行写入的时候我们就不需要进行读取操作。
写入操作对原本的值的时候没有要求,那么所有线程都可以写入新的值,虽然读取到的值是相同的,每个线程的操作也是正确的,但是最终结果却是错误的。


img_33bba3ff49503f8382bfb72ef4015200.png
JMM

感兴趣的可以运行如下代码:

public class VolatileTest {
    public static volatile int count = 0;
    public static final int THREAD_COUNT = 20;

    public static void add(){
        count++;
    }

    public static void main(String[] args) {
        Thread[] threads = new Thread[THREAD_COUNT];
        for (int i = 0; i < THREAD_COUNT; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    add();
                }
            });
            threads[i].start();
        }
        
        for (int i = 0; i < THREAD_COUNT; i++) {
            threads[i].join();
        }        
        
        System.out.println(count);
    }
    
}
// 如果并发正确的话:应该是20000,但是每次运行结果都不到20000
Volatile适合做什么?

适合做标量,当一个线程对某个变量进行读写操作,而其它线程仅仅进行读操作的时候,是可以保证volatile的正确性的。如下:

volatile bool stopped;
public void stop(){
    stopped = true
}

while(!stoppped){
    // 执行操作
}

Synchronized

Synchronized保证了原子性,可见性与有序性,它的工作时对同步的代码块加锁,使得每次只有一个线程进入代码块,从而保证线程安全。synchronized反应到字节码层面就是monitorenter与monitorexit.

注意*:虽然synchonized关键字看起来是万能的,能保证线程安全性,但是越万能的控制往往越伴随着越大的性能影响。

Synchonzied用法
  1. 实例方法上,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
  2. 静态方法上,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
  3. 实例方法代码块.
  4. 静态方法代码块。
 //实例方法
 public synchronized void add(int value){
      this.count += value;
 }
 //静态方法
 public static synchronized void add(int value){
      count += value;
 }
 
 //实例方法代码块 
 public void add(int value){
   synchronized(this){
       this.count += value;   
    }
 }
 
 //静态方法代码块
 public class MyClass {

    public static synchronized void log1(String msg1, String msg2){
       log.writeln(msg1);
       log.writeln(msg2);
    }

  
    public static void log2(String msg1, String msg2){
       synchronized(MyClass.class){
          log.writeln(msg1);
          log.writeln(msg2);  
       }
    }
  }
  
Synchonzied案例
public class SynchronziedTest implements Runnable{
    static int i = 0;
    static int j = 0;
    static SynchronziedTest instance=  new SynchronziedTest();

    @Override
    public void run() {
        for (int j = 0; j < 1000000; j++) {
            increase();
        }
    }

    public synchronized void increase(){
        i++;
    }

    public static void main(String[] args) throws InterruptedException {
        // 注意新建的线程指向的同一个实例,
        // 如果指向不同的实例,那么两个线程关注的锁就不是同一把锁,就会导致线程不安全
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);
        
        //错误的用法
//        Thread t3 = new Thread(new SynchronziedTest());
//        Thread t4 = new Thread(new SynchronziedTest());
        
        
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(i);
    }
}
//结果为:200000

注意创建线程的时候指向同一个实例,才会锁住相同的对象。

最后

这次我们讲了线程安全性的基本原则,然后解释了volatile和synchronized关键字,多线程中不得不掌握的关键字。

参考

  • 《实战Java高并发设计》
  • 《深入理解JVM虚拟机》
  • 《Java并发编程与高并发解决方案》
目录
相关文章
|
3月前
|
存储 安全 Java
synchronized 王的后宫总管,线程是王妃
synchronized 王的后宫总管,线程是王妃
16 0
|
17天前
|
设计模式 安全 Java
Java并发编程实战:使用synchronized关键字实现线程安全
【4月更文挑战第6天】Java中的`synchronized`关键字用于处理多线程并发,确保共享资源的线程安全。它可以修饰方法或代码块,实现互斥访问。当用于方法时,锁定对象实例或类对象;用于代码块时,锁定指定对象。过度使用可能导致性能问题,应注意避免锁持有时间过长、死锁,并考虑使用`java.util.concurrent`包中的高级工具。正确理解和使用`synchronized`是编写线程安全程序的关键。
|
7月前
|
人工智能 安全 JavaScript
3. Java 的线程安全机制之`synchronized`
3. Java 的线程安全机制之`synchronized`
|
7月前
|
Arthas 测试技术
如何检测由synchronized或Lock引起的线程阻塞问题
如何检测由synchronized或Lock引起的线程阻塞问题
108 0
|
3月前
|
安全 Java 调度
线程学习(2)线程创建,等待,安全,synchronized(三)
线程学习(2)线程创建,等待,安全,synchronized(三)
26 0
|
3月前
|
安全 Java 调度
线程学习(2)线程创建,等待,安全,synchronized(二)
线程学习(2)线程创建,等待,安全,synchronized(二)
27 0
|
3月前
|
安全 Java 调度
线程学习(2)线程创建,等待,安全,synchronized(一)
线程学习(2)线程创建,等待,安全,synchronized(一)
40 0
|
8月前
|
存储 Oracle Java
多线程进阶学习04------Synchronized详解(1)
多线程进阶学习04------Synchronized详解
43 0
【并发技术03】传统线程互斥技术—synchronized
【并发技术03】传统线程互斥技术—synchronized
|
3月前
|
Java
多线程与并发编程:解释什么是死锁,并给出一个在Java中发生死锁的例子。描述一下Java中的volatile关键字的作用,以及它与synchronized的区别。
多线程与并发编程:解释什么是死锁,并给出一个在Java中发生死锁的例子。描述一下Java中的volatile关键字的作用,以及它与synchronized的区别。
28 0

热门文章

最新文章