在前面我们了解了很多关于同步(互斥锁)的问题,下面来看一下线程之间的协作。这里主要说一下Java线程中的join()、sleep()、yield()、wait()、notify()和notifyAll()方法。其中wait()、notify()和notifyAll()是线程间的协作的主要方法。
join :让一个线程等待另一个线程完成才继续执行。如A线程线程执行体中调用B线程的join()方法,则A线程被阻塞,一直等到 B线程执行完为止,A才能得以继续执行。类似于生活中排队的时候,有人插队,一直等到前面插队的人的事情处理完才可以。
public class UseJoin {
static class JumpQueue implements Runnable {
private Thread thread;
public JumpQueue(Thread thread) {
this.thread = thread;
}
public void run() {
try {
thread.join();//调用传入线程的join方法,必须等这个方法返回后,当前线程才能继续执行
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName() + " terminate.");
}
}
public static void main(String[] args) throws Exception {
Thread previous = Thread.currentThread();//现在previous是主线程
for (int i = 0; i < 10; i++) {
// 每个线程拥有前一个线程的引用,需要等待前一个线程终止,才能从等待中返回
Thread thread =
new Thread(new JumpQueue(previous), String.valueOf(i));
System.out.println(previous.getName()+" jump a queue the thread:"
+thread.getName());
thread.start();
previous = thread;
}
SleepTools.second(2);//让主线程休眠2秒
System.out.println(Thread.currentThread().getName() + " terminate.");
}
}
可以看出,程序中定义了10个线程,将主线程作为参数传递给第一个线程,在run方法中调用了主线程的join方法,然后将第一个参数作为参数传递给第二个线程 ,在run方法中调用了第一个线程的join方法。。。所以,第一个线程要等到主线程执行完才能执行,第二个线程要等到第一个线程执行完才能执行。。。
main jump a queue the thread:0
0 jump a queue the thread:1
1 jump a queue the thread:2
2 jump a queue the thread:3
3 jump a queue the thread:4
4 jump a queue the thread:5
5 jump a queue the thread:6
6 jump a queue the thread:7
7 jump a queue the thread:8
8 jump a queue the thread:9
main terminate.
0 terminate.
1 terminate.
2 terminate.
3 terminate.
4 terminate.
5 terminate.
6 terminate.
7 terminate.
8 terminate.
9 terminate.
sleep:让当前的正在执行的线程暂停指定的时间,并进入阻塞状态。在其睡眠的时间段内,该线程由于不是处于就绪状态,因此不会得到执行的机会。即使此时系统中没有任何其他可执行的线程,出于sleep()中的线程也不会执行。因此sleep()方法常用来暂停线程执行。
前面有讲到,当调用了新建的线程的start()方法后,线程进入到就绪状态,可能会在接下来的某个时间获取CPU时间片得以执行,如果希望这个新线程必然性的立即执行,直接调用原来线程的sleep(1)即可。
ublic class ThreadTest {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == 30) {
thread.start();
try {
Thread.sleep(1); // 使得thread必然能够马上得以执行
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
Java线程中的Thread.yield( )方法,即线程让步。顾名思义,就是说当一个线程使用了这个方法之后,它就会把自己CPU执行的时间让掉,让自己或者其它的线程运行,注意是让自己或者其他线程运行,并不是单纯的让给其他线程。
yield()的作用是让步。它能让当前线程由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获取执行权;但是,并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权;也有可能是当前线程又进入到“运行状态”继续运行!
举个例子:
一帮朋友在排队上公交车,轮到Yield的时候,他突然说:我不想先上去了,咱们大家来竞赛上公交车。然后所有人就一块冲向公交车,有可能是其他人先上车了,也有可能是Yield先上车了。 但是线程是有优先级的,优先级越高的人,就一定能第一个上车吗?这是不一定的,优先级高的人仅仅只是第一个上车的概率大了一点而已,最终第一个上车的,也有可能是优先级最低的人。并且所谓的优先级执行,是在大量执行次数中才能体现出来的。
wait():导致当前线程等待并使其进入到等待阻塞状态。直到其他线程调用该同步锁对象的notify()或notifyAll()方法来唤醒此线程。
notify():唤醒在此同步锁对象上等待的单个线程,如果有多个线程都在此同步锁对象上等待,则会任意选择其中某个线程进行唤醒操作,只有当前线程放弃对同步锁对象的锁定,才可能执行被唤醒的线程。
notifyAll():唤醒在此同步锁对象上等待的所有线程,只有当前线程放弃对同步锁对象的锁定,才可能执行被唤醒的线程。
下面看一个例子:
在快递邮寄过程中,公里数大于100的时候更新城市,当城市为ShangHai的时候显示到站。在main方法中启动了三个检查里程数变化的线程,三个检查地点变化的线程。
以下是示例代码:
public class Express {
public final static String CITY = "ShangHai";
private int km;/*快递运输里程数*/
private String site;/*快递到达地点*/
public Express() {
}
public Express(int km, String site) {
this.km = km;
this.site = site;
}
/* 变化公里数,然后通知处于wait状态并需要处理公里数的线程进行业务处理*/
public synchronized void changeKm(){
this.km = 101;
notifyAll();
}
/* 变化地点,然后通知处于wait状态并需要处理地点的线程进行业务处理*/
public synchronized void changeSite(){
this.site = "BeiJing";
notifyAll();
}
public synchronized void waitKm(){
while(this.km<=100){//公里数小于100不做处理
try {
wait();
System.out.println("Check Km thread["+Thread.currentThread().getId()
+"] is be notified");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("the Km is "+this.km+",I will change db");
}
public synchronized void waitSite(){
while(this.site.equals(CITY)){//快递到达目的地
try {
wait();
System.out.println("Check Site thread["+Thread.currentThread().getId()
+"] is be notified");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("the site is "+this.site+",I will call user");
}
}
public class TestWN {
private static Express express = new Express(0,Express.CITY);
/*检查里程数变化的线程,不满足条件,线程一直等待*/
private static class CheckKm extends Thread{
@Override
public void run() {
express.waitKm();
}
}
/*检查地点变化的线程,不满足条件,线程一直等待*/
private static class CheckSite extends Thread{
@Override
public void run() {
express.waitSite();
}
}
public static void main(String[] args) throws InterruptedException {
for(int i=0;i<3;i++){
new CheckSite().start();
}
for(int i=0;i<3;i++){
new CheckKm().start();
}
Thread.sleep(1000);
express.changeKm();//快递里程数变化
express.changeSite();//快递地点变化
}
}
Check Km thread[17] is be notified
the Km is 101,I will change db
Check Km thread[16] is be notified
the Km is 101,I will change db
Check Km thread[15] is be notified
the Km is 101,I will change db
Check Site thread[14] is be notified
the site is BeiJing,I will call user
Check Site thread[13] is be notified
the site is BeiJing,I will call user
Check Site thread[12] is be notified
the site is BeiJing,I will call user
1.、wait()方法执行后,当前线程立即进入到等待阻塞状态,其后面的代码不会执行。
2、notify()/notifyAll()方法执行后,将唤醒此同步锁对象上的线程对象,但是,此时还并没有释放同步锁对象,也就是说,如果notify()/notifyAll()后面还有代码,还会继续进行,知道当前线程执行完毕才会释放同步锁对象。
3、wait()/notify()/nitifyAll()完成线程间的通信或协作都是基于不同对象锁的,因此,如果是不同的同步对象锁将失去意义。同时,同步对象锁最好是与共享资源对象保持一一对应关系。
4、当wait线程唤醒后并执行时,是接着上次执行到的wait()方法代码后面继续往下执行的。
5、wait()、notify()和notifyAll()方法是本地方法,并且为final方法,无法被重写。
6、调用某个对象的wait()方法能让当前线程阻塞,并且当前线程必须拥有此对象的锁。
7、调用某个对象的notify()方法能够唤醒一个正在等待这个对象的锁的线程,如果有多个线程都在等待这个对象的锁,则只能唤醒其中一个线程。
8、调用notifyAll()方法能够唤醒所有正在等待这个对象的锁的线程。
9、wait()、notify()和notifyAll()这三个不是Thread类声明中的方法,而是Object类中声明的方法,当然由于Thread类继承了Object类,所以Thread也可以调用者三个方法。WHY?由于每个对象都拥有锁,所以让当前线程等待某个对象的锁,当然应该通过这个对象来操作了。而不是用当前线程来操作,因为当前线程可能会等待多个线程的锁,如果通过线程来操作,就非常复杂了。
10、调用某个对象的wait()方法,当前线程必须拥有这个对象的锁,因此调用wait()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)。
11、调用某个对象的wait()方法,相当于让当前线程交出此对象的锁,然后进入等待状态,等待后续再次获得此对象的锁(Thread类中的sleep方法使当前线程暂停执行一段时间,从而让其他线程有机会继续执行,但它并不释放对象锁);
12、调用某个对象的notify()方法,当前线程也必须拥有这个对象的锁,因此调用notify()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)。
13、notify()和notifyAll()方法只是唤醒等待该对象的锁的线程,并不决定哪个线程能够获取到锁。
14、一个线程被唤醒不代表立即获取了对象的锁,只有等调用完notify()或者notifyAll()并退出synchronized块,释放对象锁后,其余线程才可获得锁执行。
yield() :不会释放锁的。
sleep():不会释放锁的。
wait():调用前持有锁,调用了wait方法以后,锁会被释放。
notify() :调用前持有锁,包含了notify()的方法结束以后,锁才会被释放。