本文主要总结Thread的几个常用方法,并相互做对比。
Thread类在线程的生命周期中,常见的方法有:
- wait, notify, notifyall
- sleep, interrupt
- yield, join
一、wait,notify,notifyall的使用
这三个方法都是被定义在Object类中,它们主要应用于线程间交换对象锁的状态,先从一个例子入手吧,看完例子再解释其原理,这是一个生产者-消费者案例。
以下是生产者的代码:
class Producer implements Runnable
{
private final List<Integer> taskQueue;
private final int MAX_CAPACITY;
public Producer(List<Integer> sharedQueue, int size)
{
this.taskQueue = sharedQueue;
this.MAX_CAPACITY = size;
}
@Override
public void run()
{
int counter = 0;
while (true)
{
try
{
produce(counter++);
}
catch (InterruptedException ex)
{
ex.printStackTrace();
}
}
}
private void produce(int i) throws InterruptedException
{
synchronized (taskQueue)
{
while (taskQueue.size() == MAX_CAPACITY)
{
System.out.println("Queue is full " + Thread.currentThread().getName() + " is waiting , size: " + taskQueue.size());
taskQueue.wait();
}
Thread.sleep(1000);
taskQueue.add(i);
System.out.println("Produced: " + i);
taskQueue.notifyAll();
}
}
}
taskQueue是存放商品的媒介,对于生产者和消费者而言是互斥访问的,所以自然是用synchronized括起来了。另外,当taskQueue已经存满,则生产者需要进入等待状态,此时调用了taskQueue.wait(),当前线程放弃了对象锁taskQueue,使得其他线程——消费者可以获得该对象锁。后面我们会看到,当另一个线程(消费者)调用taskQueue.notify()后,停留在taskQueue.wait()的生产者线程会恢复运行。生产者生产了商品后,调用了taskQueue.notifyAll(),这个方法可以唤醒所有正在等待商品的消费者中线程优先级最高的一个。
相信解释了生产者的实现代码,以下消费者的实现代码就很容易理解了。
再来看一下消费者的代码
class Consumer implements Runnable
{
private final List<Integer> taskQueue;
public Consumer(List<Integer> sharedQueue)
{
this.taskQueue = sharedQueue;
}
@Override
public void run()
{
while (true)
{
try
{
consume();
} catch (InterruptedException ex)
{
ex.printStackTrace();
}
}
}
private void consume() throws InterruptedException
{
synchronized (taskQueue)
{
while (taskQueue.isEmpty())
{
System.out.println("Queue is empty " + Thread.currentThread().getName() + " is waiting , size: " + taskQueue.size());
taskQueue.wait();
}
Thread.sleep(1000);
int i = (Integer) taskQueue.remove(0);
System.out.println("Consumed: " + i);
taskQueue.notifyAll();
}
}
}
我们再来写一个测试例子
public class ProducerConsumerExampleWithWaitAndNotify
{
public static void main(String[] args)
{
List<Integer> taskQueue = new ArrayList<Integer>();
int MAX_CAPACITY = 5;
Thread tProducer = new Thread(new Producer(taskQueue, MAX_CAPACITY), "Producer");
Thread tConsumer = new Thread(new Consumer(taskQueue), "Consumer");
tProducer.start();
tConsumer.start();
}
}
Output:
Produced: 0
Consumed: 0
Queue is empty Consumer is waiting , size: 0
Produced: 1
Produced: 2
Consumed: 1
Consumed: 2
Queue is empty Consumer is waiting , size: 0
Produced: 3
Produced: 4
Consumed: 3
Produced: 5
Consumed: 4
Produced: 6
Consumed: 5
Consumed: 6
Queue is empty Consumer is waiting , size: 0
Produced: 7
Consumed: 7
Queue is empty Consumer is waiting , size: 0
从上面的例子可以看出,生产者和消费者两个异步线程可以借助wait和notify很好地进行通信。
接下来再稍微做个总结,我们一般按如下代码调用wait方法
synchronized( lockObject )
{
while( ! condition )
{
lockObject.wait();
}
//take the action here;
}
如上将wait方法放到while循环中,其中的好处有很多说法,不过有一种我比较赞同,就是当其他恶意线程想notify这个线程时会失败,因为如果该恶意线程没有翻转此处condition的状态的话。
另外,我们一般按照如下代码调用notify方法或者notifyall方法
synchronized(lockObject)
{
//establish_the_condition;
lockObject.notify();
//lockObject.notifyAll();
//any additional code if needed
}
二、sleep,interrupt的使用
sleep方法我们再熟悉不过,平时见到的很多Java例子都喜欢借助这个方法,
该方法的签名如下:
public static void sleep(long time) throws InterruptedException {
调用方式大致如下:
try {
Thread.sleep(2000);
} catch (InterruptedException x) {
//interrupttd by other thread
}
sleep方法属于Thread的静态方法,interrupt属于线程对象的方法,不是静态方法。当在其他线程中调用了上 面例子中的线程的interrupt,则上述代码会进入InterruptedException异常捕获中。到这里,发现没有,这就起到了线程交互的目的了,你只要把你将要做的事情放在异常处理的代码里就行了。
另外interrupt除了会使sleep抛出InterruptedException异常,也会使wait抛出InterruptedException异常。
sleep和wait方法虽然很相似,但是也有很多区别,如下:
调用
sleep: 通过当前线程调用
wait: 通过Object对象调用
锁
sleep: 不会释放对象锁
wait: 释放对象锁
唤醒
sleep: 当sleep的时间过去后才唤醒,或者通过*interrupt *方法唤醒
wait: 有人调用了当前对象的notify或者notifyAll.
三、yield,join的使用
yield方法是Thread的静态方法,方法声明如下
public static native void yield();
yield方法可以使当前线程让出执行机会,让其他相同优先级或者更高优先级的线程有机会执行。执行完yield方法后的线程将从Running State状态进入Runnable State状态,而非blocked state。与之形成对比的是,sleep方法会使线程进入blocked state状态,而且sleep方法是不仅为所有优先级的线程提供运行机会,包括更低优先级的线程。
请参考下面一个小例子加深对yield方法的理解
public class YieldExample
{
public static void main(String[] args)
{
Thread producer = new Producer();
Thread consumer = new Consumer();
producer.setPriority(Thread.MIN_PRIORITY); //Min Priority
consumer.setPriority(Thread.MAX_PRIORITY); //Max Priority
producer.start();
consumer.start();
}
}
class Producer extends Thread
{
public void run()
{
for (int i = 0; i < 5; i++)
{
System.out.println("I am Producer : Produced Item " + i);
Thread.yield();
}
}
}
class Consumer extends Thread
{
public void run()
{
for (int i = 0; i < 5; i++)
{
System.out.println("I am Consumer : Consumed Item " + i);
Thread.yield();
}
}
}
注释掉以上两个yield方法,输出如下
I am Consumer : Consumed Item 0
I am Consumer : Consumed Item 1
I am Consumer : Consumed Item 2
I am Consumer : Consumed Item 3
I am Consumer : Consumed Item 4
I am Producer : Produced Item 0
I am Producer : Produced Item 1
I am Producer : Produced Item 2
I am Producer : Produced Item 3
I am Producer : Produced Item 4
执行以上两个yield方法,输出如下
I am Producer : Produced Item 0
I am Consumer : Consumed Item 0
I am Producer : Produced Item 1
I am Consumer : Consumed Item 1
I am Producer : Produced Item 2
I am Consumer : Consumed Item 2
I am Producer : Produced Item 3
I am Consumer : Consumed Item 3
I am Producer : Produced Item 4
I am Consumer : Consumed Item 4
接下来再讲讲join方法,该方法的签名如下:
//Waits for this thread to die.
public final void join() throws InterruptedException
如果在B线程中执行A线程的join方法,则B线程会暂停,等待A线程执行完毕。
看如下例子
public class JoinExample
{
public static void main(String[] args) throws InterruptedException
{
Thread t = new Thread(new Runnable()
{
public void run()
{
System.out.println("First task started");
System.out.println("Sleeping for 2 seconds");
try
{
Thread.sleep(2000);
} catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println("First task completed");
}
});
Thread t1 = new Thread(new Runnable()
{
public void run()
{
System.out.println("Second task completed");
}
});
t.start(); // Line 15
t.join(); // Line 16
t1.start();
}
}
Output:
First task started
Sleeping for 2 seconds
First task completed
Second task completed
join和sleep一样,会因为interrupt抛出InterruptedException,而yield不会。
四、参考链接
近来发现一个不错的网站,总结了很多Java的基础知识:
How to work with wait notify and notifyall