文章目录
一、如何实现线程协作?
1.synchronized关键字
synchronized关键字只是起到了多个线程“串行”执行临界区中代码的作用,但是哪个线程先执行,哪个线程后执行依无法确定
2.解决线程协作问题
“合理”使用Object类中的wait()、notify()和notifyAll()方法可以确定多线程中线程的先后执行顺序
①wait()方法
对象锁调用了wait()方法会使当前持有该对象锁的线程处于线程等待状态同时该线程释放对对象锁的控制权,直到在其他线程中该对象锁调用notify()方法或notifyAll()方法时等待此对象锁的线程才会被唤醒。
package multithreading;
class CounterThread extends Thread {
private Object lockObj;
public CounterThread(String threadName, Object lockObj) {
super(threadName);
this.lockObj = lockObj;
}
@Override
public void run() {
int i = 1;
while (true) {
synchronized (lockObj) {
System.out.println(getName() + ":" + i);
try {
lockObj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
i++;
}
}
}
}
public class Test {
public static void main(String[] args) {
Object lockObj = new Object();
new CounterThread("1号计数器",lockObj).start();
new CounterThread("2号计数器",lockObj).start();
}
}
不论1号计数器线程还是2号计数器线程先获得CPU的资源,进入就绪状态,获得锁,输出线程名,调用wait方法进入阻塞状态释放对象锁
另一个线程获得CPU资源和对象锁后,输出线程名,调用wait方法进入阻塞状态释放对象锁
此时主线程已经死亡,现有的两个线程都处于阻塞状态,而且都需要notify方法来唤醒,故陷入了一直的等待状态,程序不会结束
import java.text.*;
import java.util.Date;
public class Test {
public static void main(String[] args) {
TimeThread timeThread = new TimeThread ();
timeThread.start();
synchronized (timeThread) {
try {//哪个线程执行wait方法则哪个线程阻塞并释放对象锁,并不是调用wait方法的线程对象阻塞
timeThread.wait();//为什么时间线程没有进入阻塞状态呢?——timeThread变量所代表的对象充当对象锁,由于该代码在main方法中,也就是说将来由主线程执行临界区中的代码,也就是说主线程是持有对象锁的线程,主线程执行完“timeThread.wait()”代码后进入阻塞状态,是主线程进入阻塞状态而非时间线程,这也是main方法中“System.out.println("main方法");”代码不执行的原因。
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("main方法");//为什么这行代码不执行?——由于主线程进入了阻塞状态,并且timeThread对象并没有调用notify方法,所以该行代码不执行
}
}
class TimeThread extends Thread{
@Override
public void run() {
DateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
while (true) {
String currentTime = dateFormat.format(new Date());
System.out.println(currentTime);
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
②notify()方法
对象锁调用notify()方法就会唤醒在此对象锁上等待的单个线程。
③notifyAll()方法
对象锁调用notifyAll()方法就会唤醒在此对象锁上等待的所有线程;调用notifyAll()方法并不会立即激活某个等待线程,它只能撤销等待线程的中断状态,这样它们就能够在当前线程退出同步方法或同步代码块法后与其它线程展开竞争,以争取获得资源对象来执行。
package multithreading;
class CounterThread extends Thread {
private Object lockObj;
public CounterThread(String threadName, Object lockObj) {
super(threadName);
this.lockObj = lockObj;
}
@Override
public void run() {
int i = 1;
while (true) {
synchronized (lockObj) {//不论哪一个线程先获得对象锁,输出名字和1后都执行lockObj.wait()方法进入阻塞状态
System.out.println(getName() + ":" + i);//并且释放了对象锁,另一个线程获得,也输出名字和1后都执行lockObj.wait()方法进入阻塞状态
try {
lockObj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
i++;
}
}
}
}
class NotifyAllThread extends Thread {
private Object lockObj;
public NotifyAllThread(Object lockObj) {
this.lockObj = lockObj;
}
@Override
public void run() {
synchronized (lockObj) {
System.out.println("notifyAll方法执行完毕");
lockObj.notifyAll();
}
}
}
public class Test {
public static void main(String[] args) {
Object lockObj = new Object();//共享对象
new CounterThread("1号计数器", lockObj).start();//1号计数器进入就绪状态
new CounterThread("2号计数器", lockObj).start();//2号计数器进入就绪状态
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new NotifyAllThread(lockObj).start();
}
}
①不论哪一个线程先获得对象锁,输出名字和1后都执行lockObj.wait()方法进入阻塞状态
并且释放了对象锁
②另一个线程获得对象锁,也输出名字和1后都执行lockObj.wait()方法进入阻塞状态
③主线程获得CPU资源,进入运行状态,阻塞5s后,NotifyAllThread线程就绪
④NotifyAllThread线程调用run方法,获得对象锁,执行notifyAll方法
⑤两个阻塞的线程都被唤醒
⑥这两个线程醒来后,又把刚刚的过程执行了一边
⑦但是这一次没有“谁”调用notify或notifyAll方法,两个线程又陷入了等待阻塞状态,程序一直无输出的运行下去
谁调用了wait方法,谁就必须调用notify或notifyAll方法,并且“谁”是对象锁
二、实现线程协作需要注意什么?
1.wait()方法需要和notify()或notifyAll()方法中的一个配对使用
2.wait方法与notify()或notifyAll()方法配对使用时不能在同一个线程中
public class ElectronicWatch {
String currentTime;
DisplayThread displayThread = new DisplayThread();
public static void main(String[] args) {
new ElectronicWatch().displayThread.start();//显示器线程就绪
}
class DisplayThread extends Thread{
@Override
public void run() {
synchronized (this) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
this.notify();//由于wait方法与notify()定义在同一个线程中
//该线程执行完wait方法后就处于等待状态,不会继续执行后面的代码
//该线程一直等待对象锁调用wait方法,但是等不到
}
}
}
}
3.wait()方法、notify()方法和notifyAll()方法必须在同步方法或者同步代码块中使用,否则出现IllegalMonitorStateException 异常
package multithreading;
class CounterThread extends Thread {
private Object lockObj;
public CounterThread(Object lockObj) {
this.lockObj = lockObj;
}
@Override
public void run() {
try {
lockObj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Test {
public static void main(String[] args) {//主线程
Object lockObj = new Object();//定义一个变量做对象锁
new CounterThread(lockObj).start();//计数器线程就绪
}
}
4.调用wait()方法、notify()方法和notifyAll()方法的对象必须和同步锁对象是一个对象
package multithreading;
import java.text.SimpleDateFormat;
import java.util.Date;
public class ElectronicWatch {
String currentTime;
DisplayThread displayThread = new DisplayThread();
public static void main(String[] args) {
new ElectronicWatch().displayThread.start();
}
class DisplayThread extends Thread{
@Override
public synchronized void run() {
new TimeThread().start();
try {
this.wait();//这里的this指的是显示器线程对象
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(currentTime);
}
}
class TimeThread extends Thread{
@Override
public synchronized void run() {
try {
sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
String pattern = "yyyy-MM-dd HH:mm:ss";
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
currentTime = sdf.format(new Date());
this.notify();//这里的this指的是时间线程对象,两个this不是同一个对象
//调用wait方法和notify方法的对象不同,故显示器线程不能被唤醒
}
}
}
三、同样是阻塞sleep方法和wait方法有什么区别?
sleep()方法被调用后当前线程进入阻塞状态,但是当前线程仍然持有对象锁,在当前线程sleep期间,其它线程无法执行sleep方法所在临界区中的代码
public class Test {
public static void main(String[] args) {
Object lockObj = new Object();
new PrintThread("1号打印机",lockObj).start();
new PrintThread("2号打印机",lockObj).start();
}
}
class PrintThread extends Thread {
private Object lockObj;
public PrintThread(String threadName, Object lockObj) {
super(threadName);
this.lockObj = lockObj;
}
@Override
public void run() {
synchronized (lockObj) {//当某台打印机执行临界区中的代码,输出线程名后由于调用了sleep方法,从而使得该打印机线程阻塞30秒,在这30秒期间因该打印机线程仍然持有对象锁,从而导致另一台打印机线程只能在30秒后才能执行临界区中的代码。
System.out.println(getName());
try {
sleep(30000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
wait方法被调用后当前线程进入阻塞状态,并且释放了对象锁,知道调用wait方法的对象调用notify方法或notifyAll方法是,当前线程才离开阻塞状态
把上述sleep方法换为wait方法,则输出完一个线程后立即输出另一个线程,且程序执行不会停止,因为调用wait方法的对象没有调用notify方法时处于阻塞状态的线程被唤醒