前言
讨论这个问题前,需要先了解以下知识点!
1、等待队列(blocked)中的线程不参与cpu竞争,就绪队列(runable)中的线程才会参与cpu竞争。
2、CPU竞争策略有多种,Unix使用的是时间片算法,Windows属于抢占式。
a)时间片:
所有的进程排成一个队列。操作系统按照他们的顺序,给每个进程分配一段时间,即允许该进程运行的时间。
b)抢占式操作系统
就是说如果一个进程得到了 CPU 时间,除非它自己放弃使用 CPU ,否则将完全霸占 CPU 。因此可以看出,在抢 占式操作系统中,操作系统假设所有的进程都是“人品很好”的,会主动退出 CPU 。
一、start()用来启动一个线程
调用该方法后,相应线程就会进入就绪状态,也就是runable状态,等待某个时机被CPU调用。
声明:wait() 、notify()、notifyAll()是Object类的方法,不是Thread类的方法。
二、yield()让线程让出CPU的执行(不释放锁)
该方法与sleep()类似,不能由用户指定暂停多长时间,它的作用是放弃当前CPU资源,让给其他线程去使用,但是放弃时间不确定。
它并不会让线程进入阻塞状态,而是让线程回到就绪状态,随时等待重新得到CPU的执行。
实际中无法保证yield()达到让步目的,因为让步的线程还有可能会再次被线程调度程序选中。
注意:
1.yield()并不会释放锁,仅仅是暂时交出了占用CPU执行权限。
2.yield()的线程有可能在进入到可执行状态后马上又被执行。
3.yield()方法只能使同优先级或者高优先级的线程得到执行机会,这也和sleep()方法不同。
通常配合Thread.activeCount()使用:
while(Thread.activeCount()>1) {
Thread.yield();//保障前面的线程都执行完
}
注:这里的1指的是主线程,如果你的main方法中new了一个线程类4个实例对象,分别启动这4各类的实例,那么就至少会有4个主线程(这里就需要用4去判断,所有子线程是否执行完毕)
使用场景示例
public class VolatileTest {
public volatile int num = 0;
public synchronized void increase() {
num++;
}
public static void main(String[] args) {
final VolatileTest test = new VolatileTest();
for (int i = 0; i < 10; i++) {
//开启10个子线程,对num同时操作
new Thread() {
public void run() {
for (int j = 0; j < 1000; j++) {
test.increase();
}
}
}.start();
}
while(Thread.activeCount()>1) {
Thread.yield();//保障前面的线程都执行完
}
System.out.println(test.num);
System.out.println(Thread.activeCount());
}
}
三、join()会释放Thread的锁,不会释放对象锁
简单的说:如果在main方法中,调用其他线程的join方法,则main方法的主线程A就需要给其他线程B让步,让B先执行完,主线程A才能继续执行,相当于阻塞主线程,直到被唤醒。
注:阻塞状态,它是让“外层的主线程”从 RUNNABLE 变为 WAITING 状态(因为join的底层就是wait())。
join(long millis)方法:执行当前A线程交出CPU执行权限millis后,再和其它线程一起竞争CPU资源。
如果在A主线程中,一次join了多个其他线程B/C/D,则被join的多个子线程执行的先后顺序不影响,都是并行执行。
注意:
1、如果有两个线程同时调用另外一个线程的join方法,会有一个线程成功得到锁,而另外一个则必须等待,进入阻塞状态,而在得到锁之后,才会执行join方法。
2、join底层使用wait,wait会释放锁,但是wait释放的是Thread的锁,所以Join在设计的时候也只是会释放Thread的锁,不会释放线程对象的锁,点击:进入代码示例。
如果看完,依然有疑问:那就单独列出来讨论一下。
join底层调用的是wait(),而wait是Object的方法,wait本身是会释放锁(彻底交出CPU的执行权),所以 Thread 的join() 方法是否会释放锁?答案是会!
点击进入:狂抓!join()方法到底会不会释放锁,给你彻底介绍清楚(带示例)
四、sleep()进入阻塞状态,让其他线程得到执行机会(不释放锁)
该方法需要指定等待的时间,它可以让当前正在执行的线程在指定的时间内暂停执行,进入阻塞状态,让其他线程得到执行机会。但是sleep()方法不会释放“锁标志”,其他线程仍然不能访问被锁定的共享数据。
举例:
1、拿 "点火->烧水->煮面"来说事儿,而当A点完火之后,并不想不立即烧水,要休息一段时间再烧,煮面的主动权是由A来控制(不释放锁)。
2、再拿看三个人看电视来说,A只是想休息一下,并不想交出遥控(锁),此时调用sleep(10),指定个休息时间;如果来了个管理员,需要让A交出遥控器,需要调用wait(10)方法,调用wait的同时,A立刻交出遥控,在wait的10毫秒期间,不参与遥控的竞争,10毫秒后几个人重新争夺遥控(锁)。
1.它需要捕获InterruptedException异常
如果调用了sleep方法,必须捕获InterruptedException异常或者将该异常向上层抛出,而wait,notify和notifyAll不需要捕获异常。
2.不会释放对象锁,其他线程无法访问这个对象
sleep方法不会释放该线程所拥有的资源(例如:锁),也就是说如果当前线程持有对某个对象的锁,其他线程也无法访问这个共享对象。
五、Object类的三个方法wait(),notify()和notifyAll()
wait 与 notify/notifyAll 方法必须在synchronized 同步代码块中使用,即要先对调用对象加锁,不放在synchronized 中则会在程序运行时抛出“java.lang.IllegalMonitorStateException”异常。
ReenTrantLock.newCondition()获取一个Condition类对象,然后Condition的await(),signal()以及signalAll()分别对应上面的三个方法。ReenTrantLock是Lock接口的实现类。
1.wait()指定时间内不参与锁的竞争(释放锁)
到时间会自动唤醒;否则就需要 notify / notifyAll 去唤醒。
它和sleep的重要区别是,sleep是Thread类的方法,wait是Object的方法。wait调用时,需写在synchronized代码块内。
拿看电视来举例,三个人看电视,锁就是遥控器。
A正在拿遥控器,一旦调用wait时,意味着A立刻交出遥控器(释放锁),至于交给B还是C,有JVM来控制。
那么,拿到遥控器的,可以每隔10秒,换一个电视台,这间隔的10秒就需要sleep实现,因为遥控仍在他手上,并没有交出遥控(sleep不释放锁)。
2.notify()和notifyAll()叫醒一个或所有线程参与锁的竞争
notify() 只会唤醒一个线程,notifyAll() 会唤醒所有的线程。
notifyAll() 调用后,会将全部线程由等待池(等待队列)移到锁池(就绪队列),然后参与锁的竞争,竞争成功则意味着获取到了CPU的执行权(不一定会立刻被CPU执行),如果不成功则留在锁池等待锁被释放后再次参与竞争。而 notify()只会唤醒一个线程,具体唤醒哪一个线程由虚拟机控制。
总结
这些方法相互之间总是容易混淆,重点记忆两点:
1、他们的父类不同:
wait(),notify()和notifyAll()三个方法属于Object类,只有用在synchronized代码同步块中才有意义。其他几个方法都属于Thread线程。
2、关于锁的获取是释放
wait会释放锁,yield、join()、park()、都不释放锁,需要注意的是join不释放对象锁,释放thread的锁(更多,点击进入)
尾言
学好多线程,这些都是绕不过去的点,只能深挖后一个一个突破,加油!
猜你还可能会对以下内容感兴趣:
1、 JAVA多线程:synchronized理论和用法 | Lock和ReentrantLock Volatile 区别和联系(一)