Java核心复习 —— 线程间的协作
一、线程间的协作
线程之间如何进行协作,使得多个任务可以一起解决同一个问题?
wait()与notify()
wait()
方法的作用是,让线程一直处于等待中,在等待过程中,锁是释放的。直到notify()
或notifyAll()
发生,才被唤醒。
wait()、notify()和notifyAll()
都是Object的公有方法。为什么要这样设计?因为锁也是对象的一部分。
注意
实际上,只能在同步方法或同步代码块
中调用wait()、notify()、notifyAll()
,否则会抛出IllegalMonitorStateException
异常,该异常是一种运行时异常
。
sleep()
sleep()
也会让线程进入等待中,不同的是,wait()
会释放锁,而sleep()
不会释放锁。
join()
在一个线程B中,调用另一个线程A的join方法,B线程会进入等待,直到A线程完成任务,B线程继续执行。
public class JoinDemo {
class A extends Thread {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("AAAAA");
}
}
class B extends Thread {
private A a;
public B(A a) {
this.a = a;
}
@Override
public void run() {
try {
a.join();
System.out.println("BBBB");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void test() {
A a = new A();
B b = new B(a);
a.start();
b.start();
}
public static void main(String[] args) {
JoinDemo joinDemo = new JoinDemo();
joinDemo.test();
}
}
运行效果
二、题目一
首先出一个简单的题目,需要用到A和B两个线程来完成这个任务。解法会在本节最后
列出。
要求使用A,B两个线程按顺序打印1-100,A打印奇数,B打印偶数。
题目分析
该题目可以有多种解法,其中一种方法可以使用wait()
和notify()
来解前面那题。
首先要想
①两个线程要协作
完成打印的任务,那么为了防止两个线程同时打印
,就应该对打印的方法加上一道synchronized锁
。
②那么synchronized锁应该就要用对象锁
(使用同步方法或同步代码块都是可以的),而不是类锁。思考下,为什么用类锁不行?
③怎么保证顺序打印呢?A、B两个线程要轮番获得锁
,也就是A先获得锁完成打印任务,紧接着下次就不能获得锁了,要由B获得,这时就可以想到让A打印完,进入等待
状态,并将锁释放
出来交给B,B获得锁之后,完成打印任务,让A恢复执行
。B再进入等待状态。
也就是
A获得锁,执行任务 ——> A唤醒B ——> A释放锁 ,进入等待——> B获得锁,执行任务 ——> B唤醒A ——> B释放锁,进入等待 ——> A获得锁,执行任务 ——> A唤醒B ——> A、进入等待 ——> B获得锁,执行任务 ——> 依次类推.........
④于是就有了 synchronized { task()、notify()、wait()}
的代码思路
运行代码
public class PrintOddEven {
//定义打印的方法
public synchronized void print(String str,int num){
System.out.println(str+num);
notify();
try {
//防止最后到了100时,只有等待,没有唤醒
if(100 != num){
wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//奇数打印线程
class Odd implements Runnable{
@Override
public void run() {
for(int i=1;i<100;i+=2){
print("奇数:",i);
}
}
}
//偶数打印线程
class Even implements Runnable{
@Override
public void run() {
for(int i=2;i<=100;i+=2){
print("偶数:",i);
}
}
}
public static void main(String[] args) {
PrintOddEven p = new PrintOddEven();
Odd odd = p.new Odd();
Even even = p.new Even();
new Thread(odd).start();
new Thread(even).start();
}
}
运行结果
题目二
如果再多一个线程,现在有A、B、C三个线程,分别打印出A,B,C,要如何实现?
其实跟上面的题的思路很相似,只是额外加上了一个条件,A执行完,通知B执行,B执行完通知C执行。问题的关键是,如何实现A释放锁,由B获得锁,而不让C获得。
public class PrintABC1 implements Runnable {
private Object prev;
private Object curr;
private String value;
public Object getPrev() {
return prev;
}
public void setPrev(Object prev) {
this.prev = prev;
}
public Object getCurr() {
return curr;
}
public void setCurr(Object curr) {
this.curr = curr;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public PrintABC1(Object prev, Object curr, String value) {
this.prev = prev;
this.curr = curr;
this.value = value;
}
public static void main(String[] args) {
Object lockA = new Object();
Object lockB = new Object();
Object lockC = new Object();
Thread a = new Thread(new PrintABC1(lockC, lockA, "A"));
Thread b = new Thread(new PrintABC1(lockA, lockB, "B"));
Thread c = new Thread(new PrintABC1(lockB, lockC, "C"));
a.start();
sleep();
b.start();
sleep();
c.start();
sleep();
}
static void sleep() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void run() {
for (int i = 1; i <= 10; i++) {
synchronized (prev) {
synchronized (curr) {
System.out.println(value);
curr.notify();
}
if (i < 10) {
try {
prev.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
参考文档
Java编程思想
两个线程,分别打印[1,3,5]和[2,4,6],写一个程序,打印[1,2,3,4,5,6]。
Java线程面试题 Top 50
关于作者
后端程序员,五年开发经验,从事互联网金融方向。技术公众号「清泉白石」。如果您在阅读文章时有什么疑问或者发现文章的错误,欢迎在公众号里给我留言。