例子:现假设有一块钟表对象,钟表有两个线程,一个是查询当前时间,另一个是将时间显示出来。需要显示出正确的时间。
分析:这个题目看起来只是需要简单的实例化一个钟表,然后定义两个线程,并且执行就可以了。于是使用下面的代码:
import java.text.SimpleDateFormat;
public class Clock {
private String currentTime;
/**
*获取当前时间
*/
class TimeThread extends Thread{
@Override
public void run() {
String pattern = "yyyy-MM-dd hh:mm:ss";
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
currentTime = sdf.format(new Date());
}
}
/**
* 显示时间
*/
class ShowThread extends Thread{
@Override
public void run() {
System.out.println("现在的时间是:"+currentTime);
}
}
public static void main(String[] args) {
Clock clock = new Clock();
TimeThread timeThread = clock.new TimeThread();
ShowThread showThread = clock.new ShowThread();
timeThread.start();
showThread.start();
}
}
看似完美的解决方案,可是多执行几次,问题就出现了:,执行Java代码之后,由于线程之间的竞争关系,导致很多次显示时间的线程先执行,而获取时间的线程后执行,不能显示正确的时间。在这种情况下,以下三种方法可以避免这种情况的发生:
方案一:ShowThread调用sleep方法,一秒钟之后再执行
import java.text.SimpleDateFormat;
import java.util.Date;
public class Clock {
private String currentTime;
/**
*获取当前时间
*/
class TimeThread extends Thread{
@Override
public void run() {
String pattern = "yyyy-MM-dd hh:mm:ss";
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
currentTime = sdf.format(new Date());
}
}
/**
* 显示时间
*/
class ShowThread extends Thread{
@Override
public void run() {
try {
this.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("现在的时间是:"+currentTime);
}
}
public static void main(String[] args) {
Clock clock = new Clock();
TimeThread timeThread = clock.new TimeThread();
ShowThread showThread = clock.new ShowThread();
timeThread.start();
showThread.start();
}
}
此时可以解决线程竞争的问题,每次都会显示时间,但是……一秒钟之后再显示时间,显然时间并不精确,这种方案并不十分完美。
方案二:使用join方法
join方法的特点:执行该方法的线程进入阻塞状态,直到调用该方法的线程结束后再继续向下执行。方案如下:
package playground;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Clock {
String currentTime;
class ShowThread extends Thread{
TimeThread timeThread ;
public ShowThread(TimeThread timeThread) {
this.timeThread = timeThread;
}
@Override
public void run() {
try {
timeThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前时间为:"+currentTime);
}
}
class TimeThread extends Thread{
@Override
public void run() {
String pattern = "yyyy-MM-dd HH:mm:ss";
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
currentTime = sdf.format(new Date());
}
}
public static void main(String[] args) {
Clock clock = new Clock();
TimeThread timethread = clock.new TimeThread();
ShowThread showThread = clock.new ShowThread(timethread);
timethread.start();
showThread.start();
}
}
在这个解决方案中,显示时间的线程执行join方法,而获取时间的线程调用join方法,因此显示时间的线程会等待获取时间的线程执行完再执行,从而可以保证每次都可以获得正确的时间。
方案三:使用wait和notify方法:
对象锁调用了wait()方法会使当前持有该对象锁的线程处于线程等待状态同时该线程释放对对象锁的控制权,直到在其他线程中该对象锁调用notify()方法或notifyAll()方法时等待此对象锁的线程才会被唤醒。方案如下:
package playground;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Clock {
String currentTime;
private static final Object obj = new Object();
class ShowThread extends Thread{
TimeThread timethread;
public ShowThread(TimeThread timethread) {
this.timethread = timethread;
}
@Override
public void run() {
synchronized (obj) {
if (currentTime == null) {
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println("当前时间为:"+currentTime);
}
}
class TimeThread extends Thread{
@Override
public void run() {
String pattern = "yyyy-MM-dd HH:mm:ss";
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
currentTime = sdf.format(new Date());
synchronized (obj) {
obj.notify();
}
}
}
public static void main(String[] args) {
Clock clock = new Clock();
TimeThread timethread = clock.new TimeThread();
timethread.start();
ShowThread showThread = clock.new ShowThread(timethread);
showThread.start();
}
}
在这个解决方案中,如果还没有获得时间就执行显示时间的线程,那么显示时间的线程会进入等待的状态,直到再次被唤醒。在这个方案中应该注意调用wait,notify和synchronized的对象是同一对象。
经过解决这个问题,就可以比较容易的理解线程的join、wait及 notify方法了!