一、java线程的六种状态
其中,RUNNABLE状态包括 【运行中】 和 【就绪】;
BLOCKED(阻塞态)状态只有在【等待进入synchronized方法(块)】和 【其他Thread调用notify()或notifyAll(),但是还未获得锁】才会进入;
二、sleep() 、yield()、join()与 wait()/notify()的区别
sleep() 、yield()、join()是Thread的方法,只放弃cpu,但是不放弃锁
1、Thread.sleep(long millis),一定是当前线程调用此方法,当前线程进入TIMED_WAITING状态,但不释放对象锁,millis后线程自动苏醒进入就绪状态。作用:给其它线程执行机会的最佳方式。
2、Thread.yield(),一定是当前线程调用此方法,当前线程放弃获取的CPU时间片,但不释放锁资源,由运行状态变为就绪状态,让OS再次选择线程。作用:让相同优先级的线程轮流执行,但并不保证一定会轮流执行。实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield()不会导致阻塞。该方法与sleep()类似,只是不能由用户指定暂停多长时间。
3、t.join()/t.join(long millis),当前线程里调用其它线程t的join方法,当前线程进入WAITING/TIMED_WAITING状态,当前线程不会释放已经持有的对象锁。线程t执行完毕或者millis时间到,当前线程进入就绪状态。
wait()是Object的方法,放弃cpu,也放弃锁
4、obj.wait(),当前线程调用对象的wait()方法,当前线程释放对象锁,进入等待队列。依靠notify()/notifyAll()唤醒或者wait(long timeout) timeout时间到自动唤醒。
5、obj.notify()唤醒在此对象监视器上等待的单个线程,选择是任意性的。notifyAll()唤醒在此对象监视器上等待的所有线程。
public class Test {
public static void main(String[] args) {
new Thread1().start();
new Thread2().start();
}
public static class Thread1 extends Thread{
@Override
public void run() {
synchronized (Test.class){
System.out.println("Thread1 start");
try {
/**
* 1、wait()和notify()是Object锁的方法
* 2、wait()会让出锁
*/
Test.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread1 go on ");
}
}
}
public static class Thread2 extends Thread{
@Override
public void run() {
synchronized (Test.class){
System.out.println("Thread2 start");
/**
* 1、notify()调用后,该线程会等待该同步块执行完毕才释放锁
*/
Test.class.notifyAll();
try {
/**
* 1、sleep()是Thread的方法
* 2、sleep()不让出锁,只让出cpu
*/
Thread2.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread2 go on");
}
}
}
}
三、LockSupport中的park() 和 unpark()
总结一下,LockSupport比Object的wait/notify有两大优势:
①LockSupport不需要在同步代码块里 ,所以线程间也不需要维护一个共享的同步对象了,实现了线程间的解耦;
而wait/notify必须在同步块或同步方法中才能调用。
②unpark函数可以先于park调用,所以不需要担心线程间的执行的先后顺序,而wait必须先于notify。
1、为什么LockSupport不需要在同步代码块里而wait()需要?
线程A执行一段业务逻辑后调用wait阻塞住自己。主线程调用notify方法唤醒线程A,线程A然后打印自己执行的结果:
public class TestObjWait {
public static void main(String[] args)throws Exception {
final Object obj = new Object();
Thread A = new Thread(new Runnable() {
@Override
public void run() {
int sum = 0;
for(int i=0;i<10;i++){
sum+=i;
}
try {
synchronized (obj){
obj.wait();
}
}catch (Exception e){
e.printStackTrace();
}
System.out.println(sum);
}
});
A.start();
//睡眠一秒钟,保证线程A已经计算完成,阻塞在wait方法
Thread.sleep(1000);
synchronized (obj){
obj.notify();
}
}
}
使用LockSupport实现:
public class TestObjWait {
public static void main(String[] args)throws Exception {
Thread A = new Thread(new Runnable() {
@Override
public void run() {
int sum = 0;
for(int i=0;i<10;i++){
sum+=i;
}
LockSupport.park();
System.out.println(sum);
}
});
A.start();
//睡眠一秒钟,保证线程A已经计算完成
Thread.sleep(1000);
LockSupport.unpark(A);
}
}
2、为什么LockSupport不需要担心unpark函数和park调用顺序,而Object的wait/notify需要关心?
如果我们将上面代码的这一句去掉:
//睡眠一秒钟,保证线程A已经计算完成
Thread.sleep(1000);
那么,使用wait()和notify()的就会出题,可能A会永远被挂起,因为主线程的notify()先于wait()调用了;
但是LockSupport的代码还是正确的执行,因为
LockSupport和每个使用它的线程都与一个许可(permit)关联。permit相当于1,0的开关,默认是0;
调用unpark就将permit赋值1;
调用park时,会判断permit如果为1,就会将permit赋值0,并且立即返回,如果permit为0,会阻塞在这里,直到permit变为1