package venus;
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();//最后结果多为null,先后执行顺序无法确定
}
}
显然上面的代码无法达到显示时间的要求,要想显示出时间,就需要先执行时间线程再执行显示器线程。这时候就需要线程间的协作来完成功能。
一种方法:
package venus;
import java.util.Date;
public class Watch {
private static String time;
static class Display extends Thread{
Time timeThread;
Display(Time timeThread){
this.timeThread = timeThread;
}
@Override
public void run() {
if(time==null){
try {
timeThread.join();//如果time为null,则显示器线程阻塞,当时间线程结束后再进行显示器线程。值得注意的是,当到此处时time已经有值,即时间线程结束,显示器线程不会阻塞。
} 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 timeThread = new Time();
timeThread.start();
new Display(timeThread).start();
}
}
另一种方法:
synchronized关键字只是起到了多个线程“串行”执行临界区中代码的作用,但是哪个线程先执行,哪个线程后执行依无法确定,Object类中的wait()、notify()和notifyAll()三个方法解决了线程间的协作问题,通过这三个方法的“合理”使用可以确定多线程中线程的先后执行顺序:
wait():对象锁调用了wait()方法会使当前持有该对象锁的线程处于线程等待状态同时该线程释放对对象锁的控制权,直到在其他线程中该对象锁调用notify()方法或notifyAll()方法时等待此对象锁的线程才会被唤醒。
notify():对象锁调用notify()方法就会唤醒在此对象锁上等待的单个线程。
notifyAll():对象锁调用notifyAll()方法就会唤醒在此对象锁上等待的所有线程;调用notifyAll()方法并不会立即激活某个等待线程,它只能撤销等待线程的中断状态,这样它们就能够在当前线程退出同步方法或同步代码块法后与其它线程展开竞争,以争取获得资源对象来执行。
谁调用了wait方法,谁就必须调用notify或notifyAll方法,并且“谁”是对象锁。
package venus;
import java.util.Date;
public class Watch {
private static String time;
static class Display extends Thread{
Time timeThread;
Object object;
Display(Time timeThread,Object object){
this.timeThread = timeThread;
this.object = object;
}
@Override
public void run() {
if(time==null){
/* try {
timeThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}*/
synchronized (object){
try {
object.wait();
} 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){
object.notify();
}
}
}
public static void main(String[] args) {
final Object object = new Object();
Time timeThread = new Time(object);
timeThread.start();
new Display(timeThread,object).start();
}
}
使用Object类中的wait()、notify()和notifyAll()三个方法需要注意以下几点:
- wait()方法需要和notify()或notifyAll()方法中的一个配对使用,且wait方法与notify()或notifyAll()方法配对使用时不能在同一个线程中。
- wait()方法、notify()方法和notifyAll()方法必须在同步方法或者同步代码块中使用,否则出现IllegalMonitorStateException 异常。
- 调用wait()方法、notify()方法和notifyAll()方法的对象必须和同步锁对象是一个对象。
sleep()与wait()的区别:
sleep()方法被调用后当前线程进入阻塞状态,但是当前线程仍然持有对象锁,在当前线程sleep期间,其它线程无法执行sleep方法所在临界区中的代码。
而对象锁调用了wait()方法会使当前持有该对象锁的线程处于线程等待状态同时该线程释放对对象锁的控制权,在当前线程处于线程等待期间,其它线程可以执行wait方法所在临界区中的代码。
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) {
System.out.println(getName());
try {
lockObj.wait();//若此处换为sleep();则只能打印两个中的一个
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}