面试题:
1、LockSupport为什么可以先唤醒线程后阻塞线程?
因为unpark()获得了一个凭证,之后再调用park()方法,就可以名正言顺的消费凭证,故不会阻塞。
2、LockSupport为什么唤醒两次后阻塞两次,但最终结果还会阻塞线程?
因为凭证的数量最多为1,连续调用两次unpark()和调用一次unpark效果一样,只会增加一个凭证,
而调用两次park()却需要消费两个凭证,证不够,不能放行。
目录
一、概述
1、wait() && notify()
异常1:wait() && notify(),两个方法都去掉同步代码块执行
异常2:将notify放在wait方法前执行,程序无法唤醒,无法执行。
2、Condition中的await()、signal()实现线程等待唤醒
异常1:await() && signal(),两个方法都去掉锁执行
异常2:将signal()放在await方法前执行,程序无法唤醒,无法执行。
3、LockSupport类中的park()阻塞和unpark()唤醒
3.1、park() && park(Object blocker):阻塞当前线程/阻塞传入的具体线程
3.2、unpark(Thread thread):唤醒处于阻塞状态的指定线程
3.3、park() && unpark(Thread thread)阻塞唤醒
3.4、先unpark(Thread thread)后park()方法:先唤醒后阻塞,依旧正常执行
3.5、LockSupport总结
一、概述
java.util.concurrent.locks下的类LockSupport:用于创建锁和其他同步类的基本线程阻塞原语。
public class LockSupportextends Object
park():阻塞线程
unpark():解除阻塞线程
LockSupport:是线程等待唤醒机制(wait/notify)的加强版
让线程等待和唤醒的方法:
方式1:使用Object中的wait()方法让线程等待,使用Object中的notify()方法唤醒线程
方式2:使用JUC包中Condition的await()方法让线程等待,使用signal()方法唤醒线程
方式3:LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程
1、wait() && notify()
public class LockSupportDemo {
static Object obj = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (obj) {
System.out.println(Thread.currentThread().getName() + "\t ---come in");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t ---被唤醒");
}
}, "A").start();
new Thread(() -> {
synchronized (obj) {
obj.notify();
System.out.println(Thread.currentThread().getName() + "\t ---唤醒");
}
}, "B").start();
}
}
正常结果:
A ---come in
B ---唤醒
A ---被唤醒
异常1:wait() && notify(),两个方法都去掉同步代码块执行
public class LockSupportDemo {
static Object obj = new Object();
public static void main(String[] args) {
new Thread(() -> {
// synchronized (obj) {
System.out.println(Thread.currentThread().getName() + "\t ---come in");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t ---被唤醒");
// }
}, "A").start();
new Thread(() -> {
// synchronized (obj) {
obj.notify();
System.out.println(Thread.currentThread().getName() + "\t ---唤醒");
// }
}, "B").start();
}
}
异常结果:
A ---come in
Exception in thread "A" Exception in thread "B" java.lang.IllegalMonitorStateException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at com.lwz.test.LockSupportDemo.lambda$0(LockSupportDemo.java:12)
at java.lang.Thread.run(Thread.java:748)
java.lang.IllegalMonitorStateException
at java.lang.Object.notify(Native Method)
at com.lwz.test.LockSupportDemo.lambda$1(LockSupportDemo.java:22)
at java.lang.Thread.run(Thread.java:748)
结论:Object类中的wait、notify、notifyAll用于线程等待唤醒的方法,都必须在synchronized内部执行(必须用到关键字synchronized)。
异常2:将notify放在wait方法前执行,程序无法唤醒,无法执行。
import java.util.concurrent.TimeUnit;
public class LockSupportDemo {
static Object obj = new Object();
public static void main(String[] args) {
new Thread(() -> {
// 暂停3秒线程A
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
synchronized (obj) {
System.out.println(Thread.currentThread().getName() + "\t ---come in");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t ---被唤醒");
}
}, "A").start();
new Thread(() -> {
synchronized (obj) {
obj.notify();
System.out.println(Thread.currentThread().getName() + "\t ---唤醒");
}
}, "B").start();
}
}
结果:
B ---唤醒
A ---come in
结论:先wait后notify、notifyAll方法,等待中的线程才会被唤醒,否则无法唤醒
wait和notify方法必须要在同步代码块或者方法里面且成对出现使用,先wait后notify才行。
2、Condition中的await()、signal()实现线程等待唤醒
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockSupportDemo {
static Lock lock = new ReentrantLock();
static Condition condition = lock.newCondition();
public static void main(String[] args) {
new Thread(() -> {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "\t --come in");
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t ---被唤醒");
} finally {
lock.unlock();
}
}, "A").start();
new Thread(() -> {
lock.lock();
try {
condition.signal();
System.out.println(Thread.currentThread().getName() + "\t --唤醒");
} finally {
lock.unlock();
}
}, "B").start();
}
}
正常结果:
A --come in
B --唤醒
A ---被唤醒
异常1:await() && signal(),两个方法都去掉锁执行
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockSupportDemo {
static Lock lock = new ReentrantLock();
static Condition condition = lock.newCondition();
public static void main(String[] args) {
new Thread(() -> {
// lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "\t --come in");
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t ---被唤醒");
} finally {
// lock.unlock();
}
}, "A").start();
new Thread(() -> {
// lock.lock();
try {
condition.signal();
System.out.println(Thread.currentThread().getName() + "\t --唤醒");
} finally {
// lock.unlock();
}
}, "B").start();
}
}
异常结果:
A --come in
Exception in thread "A" Exception in thread "B" java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.fullyRelease(AbstractQueuedSynchronizer.java:1723)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2036)
at com.lwz.test.LockSupportDemo.lambda$0(LockSupportDemo.java:18)
at java.lang.Thread.run(Thread.java:748)
java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.signal(AbstractQueuedSynchronizer.java:1939)
at com.lwz.test.LockSupportDemo.lambda$1(LockSupportDemo.java:32)
at java.lang.Thread.run(Thread.java:748)
异常2:将signal()放在await方法前执行,程序无法唤醒,无法执行。
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockSupportDemo {
static Lock lock = new ReentrantLock();
static Condition condition = lock.newCondition();
public static void main(String[] args) {
new Thread(() -> {
// 暂停3秒线程A
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "\t --come in");
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t ---被唤醒");
} finally {
lock.unlock();
}
}, "A").start();
new Thread(() -> {
lock.lock();
try {
condition.signal();
System.out.println(Thread.currentThread().getName() + "\t --唤醒");
} finally {
lock.unlock();
}
}, "B").start();
}
}
结果:
B --唤醒
A --come in
结论:
现象和wait、notify效果是一样的,线程先要获得并持有锁,必须在锁块(synchronized或lock)中
必须要先等待后唤醒,线程才能够被唤醒。
3、LockSupport类中的park()阻塞和unpark()唤醒
LockSupport类通过park()和unpark()方法来实现阻塞和唤醒线程的操作。
static void park():除非许可证可用,否则禁用当前线程以进行线程调度。
static void park(Object blocker):除非许可证可用,否则禁用当前线程以进行线程调度。
static void unpark(Thread thread):如果给定线程尚不可用,则为其提供许可。
public class LockSupport
extends Object
Basic thread blocking primitives for creating locks and other synchronization classes.
This class associates, with each thread that uses it, a permit (in the sense of the
Semaphore class). A call to park will return immediately if the permit is available,
consuming it in the process; otherwise it may block. A call to unpark makes the permit
available, if it was not already available. (Unlike with Semaphores though, permits do
not accumulate. There is at most one.)
LockSupport:用于创建锁和其他同步类的基本线程阻塞原语。
LockSupport类使用了一种名为Permit(许可)的概念来做阻塞和唤醒功能,每个线程都有一个许可(Permit),
permit只有两个值1和零,默认是零。
可以把许可看成是一种(0,1)信号量(Semaphore),但与Semaphore不同的是,许可的累加上限是1.
3.1、park() && park(Object blocker):阻塞当前线程/阻塞传入的具体线程
调用LockSupport.park();
public static void park() {
UNSAFE.park(false, 0L);
}
permit默认是0,所以一开始调用park()方法,当前线程就会阻塞,直到别的线程将当前线程的
permit设置为1时,park()方法会被唤醒,然后会将permit再次设置为0并返回。
3.2、unpark(Thread thread):唤醒处于阻塞状态的指定线程
调用LockSupport.unpark(thread);
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
调用unpark(thread)方法后,就会将thread线程的许可permit设置成1(注意多次调用unpark方法,不
会累加,permit值还是1)会自动唤醒thread线程,即之前阻塞中的LockSupport.park();方法会立即返回。
3.3、park() && unpark(Thread thread)阻塞唤醒
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
public class LockSupportDemo {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t --come in");
LockSupport.park();// 被阻塞..等待许可证
System.out.println(Thread.currentThread().getName() + "\t ---被唤醒");
}, "t1");
t1.start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread t2 = new Thread(() -> {
LockSupport.unpark(t1);
System.out.println(Thread.currentThread().getName() + "\t --唤醒");
}, "t2");
t2.start();
}
}
结果:
t1 --come in
t2 --唤醒
t1 ---被唤醒
3.4、先unpark(Thread thread)后park()方法:先唤醒后阻塞,依旧正常执行
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
public class LockSupportDemo {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t --come in");
LockSupport.park();// 被阻塞..等待许可证
System.out.println(Thread.currentThread().getName() + "\t ---被唤醒");
}, "t1");
t1.start();
Thread t2 = new Thread(() -> {
LockSupport.unpark(t1);
System.out.println(Thread.currentThread().getName() + "\t --唤醒");
}, "t2");
t2.start();
}
}
结果:
t2 --唤醒
t1 --come in
t1 ---被唤醒
3.5、LockSupport总结
LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
LockSupport是一个线程阻塞工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,阻塞之后也有对应的唤醒方法。归根结底,LockSupport调用的Unsafe中的native代码。
LockSupport提供park()和unpark()方法实现阻塞线程和接触线程阻塞的过程
LockSupport和每个使用它的线程都有一个许可证(permit)关联。permit相当于1,0的开关,默认是0,调用依次unpark()就加1变成1,调用一次park()会消费permit,也就是将1变为0,同时park()立即返回。如再次调用park()会变成阻塞(因为permit为零了会阻塞在这里,一直到permit变为1),这时调用unpark()会把permit置为1.
每个线程都有一个相关的permit,permit最多只有一个,重复调用unpark()也不会积累凭证。
形象的理解
线程阻塞需要消耗凭证(permit),这个凭证最多只有1个。
当调用park()方法时:
如果有凭证,则会直接消耗掉这个凭证然后正常退出。
如果无凭证,就必须阻塞等待凭证可用。
而unpark()则相反,它会增加一个凭证,但凭证最多只能有1个,累加无效。
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
public class LockSupportDemo {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t --come in");
LockSupport.park();// 被阻塞..等待许可证
LockSupport.park();// 被阻塞..等待许可证
System.out.println(Thread.currentThread().getName() + "\t ---被唤醒");
}, "t1");
t1.start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread t2 = new Thread(() -> {
LockSupport.unpark(t1);
LockSupport.unpark(t1);
System.out.println(Thread.currentThread().getName() + "\t --唤醒");
}, "t2");
t2.start();
}
}
结果:
t1 --come in
t2 --唤醒
结论:说明了unpark()只能发放一张许可证对应一个park()方法通行。
Java volatile学习
干我们这行,啥时候懈怠,就意味着长进的停止,长进的停止就意味着被淘汰,只能往前冲,直到凤凰涅槃的一天!