线程通信
「这是我参与2022首次更文挑战的第3天,活动详情查看:2022首次更文挑战」。
对上次多线程编程步骤补充(中部):
-
创建资源类,在资源类中创建属性和操作方法
-
在资源类里面操作
- 判断
- 干活
- 通知
-
创建多个线程,调用资源类的操作方法
线程通信的实现例子:
两个线程,实现对一个初始变量为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可以修饰方法或者同步块;
作用:确保多个线程在同一时刻,只能由一个线程处于方法或者同步块中,它保证了线程对变量访问的可见性和排他性。
任何一个对象都有其对应的监视器,当这个对象由同步块或者同步方法调用的时候,需要进行以下逻辑:
任意线程对Object(Object由synchronized保护)的访问,首先要获得Object的监视器。如果获取失败,线程进入同步队列,线程状态变为BLOCKED。当访问Object的前驱(获得了锁的线程)释放了锁,则该释放操作唤醒阻塞在同步队列中的线程,使其重新尝试对监视器的获取。