JUC之线程通信(第一部分)

217 阅读3分钟

线程通信

「这是我参与2022首次更文挑战的第3天,活动详情查看:2022首次更文挑战」。

对上次多线程编程步骤补充(中部):

  1. 创建资源类,在资源类中创建属性和操作方法

  2. 在资源类里面操作

    • 判断
    • 干活
    • 通知
  3. 创建多个线程,调用资源类的操作方法

线程通信的实现例子:

两个线程,实现对一个初始变量为0进行操作,一个线程对其+1,一个线程对其-1,使得变量结果不改变

使用Synchronized实现的线程通信:

 package com.JUC;
 ​
 /**
  * 创建资源类
  */
 class Share{
     //初始值
     private int number = 0;
 ​
     //创建方法
     public synchronized void incr() throws InterruptedException {
         //判断 干活 通知
         if(number != 0){
             this.wait();
         }
         number++;
         System.out.println(Thread.currentThread().getName()+"::"+number);
         //通知其他线程
         this.notifyAll();
         //System.out.println(this.getClass());
     }
     public synchronized void decr() throws InterruptedException {
         if(number != 1){
             this.wait();
         }
         number--;
         System.out.println(Thread.currentThread().getName()+"::"+number);
         //唤醒其他的线程,这里的this指代在方法中指调用该方法的对象
         this.notifyAll();
     }
 ​
 }
 public class ThreadSignaling {
     public static void main(String[] args) throws InterruptedException {
         Share share = new Share();
 ​
         new Thread(()->{
             for (int i = 0; i < 10; i++) {
                 try {
                     share.incr();
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }
         },"AAA").start();
 ​
         new Thread(()->{
             for (int i = 0; i < 10; i++) {
                 try {
                     share.decr();
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }
         },"BBB").start();
     }
 }

volatile和synchronized关键字

volatile:即可见性,当修改一个变量的时候,如果该变量是通过volatile修饰的,那么其他所有的线程都会感知到该变量的变化情况。

如果不使用该关键字的话:

可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

举个简单的例子,看下面这段代码:

 //线程1执行的代码
 int i = 0;
 i = 10;
 ​
 //线程2执行的代码
 j = i;

 假若执行线程1的是CPU1,执行线程2的是CPU2。由上面的分析可知,当线程1执行 i =10这句时,会先把i的初始值加载到CPU1的高速缓存中,然后赋值为10,那么在CPU1的高速缓存当中i的值变为10了,却没有立即写入到主存当中。

  此时线程2执行 j = i,它会先去主存读取i的值并加载到CPU2的缓存当中,注意此时内存当中i的值还是0,那么就会使得j的值为0,而不是10.

  这就是可见性问题,线程1对变量i修改了之后,线程2没有立即看到线程1修改的值

上述的解释其实可以对应到书中的以下片段:

Java支持多个线程同时访问一个对象或者对象的成员变量,由于每个线程可以拥有这个变量的拷贝(虽然对象以及成员变量分配的内存是在共享内存中的,但是每个执行的线程还是可以拥有一份拷贝,这样做的目的是加速程序的执行,这是现代多核处理器的一个显著特性),所以程序在执行过程中,一个线程看到的变量并不一定是最新的。

使用关键字synchronized可以修饰方法或者同步块;

作用:确保多个线程在同一时刻,只能由一个线程处于方法或者同步块中,它保证了线程对变量访问的可见性和排他性。

任何一个对象都有其对应的监视器,当这个对象由同步块或者同步方法调用的时候,需要进行以下逻辑:

image-20211218204458960

任意线程对Object(Object由synchronized保护)的访问,首先要获得Object的监视器。如果获取失败,线程进入同步队列,线程状态变为BLOCKED。当访问Object的前驱(获得了锁的线程)释放了锁,则该释放操作唤醒阻塞在同步队列中的线程,使其重新尝试对监视器的获取。