前言
在前面介绍了线程入门、线程安全、线程的一些琐碎的知识点,现在我们讲讲如何让线程变得听话,指挥线程执行。
wait notfiy
- 首先声明一点wait( ),notify( ),notifyAll( )都不属于Thread类,而是属于Object基础类,也就是每个对象都有wait( ),notify( ),notifyAll( ) 的功能,因为每个对象都有锁,锁是每个对象的基础,当然操作锁的方法也是最基础了。
- notify( )方法只会通知等待队列中的第一个相关线程(不会通知优先级比较高的线程)notifyAll( )通知所有等待该竞争资源的线程(也不会按照线程的优先级来执行)
- 假设有三个线程执行了obj.wait( ),那么obj.notifyAll( )则能全部唤醒tread1,thread2,thread3,但是要继续执行obj.wait()的下一条语句,必须获得obj锁,因此,tread1,thread2,thread3只有一个有机会获得锁继续执行,例如tread1,其余的需要等待thread1释放obj锁之后才能继续执行。
- 当调用obj.notify/notifyAll后,调用线程依旧持有obj锁,因此,thread1,thread2,thread3虽被唤醒,但是仍无法获得obj锁。直到调用线程退出synchronized块,释放obj锁后,thread1,thread2,thread3中的一个才有机会获得锁继续执行。
写几个例子
/**
* @classDesc:wait notify的使用
* @author: hj
* @date:2018年12月12日 下午3:18:40
*/
public class ListAdd0 {
private volatile static List list = new ArrayList();
public void add() {
list.add("bjsxt");
}
public int size() {
return list.size();
}
public static void main(String[] args) {
final ListAdd2 list2 = new ListAdd2();
// 1 实例化出来一Object lock
// 当使用wait notify 的时一定要配合synchronized关键字去使用
final Object lock = new Object();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
synchronized (lock) {
for (int i = 0; i < 10; i++) {
list2.add();
System.out.println("当前线程名称" + Thread.currentThread().getName() + "添加了一个元素..");
Thread.sleep(500);
if (list2.size() == 5) {
System.out.println("已经发出通知..");
lock.notify();
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
if (list2.size() != 5) {
try {
System.out.println("t2进入...");
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("当前线程名称" + Thread.currentThread().getName() + "收到通知线程停止..");
throw new RuntimeException();
}
}
}, "t2");
t2.start();
t1.start();
}
}
t2进入...
当前线程名称t1添加了一个元素..
当前线程名称t1添加了一个元素..
当前线程名称t1添加了一个元素..
当前线程名称t1添加了一个元素..
当前线程名称t1添加了一个元素..
已经发出通知..
当前线程名称t1添加了一个元素..
当前线程名称t1添加了一个元素..
当前线程名称t1添加了一个元素..
当前线程名称t1添加了一个元素..
当前线程名称t1添加了一个元素..
当前线程名称t2收到通知线程停止..
Exception in thread "t2"
从结果看出,在t1发出通知后,t2没有立即停止,t1还在添加数据。原因:wait()释放了锁,所以不能立即开始执行。
CountDownLatch
CountDownLatch就像是计时器一样,在完成倒计时后发令让所有的线程开始起跑。再举个例子吧!!
/**
* @classDesc: 监听,当初始化完成之后,才能通知主线程运行
* @author: hj
* @date:2018年12月12日 下午3:48:24
*/
public class UseCountDownLatch {
public static void main(String[] args) {
CountDownLatch countDown = new CountDownLatch(2);
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println("进入线程t1" + "等待其他线程处理完成...");
countDown.await();
System.out.println("t1线程继续执行...");
} catch (Exception e) {
e.printStackTrace();
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("t2线程进行初始化操作...");
try {
Thread.sleep(3000);
System.out.println("t2线程初始化完毕,通知t1线程继续...");
countDown.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("t3线程进行初始化操作...");
try {
Thread.sleep(4000);
System.out.println("t3线程初始化完毕,通知t1线程继续...");
countDown.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
t2.start();
t3.start();
}
}
t2线程进行初始化操作...
t3线程进行初始化操作...
进入线程t1等待其他线程处理完成...
t2线程初始化完毕,通知t1线程继续...
t3线程初始化完毕,通知t1线程继续...
t1线程继续执行...
CyclicBarrier
CyclicBarrier和CountDownLatch有些相似,每个线程都是一个远动员,只有所有的运动员都准备好后才能一起出发。
/**
* @classDesc: 每个线程都是一个远动员,只有所有的运动员都准备好后才能一起出发
* @author: hj
* @date:2018年12月12日 下午3:50:08
*/
public class UseCyclicBarrier {
static class Runner implements Runnable {
private CyclicBarrier barrier;
private String name;
public Runner(CyclicBarrier barrier, String name) {
this.barrier = barrier;
this.name = name;
}
@Override
public void run() {
try {
Thread.sleep(1000 * (new Random()).nextInt(5));
System.out.println(name + " 准备OK.");
barrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(name + " Go!!");
}
}
public static void main(String[] args) throws IOException, InterruptedException {
CyclicBarrier barrier = new CyclicBarrier(3);
ExecutorService pool = Executors.newFixedThreadPool(3);
pool.execute(new Thread(new Runner(barrier, "zhangsan")));
pool.execute(new Thread(new Runner(barrier, "hj")));
pool.execute(new Thread(new Runner(barrier, "papa")));
pool.shutdown();
}
}
进入线程t1等待其他线程处理完成...
t3线程进行初始化操作...
t2线程进行初始化操作...
t2线程初始化完毕,通知t1线程继续...
t3线程初始化完毕,通知t1线程继续...
t1线程继续执行...
Semaphore
Semaphore的主要方法主要有获取资源(acquire)方法和释放资源(release)方法。
/**
* @classDesc: Semaphore用法
* @author: hj
* @date:2018年12月12日 下午3:54:32
*/
public class UseSemaphore {
public static void main(String[] args) {
// 线程池
ExecutorService exec = Executors.newCachedThreadPool();
// 只能5个线程同时访问
Semaphore semp = new Semaphore(5);
// 模拟20个客户端访问
for (int index = 0; index < 20; index++) {
final int NO = index;
Runnable run = new Runnable() {
public void run() {
try {
// 获取许可
semp.acquire();
System.out.println("Accessing: " + NO);
// 模拟实际业务逻辑
Thread.sleep((long) (Math.random() * 10000));
// 访问完后,释放
semp.release();
} catch (InterruptedException e) {
}
}
};
exec.execute(run);
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
// System.out.println(semp.getQueueLength());
// 退出线程池
exec.shutdown();
}
}