回顾
在上一篇 Java基础之线程安全 介绍了怎么处理线程安全问题,主要的思路就是使用线程同步的方法,线程A执行时获取 "锁",然后对共享数据进行操作,操作完毕后退出,线程B获取锁对共享数据进行操作。
- 操作共享数据代码块的判定
- 锁对象的选择(锁需要是同一个)
死锁
我们先前都是讲的都是单个锁的问题,但是当锁有多个时,会不会出现什么其他问题呢? 例如
public class TestMain {
static StringBuffer sb1= new StringBuffer();
static StringBuffer sb2= new StringBuffer();
public static void main(String[] args) {
new Thread(){
public void run(){
synchronized (sb1){
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
sb1.append("A");
synchronized (sb2){
sb2.append("B");
System.out.println(sb1);
System.out.println(sb2);
}
}
}
}.start();
new Thread(){
public void run(){
synchronized (sb2){
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
sb1.append("C");
synchronized (sb1){
sb2.append("D");
System.out.println(sb1);
System.out.println(sb2);
}
}
}
}.start();
}
}
我们创建了两个线程(匿名方式),且有两个锁,执行这段代码,你会发现控制台卡住了,如下图。
我们在编码实践中因尽量避免以上问题,在现实情况下多个锁不可避免时,我们可以设定锁的优先级别 例如A>B 任何线程都需要先获得A 才能获得B,此时避免死锁。更糟糕都情况下,我们可以kill一个线程使得目标锁释放,使得程序继续运行。
线程通信
在Java基础之多线程编程 讲解了多个关于Thread类的方法,回顾下
Thread.currentThread() //获取当前线程
//以下都是线程实例上的方法
setName(); //设置线程名字
getName(); //获取线程名字
yield();//显示释放cpu的执行权
join();//在一个线程执行中调用另一个目标线程的join方法,意味着立马执行目标线程,且执行完毕才回到原线程
isAlive();//判断当前线程是否还存活
sleep();//显示的让线程睡眠
setPriority() //设置当前线程的优先级
getPriority()//获取当前线程的优先级
此时我们补充三个Object类下关于线程的三个方法
wait();//令当前线程挂起放弃cpu且放弃同步资源
notify();//唤醒正在排队等待同步资源的线程,其中优先级最高的结束等待
notifyAll();//唤醒正在排队的等待资源的所有线程。
可见wait方法与notify notifyAll是对应的操作:那么我们可以利用这三个方法使得线程交替执行,即线程相互通信。
注:Object中在三个方法只能在synchronized方法或者同步代码块中执行,否则会抛异常
案例
使两个线程打印1到100 且两个线程交替打印 代码实现:(两个线程打印1到100 没有交替打印版本)
class PrintNum implements Runnable{
int num=1;
@Override
public void run() {
while (true){
synchronized (this) {
if(num<=100){
try {
Thread.currentThread().sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+num);
num++;
}else {
break;
}
}
}
}
}
PrintNum printNum=new PrintNum();
Thread t1=new Thread(printNum);
Thread t2=new Thread(printNum);
t1.setName("A");
t2.setName("B");
t1.start();
t2.start();
利用notify await 实现交替打印代码
class PrintNum implements Runnable{
int num=1;
@Override
public void run() {
while (true){
synchronized (this) {
notify();
if(num<=100){
try {
Thread.currentThread().sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+num);
num++;
}else {
break;
}
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
控制台打印如下
解释下:当第一个线程A进入时notify执行,但此时没有等待状的但线程,打印1后,await(),线程A进入等待状态且释放同步锁,线程B开始执行,进入synchronized代码块内,此时已经获取了锁,notify()后,唤醒了线程A,但是锁没释放,打印2后,await()进入等待状态,然后线程A获得锁资源,又进入同步代码块执行,依次下去边交替打印。仿佛两个线程之间实现了相互通信。
总结
本篇主要讲解了 死锁和线程通信两个概念
- 什么是死锁问题,应该如何避免
- 线程通信中Object类中notify notifyAll await的准确含义和使用,应特别区分sleep与await区别,其中sleep不释放锁资源,而await释放锁资源。
喜欢本文的朋友们,欢迎长按下图关注订阅号"我的编程笔记",收看更多精彩内容~~