本文主要是写了生产者消费者模型,sync的应用层面如何实现类锁、对象锁;
基本的JUC工具类(计数器、循环任务、信号量);读写锁;阻塞队列(线程池里面就是这个)
技术:深,透,明,细
线程
生产者消费者模型——线程操作资源类:
1、高内聚低耦合,线程操作资源类
2、判断干活通知
3、判断使用while,出来以后还是重新判断。(防止多线程的虚假唤醒)
这个生产者消费者demo挺好玩的
class ProDuc{
int num = 0;
public synchronized void increment() throws InterruptedException {
// 1. num现在为 1:那么进入{},当前线程 wait等待唤醒
// 2. num现在为 0:那么就一直等待,直到盘子里面不为空
// 跳过判断,开始干活num++
while (num != 0) {
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName()+"现在数量"+num);
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException {
// 1. num现在为 1:那么就跳过判断,开始干活num--
// 2. num现在为 0:那么进入{},当前线程 wait等待唤醒(另一个线程干活完毕会叫醒服务)
while (num == 0) {
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName()+"现在数量"+num);
this.notifyAll();
}
}
public class Product01 {
public static void main(String[] args) {
ProDuc proDuc = new ProDuc();
new Thread(()->{
for (int i = 0; i < 40; i++) {
try {
proDuc.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"生产1").start();
new Thread(()->{
for (int i = 0; i < 40; i++) {
proDuc.increment();
}
},"生产2").start();
new Thread(()->{
for (int i = 0; i < 80; i++) {
proDuc.decrement();
}
},"消费者1").start();
}
}
lock——判断 干活 通知
synchronized升级lock
资源类更新
class ProDuc {
int num = 0;
Lock reentrantLock = new ReentrantLock();
Condition condition = reentrantLock.newCondition();
public void increment() throws InterruptedException {
reentrantLock.lock();
try {
while (num != 0) {
condition.await();
}
num++;
System.out.println(Thread.currentThread().getName() + "现在数量" + num);
condition.signalAll();
} finally {
reentrantLock.unlock();
}
}
精确通知,顺序访问
private int number = 1;
//1:A 2:B 3:C
private Lock lock = new ReentrantLock();
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
private Condition c3 = lock.newCondition();
public void print5(int totalLoopNumber) {
lock.lock();
try {
//1 判断
while (number != 1) {
//A 就要停止
c1.await();
}
//2 干活
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i + "\t totalLoopNumber: " + totalLoopNumber);
}
//3 通知
number = 2;
c2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
…… ……
}
8锁模型——synchronized锁详细解析:
static是类的意思,加上了就是类锁,是粒度更大的锁
NotSafe的集合API
ConcurrentModificationException:
故障:
ConcurrentModificationException
public static void main(String[] args) {
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 20; i++) {
final String tmp = String.valueOf(i);
new Thread(() -> {
list.add(tmp);
System.out.println(Thread.currentThread().getName() + "添加成功");
}, tmp).start();
}
}
原因:
解决方案:
优化:
ArrayList<>() => Collections.synchronizedList(new ArrayList<>()); => CopyOnWriteList
HashSet<>(); => Collections.synchronizedSet(); => CopyOnWriteArraySet
value是object: PRESENT永久固定写死!!
Callable接口
面试题:callable接口与runnable接口的区别?
答:
(1)是否有返回值
(2)是否抛异常
(3)落地方法不一样,一个是run,一个是call
如果线程尚未完成,还没有生成return,则阻塞 get 方法。
public static void main(String[] args) throws ExecutionException, InterruptedException {
//1、资源类,Runnable、Callable
Source source = new Source();
//2、直接写在futureTask里面
FutureTask<String> futureTask2 = new FutureTask<>(()->{
System.out.println(Thread.currentThread().getName());
return "futureTask2可执行对象 的返回值";
});
FutureTask<String> futureTask = new FutureTask<>(()->{
System.out.println(Thread.currentThread().getName()+"callable");
return "futureTask GOGOGOGOGO";
});
new Thread(futureTask2,"小明").start();
new Thread(futureTask,"小智").start();
System.out.println(futureTask2.get());
System.out.println(futureTask.get());
}
道格李的JUC工具类
1. CountDownLatch
例子:教室关门
一定要那么多人都走了(判断以上执行完毕),我才能继续往下面走
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(8);
// 1、主线程分配好了很多线程去执行任务
for (int i = 0; i < 100; i++){
new Thread(() -> {
countDownLatch.countDown();
System.out.println("我确实减下来了1");
}, Thread.currentThread().getName()+"_").start();
}
// 2、到这里阻塞一下,只需要确定现在的计数确实把栅栏里面的数字全部countDown了
countDownLatch.await();
// 3、判断确实把栅栏里面的东西运行完毕了,就会到下面继续执行
System.out.println("结束");
}
2. CyclicBarrier
七龙珠例子:
public static void main(String[] args) throws InterruptedException {
// 1. 没达到7之前,都会阻塞,没有机会出去,只能等待其他线程帮忙积攒
// 2. 达到7,就是给开了一个窗口,有机会跳出去
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
System.out.println("达成目标,开始执行");
});
for (int i = 0; i < 35; i++) {
final int tmp = i;
new Thread(() -> {
System.out.println(Thread.currentThread().getName()+"收集到第"+tmp+"个");
try {
cyclicBarrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}, String.valueOf(i)).start();
}
System.out.println("aaaa");
}
3. semaphore
争抢车位:
在信号量上我们定义两种操作:
- acquire(获取) 当一个线程调用acquire操作时,它要么通过成功获取信号量(信号量减1),
- 要么一直等下去,直到有线程释放信号量,或超时。
- release(释放)实际上会将信号量的值加1,然后唤醒等待的线程。
- 信号量主要用于两个目的,一个是用于多个共享资源的互斥使用,另一个用于并发线程数的控制。
信号灯 + 栅栏 的Demo:
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(10);
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < 10; i++) {
new Thread(()->{
try {
semaphore.acquire();
System.out.println("线程"+Thread.currentThread().getName()+"抢到了车位");
try { TimeUnit.SECONDS.sleep(2); } catch(InterruptedException e) { e.printStackTrace(); }
System.out.println("线程"+Thread.currentThread().getName()+"离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
countDownLatch.countDown();
semaphore.release();
}
},String.valueOf(i+1)).start();
}
countDownLatch.await();
System.out.println("抢车位游戏结束……");
}
ReentrantReadWriteLock
可重入读写锁:
可读读,不可写写,不可写读
BlockingQueue
阻塞队列:就是一个临时的等待区
具体实现
ArrayBlockingQueue:
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
final ReentrantLock lock;
final Object[] items;
public ArrayBlockingQueue(int capacity, boolean fair,
Collection<? extends E> c) {
初步了解:由数组结构组成的有界阻塞队列。必须给定数组的大小
LinkedBlockingQueue:
public class LinkedBlockingDeque<E> extends AbstractQueue<E> implements BlockingDeque<E>, java.io.Serializable {
final ReentrantLock lock = new ReentrantLock();
static final class Node<E> {
E item;
Node<E> prev;
Node<E> next;
Node(E x) {item = x;}
}
// 不传参就给你搞一个无限大小的等待区,搞死你,CPU直接爆OOM,程序直接崩溃
public LinkedBlockingDeque() {
this(Integer.MAX_VALUE);
}
Tips:
面试的时候会经常问到的点:
1、手写冒泡排序
2、消费者生产者模型
3、设计模式的单例模式