LockSupport
LockSupport
是一个线程阻塞工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,当然阻塞之后肯定得有唤醒的方法。
常用方法:
public static void park(Object blocker); // 暂停当前线程
public static void parkNanos(Object blocker, long nanos); // 暂停当前线程,不过有超时时间的限制
public static void parkUntil(Object blocker, long deadline); // 暂停当前线程,直到某个时间
public static void park(); // 无期限暂停当前线程
public static void parkNanos(long nanos); // 暂停当前线程,不过有超时时间的限制
public static void parkUntil(long deadline); // 暂停当前线程,直到某个时间
public static void unpark(Thread thread); // 恢复当前线程
public static Object getBlocker(Thread t);
-
park
不需要获取某个对象的锁 -
park和unpark
可以实现类似wait和notify
的功能,但是并不和wait和notify
交叉,也就是说unpark
不会对wait
起作用,notify
也不会对park
起作用。 -
park和unpark
的使用不会出现死锁的情况 -
相对于线程的
stop和resume
,park和unpark
的先后顺序并不是那么严格。stop和resume
如果顺序反了,会出现死锁现象。而park和unpark
却不会。park和unpark
会对每个线程维持一个许可(boolean值)。- unpark调用时,如果当前线程还未进入park,则许可为true
- park调用时,判断许可是否为true,如果是true,则继续往下执行;如果是false,则等待,直到许可为true
-
blocker的作用是在dump线程的时候看到阻塞对象的信息
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
public class TestLockSupport {
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(); // 无限期暂停当前线程,直到unpark执行
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
LockSupport.unpark(t);
}
}
面试题
淘宝曾经的面试题1:
实现一个容器,提供两个方法,add、size,写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束
- 使用wait、notify
import java.util.ArrayList;
import java.util.List;
/**
* @author wardseptember
* @create 2020-07-05 20:30
*/
public class WaitAndNotify {
volatile List lists = new ArrayList();
public void add(Object o) {
lists.add(o);
}
public int size() {
return lists.size();
}
static Object lock = new Object();
public static void main(String[] args) {
WaitAndNotify waitAndNotify = new WaitAndNotify();
Thread t2 = new Thread(() -> {
synchronized (lock) {
System.out.println("t2 启动");
if (waitAndNotify.size() != 5) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("t2 结束");
// 通知t1继续执行
lock.notify();
}
});
Thread t1 = new Thread(() -> {
synchronized (lock) {
System.out.println("t1 启动");
for (int i = 0; i < 10; i++) {
waitAndNotify.add(i);
System.out.println("add " + i);
if (waitAndNotify.size() == 5) {
lock.notify();
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
});
t2.start();
t1.start();
}
}
- 使用CountDownLatch
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
/**
* @author wardseptember
* @create 2020-07-05 20:30
*/
public class CountDownLatch_Taobao {
volatile List lists = new ArrayList();
public void add(Object o) {
lists.add(o);
}
public int size() {
return lists.size();
}
public static void main(String[] args) {
CountDownLatch countDownLatch1 = new CountDownLatch(1);
CountDownLatch countDownLatch2 = new CountDownLatch(1);
CountDownLatch_Taobao latchTaobao = new CountDownLatch_Taobao();
Thread t2 = new Thread(() -> {
System.out.println("t2 启动");
if (latchTaobao.size() != 5) {
try {
// t2 暂停
countDownLatch1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("t2 结束");
// 放行t1
countDownLatch2.countDown();
});
Thread t1 = new Thread(() -> {
System.out.println("t1 启动");
for (int i = 0; i < 10; i++) {
latchTaobao.add(i);
System.out.println("add " + i);
if (latchTaobao.size() == 5) {
// 放行t2
countDownLatch1.countDown();
try {
// t1暂停
countDownLatch2.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t2.start();
t1.start();
}
}
- 使用LockSupport
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.LockSupport;
/**
* @author wardseptember
* @create 2020-07-05 20:30
*/
public class LockSupport_Taobao {
volatile List lists = new ArrayList();
public void add(Object o) {
lists.add(o);
}
public int size() {
return lists.size();
}
static Thread t1 = null, t2 = null;
public static void main(String[] args) {
LockSupport_Taobao latchTaobao = new LockSupport_Taobao();
t2 = new Thread(() -> {
System.out.println("t2 启动");
if (latchTaobao.size() != 5) {
LockSupport.park(t2);
}
System.out.println("t2 结束");
LockSupport.unpark(t1);
});
t1 = new Thread(() -> {
System.out.println("t1 启动");
for (int i = 0; i < 10; i++) {
latchTaobao.add(i);
System.out.println("add " + i);
if (latchTaobao.size() == 5) {
LockSupport.unpark(t2);
LockSupport.park(t1);
}
}
});
t2.start();
t1.start();
}
}
淘宝曾经的面试题2:
写一个固定容量同步容器,拥有put和get方法,以及getCount方法,能够支持2个生产者线程以及10个消费者线程的阻塞调用。
- 使用wait和notify/notifyAll来实现
import java.util.LinkedList;
import java.util.concurrent.TimeUnit;
/**
* @author wardseptember
* @create 2020-07-05 21:36
*/
public class MyContainer1<T> {
final private LinkedList<T> list = new LinkedList<>();
final private int MAX = 10;
public synchronized void put(T t) {
while (list.size() == MAX) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.add(t);
this.notifyAll();
}
public synchronized T get() {
T t = null;
while (list.size() == 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
t = list.removeFirst();
this.notifyAll();
return t;
}
public static void main(String[] args) {
MyContainer1<String> c = new MyContainer1<>();
// 启动消费者线程
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for(int j = 0; j < 5; j++) {
System.out.println(c.get());
}
}, "c" + i).start();
}
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 启动生产者线程
for (int i = 0; i < 2; i++) {
new Thread(() -> {
for(int j = 0; j < 25; j++) {
c.put(Thread.currentThread().getName() + " " + j);
}
}, "p" + i).start();
}
}
}
- 使用ReentrantLock
import java.util.LinkedList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author wardseptember
* @create 2020-07-05 21:36
*/
public class MyContainer2<T> {
final private LinkedList<T> list = new LinkedList<>();
final private int MAX = 10;
private Lock lock = new ReentrantLock();
private Condition producer = lock.newCondition();
private Condition consumer = lock.newCondition();
public void put(T t) {
try {
lock.lock();
while (list.size() == MAX) {
producer.await();
}
list.add(t);
consumer.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public T get() {
T t = null;
try {
lock.lock();
while (list.size() == 0) {
consumer.await();
}
t = list.removeFirst();
producer.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
return t;
}
public static void main(String[] args) {
MyContainer2<String> c = new MyContainer2<>();
// 启动消费者线程
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for(int j = 0; j < 5; j++) {
System.out.println(c.get());
}
}, "c" + i).start();
}
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 启动生产者线程
for (int i = 0; i < 2; i++) {
new Thread(() -> {
for(int j = 0; j < 25; j++) {
c.put(Thread.currentThread().getName() + " " + j);
}
}, "p" + i).start();
}
}
}
Synchronized和Lock的区别,使用Lock的好处
原始构成
-
synchronized是关键字属于JVM层面
monitorenter,底层是通过monitor对象来完成,其实wait/notify等方法也依赖于monitor对象,只有在同步块或者方法中才能调用wait/notify等方法
monitorexit
-
lock是具体类(java.util.concurrent.locks.lock)是api层面的锁
使用方法
synchronized不需要用户去手动释放锁,当synchronized代码执行完后系统会自动让线程释放对锁的占用。
reentrantlock需要用户去手动释放锁,若没有主动释放锁,就有可能导致出现死锁的现象。
等待是否中断
synchronized不可中断,除非抛出异常或者正常运行完成
Reentranlock 可中断:
- 设置超时方法 trylock(long timeout, TimeUnit unit)
- lockInterruptibly放代码块中,调用interrupt()方法可中断
加锁是否公平
synchronized是非公平锁
reentrantlock可以非公平锁也可以是公平锁,默认为非公平锁。
锁是否可以绑定多个Condition
synchronized不可以
Reentrantlock用来实现分组唤醒需要唤醒的线程们,可以精确唤醒,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。
AQS原理
双向链表加一个volatile int state,链表的每一个节点是一个线程。
VarHandle
VarHandle是指向某个对象的引用。可以用于原子性操作。
- 普通属性也可以通过VarHandle进行原子性操作
- 比反射快,直接操作二进制码
ThreadLocal
ThreadLocal是线程独有,别的线程不能修改。
ThreadLocal用途
- 声明式事务,保证同一个Connection
强、软、弱、虚引用
见之前教程引用
软引用,只有在内存不够用时才会被回收,多用于做缓存。
弱引用,只要进行垃圾回收,弱引用就会被回收,多用于容器里面,ThreadLocal是其一个应用例子。weakHashMap
ThreadLocal使用完了,手动remove掉。
虚引用
ConcurrentHashMap
插入效率低一些,查询时效率高。
教程见ConcurrentHashMap详解(基于1.7和1.8)
ConcurrentSkipListMap
是一个有序的并发Map。
CopyOnWriteList
读多写少可用CopyOnWriteList,写时加锁,读时不加锁。
面试题
两个线程交替输出A1B2C3…Z26
- 使用LockSupport
import java.util.concurrent.locks.LockSupport;
/**
* @author wardseptember
* @create 2020-07-06 16:50
*/
public class LockSupport_Huawei {
static Thread t1 = null, t2 = null;
public static void main(String[] args) {
char[] aI = "1234567".toCharArray();
char[] aC = "ABCDEFG".toCharArray();
t1 = new Thread(() -> {
for (char c : aC) {
System.out.println(c);
LockSupport.unpark(t2);
LockSupport.park();
}
}, "t1");
t2 = new Thread(() -> {
for (char c : aI) {
LockSupport.park();
System.out.println(c);
LockSupport.unpark(t1);
}
}, "t2");
t1.start();
t2.start();
}
}
- 使用wait和notify
/**
* @author wardseptember
* @create 2020-07-05 20:30
*/
public class WaitAndNotify_Huawei {
static Object lock = new Object();
public static void main(String[] args) {
WaitAndNotify_Huawei waitAndNotify = new WaitAndNotify_Huawei();
char[] aI = "1234567".toCharArray();
char[] aC = "ABCDEFG".toCharArray();
Thread t2 = new Thread(() -> {
synchronized (lock) {
for (char c : aC) {
System.out.println(c);
try {
lock.notify();
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
lock.notify();
}
});
Thread t1 = new Thread(() -> {
synchronized (lock) {
for (char c : aI) {
System.out.println(c);
try {
lock.notify();
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
lock.notify();
}
});
t2.start();
t1.start();
}
}
推荐阅读
欢迎关注我的公众号呦,率先更新内容,并且后续还有一些源码级的免费教程推出。