线程协作
import java.util.Date;
public class Watch {
private static String time;
static class Display extends Thread{
@Override
public void run() {
System.out.println(time);
}
}
static class Time extends Thread{
@Override
public void run() {
time = new Date().toString();
}
}
public static void main(String[] args) {
new Time().start();
new Display().start();
}
}
两个线程抢占式执行,但由于Display更加简单,因此大多数情况下先执行Display线程。因此大多数情况下执行Display时time为null。输出为null。
修改代码:
package 线程间数据共享;
import java.util.Date;
public class Watch {
private static String time;
static class Display extends Thread{
@Override
public void run() {
if(time==null) {
try {
sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(time);
}
}
static class Time extends Thread{
@Override
public void run() {
time = new Date().toString();
}
}
public static void main(String[] args) {
new Time().start();
new Display().start();
}
}
此时,由于time为null时,Display阻塞,则在阻塞结束后,Time以执行完毕,time不为null,此时可输出时间。
但是在实际操作过程中,我们无法判断sleep()中所需要限制的时间,因此我们需要通过其他方法实现。
import java.util.Date;
public class Watch {
private static String time;
static class Display extends Thread{
Time timeThread;
Object object;
Display(Time time) {
this.timeThread=time;
}
@Override
public void run() {
if(time==null) {
try {
timeThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(time);
}
}
static class Time extends Thread{
@Override
public void run() {
time = new Date().toString();
}
}
public static void main(String[] args) {
Time time = new Time();
time.start();
new Display(time).start();
}
}
此时,若time为null,则执行join方法,Display线程阻塞,执行Time线程,Time线程结束后,Display线程继续执行。
另一种方式是使用wait()方法:
import java.util.Date;
public class Watch {
private static String time;
static class Display extends Thread{
Time timeThread;
Object object;
Display(Time time,Object object) {
this.timeThread=time;
this.object=object;
}
@Override
public void run() {
if(time==null) {
synchronized (object) {
try {//如果没有synchronized关键字,则会出现报错
object.wait();//线程执行该方法,则该线程阻塞,直到调用notify方法
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println(time);
}
}
static class Time extends Thread{
Object object;
Time(Object object){
this.object=object;
}
@Override
public void run() {
time = new Date().toString();
synchronized (object) {//如果没有synchronized关键字,则会出现报错
object.notify();
}
}
}
public static void main(String[] args) {
final Object OBJECT = new Object();
Time time = new Time(OBJECT);
time.start();
new Display(time,OBJECT).start();
}
}
此时也能有相似的结果:若time为null,则执行wait方法,Display线程阻塞,执行Time线程,Time线程执行notify方法后,Display线程继续执行。
synchronized关键字只是起到了多个线程“串行”执行临界区中代码的作用,但是哪个线程先执行,哪个线程后执行依无法确定,Object类中的wait()、notify()和notifyAll()三个方法解决了线程间的协作问题,通过这三个方法的“合理”使用可以确定多线程中线程的先后执行顺序:
1、wait():对象锁调用了wait()方法会使当前持有该对象锁的线程处于线程等待状态同时该线程释放对对象锁的控制权,直到在其他线程中该对象锁调用notify()方法或notifyAll()方法时等待此对象锁的线程才会被唤醒。
2、notify():对象锁调用notify()方法就会唤醒在此对象锁上等待的单个线程。
3、notifyAll():对象锁调用notifyAll()方法就会唤醒在此对象锁上等待的所有线程;调用notifyAll()方法并不会立即激活某个等待线程,它只能撤销等待线程的中断状态,这样它们就能够在当前线程退出同步方法或同步代码块法后与其它线程展开竞争,以争取获得资源对象来执行。
一句话:谁调用了wait方法,谁就必须调用notify或notifyAll方法,并且“谁”是对象锁。
使用Object类中的wait()、notify()和notifyAll()三个方法需要注意以下几点:
1、wait()方法需要和notify()或notifyAll()方法中的一个配对使用,且wait方法与notify()或notifyAll()方法配对使用时不能在同一个线程中。
2、wait()方法、notify()方法和notifyAll()方法必须在同步方法或者同步代码块中使用,否则出现IllegalMonitorStateException 异常。
3、调用wait()方法、notify()方法和notifyAll()方法的对象必须和同步锁对象是一个对象。
sleep()方法和wait()方法区别
1、sleep()方法被调用后当前线程进入阻塞状态,但是当前线程仍然持有对象锁,在当前线程sleep期间,其它线程无法执行sleep方法所在临界区中的代码。
import java.util.Date;
public class sleep和wait区别 {
public static void main(String[] args) {
Object lockObj = new Object();
new PrintThread("1号打印机"+new Date(),lockObj).start();
new PrintThread("2号打印机"+new Date(),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) {
System.out.println(getName());
try {
sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
当某台打印机执行临界区中的代码,输出线程名后由于调用了sleep方法,从而使得该打印机线程阻塞3秒,在这3秒期间因该打印机线程仍然持有对象锁,从而导致另一台打印机线程只能在3秒后才能执行临界区中的代码。
2、对象锁调用了wait()方法会使当前持有该对象锁的线程处于线程等待状态同时该线程释放对对象锁的控制权,在当前线程处于线程等待期间,其它线程可以执行wait方法所在临界区中的代码。
wait方法
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();
}
}
尽管while循环是死循环,但由于对象锁lockObj调用了wait()方法,使得分别持有该lockObj对象锁的“1号计数器”线程和“2号计数器”线程处于线程等待状态,所以循环并没有继续下去。
输出:1号计数器:1 2号计数器:1。