LockSupport
用于创建锁和其他同步类的基本线程阻塞(不释放锁)原语。
基本操作
package character04;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
/**
* @author laimouren
*/
public class LockSupport01 {
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();
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
try {
TimeUnit.SECONDS.sleep(8);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("after 8 seconds");
//unpark()可以在park()之前调用,使park()方法失效
//相比wait()notify()更灵活
LockSupport.unpark(t);
}
}
小知识:object.notify()不释放锁,wait()方法被唤醒了之后仍需要锁
即当一个线程调用object.notify()的时候,另一个线程并不会马上执行,因为object对象的锁还是当前线程所持有,需要当前线程执行完或wait()
面试题
1. 实现一个容器,提供两个方法,add,size
写两个线程,线程1添加10个元素到容器中,线程2 监控元素个数,当个数到5个的时候,线程2给出提示并结束
可以使用两个notify()+wait()或者两个countdownLatch
下面提供使用两次LockSupport.unpark()+park();
public class Test2 {
//实现一个容器 提供两个方法 add和size
//写两个线程,线程1添加10个元素到容器中,线程2实现监控容器中元素的个数,当个数到5个时,线程2给出提示并且结束
//实现原理 LockSupport park(阻塞线程)和unpark(Tread)(唤醒某个线程)
//List list = new ArrayList<>(); //定义一个容器 (非线程安全)
volatile List list =Collections.synchronizedList(new LinkedList<>());//定义一个线程安全的list
public void add(Object o){ this.list.add(o); } //容器中添加对象
public int size(){return this.list.size(); } //获取容器大小
static Thread t2 =null;
static Thread t1 = null;
public static void main(String[] args) {
Test2 test = new Test2();
//线程1 负责监控容器长度是否等于5
t2 = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("t2 启动");
LockSupport.park(); //阻塞t2线程
if(test.size() == 5){
System.out.println("t2 结束");
}
LockSupport.unpark(t1);//唤醒线程t1 t2线程结束
}
},"t2");
t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("t1 启动");
for (int i = 0; i < 10; i++) {
test.add(i);
System.out.println("add "+i);
if(test.size() == 5){
//LockSupport.park(); //阻塞线程
LockSupport.unpark(t2);//唤醒线程t2
LockSupport.park(); //阻塞t1线程
}
}
}
},"t1");
t2.start();//启动线程
t1.start();//启动线程
}
}
2. 写一个固定容量同步容器,拥有put和get方法,以及getCount方法,能够支持2个生产者线程以及10个消费者线程的阻塞调用
synchronized方法:
package character04;
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;
public class MyContatiner1<T> {
final private LinkedList<T> list = new LinkedList<>();
final private int MAX = 10;//最多10个元素
private int count = 0;
public synchronized void put(T t){
while (list.size() == MAX){
try{
this.wait();
}catch (InterruptedException e){
e.printStackTrace();
}
}
list.add(t);
count++;
//通知消费者进行消费
this.notifyAll();
}
public synchronized T get(){
T t = null;
while (list.size() == 0){
try {
this.wait();
}catch (InterruptedException e){
e.printStackTrace();
}
}
t = list.removeFirst();
count--;
//通知生产者进行生产
this.notifyAll();
return t;
}
public static void main(String[] args) {
MyContatiner1<String> container = new MyContatiner1<>();
//启动消费者线程
for (int i = 0;i < 10;i++){
new Thread(()->{
for(int j = 0;j < 5; j++){
System.out.println(container.get());
}
},"consumer"+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++){
container.put(Thread.currentThread().getName()+"->"+j);
}
},"producer"+i).start();
}
}
}
缺陷就是消费者线程可能错误的唤醒消费者线程,生产者线程可能错误的唤醒生产者线程,导致争抢严重
condition方法:
package com.example.demo.test;
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;
public class MyContainer<T> {
final private LinkedList<T> list = new LinkedList<>();
final private int MAX = 10;//最多10个元素
private Lock lock = new ReentrantLock();
//Condition意思是一个等待队列,新建了两个等待队列
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) {
MyContainer<String> container = new MyContainer<>();
//启动消费者线程
for (int i = 0;i < 10;i++){
new Thread(()->{
for(int j = 0;j < 5; j++){
System.out.println(container.get());
}
},"consumer"+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++){
container.put(Thread.currentThread().getName()+"->"+j);
}
},"producer"+i).start();
}
}
}
3. 用两个线程顺序打印A1B2C3D4…Z26
源码阅读原则
读源码必须理解别人的思路;所以需要数据结构基础和设计模式
读源码,先读骨架
- 跑不起来不读(不要静态的点方法进去看实现,而是通过debug一步步得到具体的实现是哪个)
- 解决问题就好——目的性
- 一条线索到底
- 无关细节略过
- 一般不读静态
- 一般动态读法
ReentrantLock源码分析
模板方法设计模式
通过debug来看具体实现
AQS(CLH)初了解
为什么核心是CAS+volatile
因为核心变量state是用volatile修饰的,由子类指定含义
使用双向链表,节点装的是线程thread
以ReentrantLock为例,获得锁的过程是,先看state是否为0,如果是0,说明当前没有线程使用锁,当前线程就使用CAS的方式获得锁,并返回true