LockSupport 阻塞、唤醒线程
- LockSupport类的park,unpark方法用来阻塞和唤醒线程,类似于wait、notify,下面以代码实例举例
public class LockSupportDemo {
public static void main(String[] args) {
Thread t = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println(i);
if (i == 5) {
LockSupport.park();
}
// LockSupport.park内部维护了一个count
// if (i == 8) {
// LockSupport.park();
//}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
// 先唤醒2次(n次也一样),内部将count置为1,只会阻止一次park
LockSupport.unpark(t);
// LockSupport.unpark(t);
/*try {
TimeUnit.SECONDS.sleep(8);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("after 8 senconds!");
LockSupport.unpark(t);*/
}
}
①当调用park()方法时,会将_counter置为0,同时判断前值,小于1说明前面被unpark过,则直接退出,否则将使该线程阻塞。
② 当调用unpark()方法时,会将_counter置为1,同时判断前值,小于1会进行线程唤醒,否则直接退出。
形象的理解,线程阻塞需要消耗凭证(permit),这个凭证最多只有1个。当调用park方法时,如果有凭证,则会直接消耗掉这个凭证然后正常退出;但是如果没有凭证,就必须阻塞等待凭证可用;而unpark则相反,它会增加一个凭证,但凭证最多只能有1个。
③ 为什么可以先唤醒线程后阻塞线程?
因为unpark获得了一个凭证,之后调用park因为有凭证消费,故不会阻塞。
④ 为什么唤醒两次后阻塞两次会阻塞线程。
因为凭证的数量最多为1,连续调用两次unpark和调用一次unpark效果一样,只会增加一个凭证;而调用两次park却需要消费两个凭证。
线程间使用wait notify通信
案例:写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束
public class NotifyFreeLockDemo {
static List lists = new ArrayList();
public static void main(String[] args) {
Object lock = new Object();
new Thread(() -> {
System.out.println("t2 start...");
synchronized (lock) {
if (lists.size() != 5) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("t2 end...");
//执行结束 通知t1继续运行
lock.notify();
}
}, "t2").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//睡一秒钟 让t2线程先等待
new Thread(() -> {
System.out.println("t1 启动");
synchronized (lock) {
for (int i = 0; i < 10; i++) {
lists.add(new Object());
System.out.println("add " + lists.size());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (lists.size() == 5) {
// 唤醒对方
lock.notify();
try {
// 让出锁的执行权
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}, "t1").start();
}
}
执行结果如图:
Condition 等待队列
condition简单是用,我们以一个面试题来讲解,
面试题:写一个固定容量同步容器,拥有put和get方法,以及getCount方法,
能够支持2个生产者线程以及10个消费者线程的阻塞调用
实现代码
第一种以wait、notify实现
public class MyContainer1<T> {
private volatile LinkedList<T> linkedList = new LinkedList<>();
private int count = 0;
private final int MAX = 10;
/**
* 生产者put
*
* @param t
*/
public synchronized void put(T t) {
while (linkedList.size() == MAX) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
linkedList.add(t);
count++;
this.notifyAll();
}
/**
* 消费者get
*
* @return
*/
public synchronized T get() {
while (linkedList.size() == 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
T t = linkedList.removeFirst();
count--;
this.notifyAll();
return t;
}
public static void main(String[] args) {
MyContainer1<String> container1 = new MyContainer1<>();
//启动消费者
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 5; j++) {
System.out.println(container1.get());
}
}, "c" + i).start();
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//启动生产者线程
for (int i = 0; i < 2; i++) {
new Thread(() -> {
for (int j = 0; j < 25; j++) {
container1.put(Thread.currentThread().getName() + " " + j);
}
}, "p" + i).start();
}
}
}
第二种 以condition实现
public class MyContainer2<T> {
private volatile LinkedList<T> linkedList = new LinkedList<>();
private int count = 0;
private final int MAX = 10;
private Lock lock = new ReentrantLock();
private Condition producer = lock.newCondition();
private Condition consumer = lock.newCondition();
/**
* 生产者put
*
* @param t
*/
public void put(T t) {
try {
lock.lock();
while (linkedList.size() == MAX) {
producer.await();
}
linkedList.add(t);
count++;
consumer.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
/**
* 消费者get
*
* @return
*/
public T get() {
T t = null;
try {
lock.lock();
while (linkedList.size() == 0) {
consumer.await();
}
t = linkedList.removeFirst();
count--;
producer.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
return t;
}
public static void main(String[] args) {
MyContainer2<String> container2 = new MyContainer2<>();
//启动消费者
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 5; j++) {
System.out.println(container2.get());
}
}, "c" + i).start();
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//启动生产者线程
for (int i = 0; i < 2; i++) {
new Thread(() -> {
for (int j = 0; j < 25; j++) {
container2.put(Thread.currentThread().getName() + " " + j);
}
}, "p" + i).start();
}
}
}