0.基础知识
0.0.Java内存模型
- 操作指令:主存->工作内存->主存:read->load->use/assign->store->write; lock,unlock,都是原子命令。
0.1.线程的生命周期
0.2.Java实现多线程的3种方式
- a.继承Thread类
public class Mythread extends Thread {
@Override
public void run() {
System.out.println("线程"+ currentThread().getName()+"运行");
}
public static void main(String[] args){
Mythread mythread = new Mythread();
Mythread mythread1 = new Mythread();
mythread1.start();
mythread.start();
}
}- b.实现Runable接口
public class Mythread implements Runnable {
@Override
public void run() {
System.out.println("线程"+ Thread.currentThread().getName()+"运行");
}
public static void main(String[] args){
Thread mythread = new Thread(new Mythread());
Thread mythread1 = new Thread(new Mythread());
mythread1.start();
mythread.start();
}
}- c.实现Callable接口带返回值的线程
public class Mythread {
public static void main(String[] args) throws ExecutionException,
InterruptedException {
System.out.println("----程序开始运行----");
Date date1 = new Date();
int taskSize = 5;
// 创建一个线程池
ExecutorService pool = Executors.newFixedThreadPool(taskSize);
// 创建多个有返回值的任务
List<Future> list = new ArrayList<Future>();
for (int i = 0; i < taskSize; i++) {
Callable c = new MyCallable(i + " ");
// 执行任务并获取Future对象
Future f = pool.submit(c);
// System.out.println(">>>" + f.get().toString());
list.add(f);
}
// 关闭线程池
pool.shutdown();
// 获取所有并发任务的运行结果
for (Future f : list) {
// 从Future对象上获取任务的返回值,并输出到控制台
System.out.println(">>>" + f.get().toString());
}
Date date2 = new Date();
System.out.println("----程序结束运行----,程序运行时间【"
+ (date2.getTime() - date1.getTime()) + "毫秒】");
}
}
class MyCallable implements Callable<String> {
private String taskNum;
MyCallable(String taskNum) {
this.taskNum = taskNum;
}
public String call() throws Exception {
System.out.println(">>>" + taskNum + "任务启动");
Date dateTmp1 = new Date();
Thread.sleep(1000);
Date dateTmp2 = new Date();
long time = dateTmp2.getTime() - dateTmp1.getTime();
System.out.println(">>>" + taskNum + "任务终止");
return taskNum + "任务返回运行结果,当前任务时间【" + time + "毫秒】";
}
}1.基本原理
1.1.锁
- 分类
参考:Java锁分类
1.2.同步关键字与原理
可修饰成员变量,保证变量的可见性(一个线程对变量修改,对另一个线程可见),保证有序性(通过内存屏障禁止处理器对指令重排序),不能保证原子性。
- happens-before原则
- 内存屏障类型
在每个volatile写操作的前面插入一个StoreStore屏障;
在每个volatile写操作的后面插入一个StoreLoad屏障;
在每个volatile读操作的后面插入一个LoadLoad屏障;
在每个volatile读操作的后面插入一个LoadStore屏障。
StoreStore屏障:禁止上面的普通写和下面的volatile写重排序;
StoreLoad屏障:防止上面的volatile写与下面可能有的volatile读/写重排序
LoadLoad屏障:禁止下面所有的普通读操作和上面的volatile读重排序
LoadStore屏障:禁止下面所有的普通写操作和上面的volatile读重排序- CAS
- 原理:API层通过自旋Unsafe 的实现compareAndSwap , 底层通过CPU的cmpxchg指令保证原子性,去比较寄存器(工作内存)中的 A 和 主内存中的值 V。如果相等,就把要写入的新值 B 存入内存中。如果不相等,就将内存值 V 赋值给寄存器中的值 A,然后通过Java代码中的while循环再次调用cmpxchg指令进行重试,直到设置成功为止。
- 问题
- ABA问题: JDK从1.5开始提供了AtomicStampedReference类来解决ABA问题,具体操作封装在compareAndSet()中。compareAndSet()首先检查当前引用和当前标志与预期引用和预期标志是否相等,如果都相等,则以原子方式将引用值和标志的值设置为给定的更新值。
- 自旋照成的cpu空转开销
- 保证一个共享变量的原子操作:Java从1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,可以把多个变量放在一个对象里来进行CAS操作。
- synchronized原理:
- synchronized特点:保证内存可见性、操作原子性,同步代码块内部可能发生指令重排序。
- synchronized影响性能的原因:
- 1、加锁解锁操作需要额外操作;
- 2、互斥同步对性能最大的影响是阻塞的实现,因为阻塞涉及到的挂起线程和恢复线程的操作都需要转入内核态中完成(用户态与内核态的切换的性能代价是比较大的)
- synchronized锁:对象头中的Mark Word根据锁标志位的不同而被复用。
Mark Word:存储自身的运行时数据,例如 HashCode、GC 年龄、锁相关信息等内容。 Klass Pointer:类型指针指向它的类元数据的指针。
- 锁升级与优化点:
- 偏向锁:在只有一个线程执行同步块时提高性能。Mark Word存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需简单比较ThreadID。特点:只有等到线程竞争出现才释放偏向锁,持有偏向锁的线程不会主动释放偏向锁。之后的线程竞争偏向锁,会先检查持有偏向锁的线程是否存活,如果不存货,则对象变为无锁状态,重新偏向;如果仍存活,则偏向锁升级为轻量级锁,此时轻量级锁由原持有偏向锁的线程持有,继续执行其同步代码,而正在竞争的线程会进入自旋等待获得该轻量级锁
- 轻量级锁:线程交替执行时能提升性能。在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,尝试拷贝锁对象目前的Mark Word到栈帧的Lock Record,若拷贝成功:虚拟机将使用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针,并将Lock record里的owner指针指向对象的Mark Word。若拷贝失败:若当前只有一个等待线程,则可通过自旋稍微等待一下,可能持有轻量级锁的线程很快就会释放锁。 但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁膨胀为重量级锁
- 重量级锁:指向互斥量(mutex),底层通过操作系统的mutex lock实现。等待锁的线程会被阻塞,由于Linux下Java线程与操作系统内核态线程一一映射,所以涉及到用户态和内核态的切换、操作系统内核态中的线程的阻塞和恢复。
- mutex工作方式:
1) 申请mutex2) 如果成功,则持有该mutex3) 如果失败,则进行spin自旋. spin的过程就是在线等待mutex, 不断发起mutex gets, 直到获得mutex或者达到spin_count限制为止4) 依据工作模式的不同选择yiled还是sleep5) 若达到sleep限制或者被主动唤醒或者完成yield, 则重复1)~4)步,直到获得为止- PS. Class锁和对象锁不是相同的锁;修饰代码块,会加入monitorenter、monitorexit,修饰方法,会打标ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法。
- 锁消除:锁消除即删除不必要的加锁操作。虚拟机即时编辑器在运行时,对一些“代码上要求同步,但是被检测到不可能存在共享数据竞争”的锁进行消除。根据代码逃逸技术,如果判断到一段代码中,堆上的数据不会逃逸出当前线程,那么可以认为这段代码是线程安全的,不必要加锁。
public class SynchronizedTest {
public static void main(String[] args) {
SynchronizedTest test = new SynchronizedTest();
for (int i = 0; i < 100000000; i++) {
test.append("abc", "def");
}
}
public void append(String str1, String str2) {
StringBuffer sb = new StringBuffer();
sb.append(str1).append(str2);
//StringBuffer属于一个局部变量,并且不会从该方法中逃逸出去
//(即StringBuffer sb的引用没有传递到该方法外,不可能被其他线程拿到该引用)
//所以其实这过程是线程安全的,可以将锁消除。
}
}- 锁粗化:如果一系列的连续操作都对同一个对象反复加锁和解锁,甚至加锁操作是出现在循环体中的,那即使没有出现线程竞争,频繁地进行互斥同步操作也会导致不必要的性能损耗。如果虚拟机检测到有一串零碎的操作都是对同一对象的加锁,将会把加锁同步的范围扩展(粗化)到整个操作序列的外部。
public class StringBufferTest {
StringBuffer stringBuffer = new StringBuffer();
public void append(){
stringBuffer.append("a");
stringBuffer.append("b");
stringBuffer.append("c");
}
}- 自旋与自适应:互斥同步对性能最大的影响是阻塞的实现,因为挂起线程和恢复线程的操作都需要转入内核态中完成,这些操作给系统的并发性能带来很大的压力。同时虚拟机的开发团队也注意到在许多应用上面,共享数据的锁定状态只会持续很短一段时间,为了这一段很短的时间频繁地阻塞和唤醒线程是非常不值得的。
- 使用-XX:+UseSpinning开开启;在JDK1.6中默认开启。
- 通过参数-XX:PreBlockSpin可以调整自旋次数,默认的自旋次数为10。1.6引入自适应自旋锁
- 场景:CAS自旋,轻量级锁获取...
- 区别:
- sleep与wait
1.出参不同,sleep出自Thread,wait出自Object
2.sleep靠自定义时间唤醒,wait靠notify唤醒
2.都释放执行权,但sleep不释放锁,wait释放锁- synchronized与volitale
synchronized 关键字和 volatile 关键字是两个互补的存在,而不是对立的存在:
1、实现方式不同:volatile关键字是线程同步的轻量级实现。
2、阻塞:volatile关键字不会发生阻塞,而synchronized关键字可能会发生阻塞。
3、修饰语法:volatile关键字只能用于变量而synchronized关键字可以修饰方法以及代码块。
4、synchronized保证原子性,可见性;volatile保证可见性,有序性。
5、volatile关键字主要用于解决变量在多个线程之间的可见性,而 synchronized关键字解决的是多个线程之间访问资源的同步性。- synchronized与ReentrantLock
1、都是可重入锁
2、ReentrantLock提供了更高级的功能:如中断锁等待:lock.lockInterruptibly();公平锁的实现等;3、实现方式不同。
1.3.【ThreadLocal原理】
- 数据结构
1、Thread类有一个类型为ThreadLocal.ThreadLocalMap的实例变量threadLocals,也就是说每个线程有一个自己的ThreadLocalMap。
2、ThreadLocalMap有自己的独立实现,可以简单地将它的key视作ThreadLocal,value为代码中放入的值(实际上key并不是ThreadLocal本身,而是它的一个弱引用)。每个线程在往ThreadLocal里放值的时候,都会往自己的ThreadLocalMap里存,读也是以ThreadLocal作为引用,在自己的map里找对应的key,从而实现了线程隔离。3、ThreadLocalMap有点类似HashMap的结构,hashmap用拉链法解决hash冲突,ThreadLocalMap用的是开放地址法。4、我们还要注意Entry, 它的key是ThreadLocal<?> k ,继承自WeakReference, 也就是我们常说的弱引用类型。- hash算法
private static final int HASH_INCREMENT = 0x61c88647; private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT); //以黄金分割树递增
}- set-hash冲突 【参考文档】
第一种情况: 通过hash计算后的槽位对应的Entry数据为null,这里直接将数据放到该槽位即可。第二种情况: 槽位数据不为null,key值与当前ThreadLocal通过hash计算获取的key值一致:这里直接更新该槽位的数据。第三种情况: 槽位数据不为null,往后遍历过程中,在找到Entry为null的槽位之前,没有遇到key过期的Entry:这里直接更新该槽位的数据。第四种情况: 槽位数据不为空,往后遍历过程中,在找到Entry为null的槽位之前,遇到key过期的Entry,如下图,往后遍历过程中,一到了index=7的槽位数据Entry的key=null:散列数组下标为7位置对应的Entry数据key为null,表明此数据key值已经被垃圾回收掉了,此时就会执行replaceStaleEntry()方法,该方法含义是替换过期数据的逻辑,以index=7位起点开始遍历,进行探测式数据清理工作。初始化探测式清理过期数据扫描的开始位置:slotToExpunge = staleSlot = 7以当前staleSlot开始 向前迭代查找,找其他过期的数据,然后更新过期数据起始扫描下标slotToExpunge。for循环迭代,直到碰到Entry为null结束。【参考文档】
2.Java线程池的实现原理
- 原理:一般由一个任务队列和线程池构成,任务队列控制待执行的任务,而线程池控制线程的数量。一般,若队列中没有等待执行的任务,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程池中有等待的工作线程,就可以开始运行了;否则进入等待队列。
- 优势: 提效,降耗,管控。
1.线程池减少了线程创建和销毁的次数,每个工作线程都可以被重复利用,可执行多个任务。2.可以更好的控制线程的数量,防止线程数量太大而消耗过多的内存。 线程池状态及生命周期管理:高3位保存runState,低29位保存workerCount
- RUNNING :能接受新提交的任务,并且也能处理阻塞队列中的任务;
- SHUTDOWN:关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务。在线程池处于 RUNNING 状态时,调用 shutdown()方法会使线程池进入到该状态。(finalize() 方法在执行过程中也会调用shutdown()方法进入该状态);
- STOP:不能接受新任务,也不处理队列中的任务,会中断正在处理任务的线程。在线程池处于 RUNNING 或 SHUTDOWN 状态时,调用 shutdownNow() 方法会使线程池进入到该状态;
- TIDYING:如果所有的任务都已终止了,workerCount (有效线程数) 为0,线程池进入该状态后会调用 terminated() 方法进入TERMINATED 状态。
- TERMINATED:在terminated() 方法执行完后进入该状态,默认terminated()方法中什么也没有做。
Java.concurrent.util提供的4种默认线程池实现
a.类图
b.特点
newSingleThreadExecutor: 创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }newFixedThreadPool: 创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }newCachedThreadPool:
return new ThreadPoolExecutor (0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), threadFactory);newScheduledThreadPool: 创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
public ScheduledThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory) { super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue(), threadFactory); }
c.ThreadPoolExecutor的完整构造方法的签名
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) corePoolSize - 池中所保存的线程数,包括空闲线程。 maximumPoolSize-池中允许的最大线程数。 keepAliveTime - 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。 unit - keepAliveTime 参数的时间单位。 workQueue - 执行前用于保持任务的队列。此队列仅保持由 execute方法提交的 Runnable任务。 threadFactory - 执行程序创建新线程时使用的工厂。 handler - 由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。 ThreadPoolExecutor是Executors类的底层实现。任务队列与线程池的交互:所有BlockingQueue都可用于传输和保持提交的任务。可以使用此队列与线程池进行交互。
第一步:如果运行的线程少于 corePoolSize,则 Executor始终首选添加新的线程,而不进行排队(即,当前运行的线程小于corePoolSize,则任务根本不会存放,添加到queue中,而是直接抄家伙(thread)开始运行)。第二步:如果运行的线程等于或多于 corePoolSize,则 Executor始终首选将请求加入队列,而不添加新的线程。第三步:如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝,将执行RejectedExecutionHandler。BlockingQueue的三种策略:
- 直接提交:工作队列的默认选项是 SynchronousQueue,首先SynchronousQueue是无界的,但是由于该Queue本身的特性,在某次添加元素后必须等待其他线程取走后才能继续添加。
需防止线程数暴增,消耗太大内存 - 无界队列:使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize,因此,maximumPoolSize的值也就无效了。当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
防止任务队列暴增,消耗太大内存 - 有界队列:当使用有限的 maximumPoolSizes时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:
使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞,则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。
- 直接提交:工作队列的默认选项是 SynchronousQueue,首先SynchronousQueue是无界的,但是由于该Queue本身的特性,在某次添加元素后必须等待其他线程取走后才能继续添加。
RejectedExecutionHandler 4种策略
- a.CallerRunsPolicy:线程调用运行该任务的 execute 本身。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。这个策略显然不想放弃执行任务。但是由于池中已经没有任何资源了,那么就直接使用调用该execute的线程本身来执行。
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { r.run(); } }- b.AbortPolicy:处理程序遭到拒绝将抛出运行时RejectedExecutionException这种策略直接抛出异常,丢弃任务。
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { throw new RejectedExecutionException(); }- c.DiscardPolicy:不能执行的任务将被删除,这种策略和AbortPolicy几乎一样,也是丢弃任务,只不过他不抛出异常。
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { }- c.DiscardOldestPolicy:如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序(如果再次失败,则重复此过程)该策略就稍微复杂一些,在pool没有关闭的前提下首先丢掉缓存在队列中的最早的任务,然后重新尝试运行该任务。这个策略需要适当小心。 设想:如果其他线程都还在运行,那么新来任务踢掉旧任务,缓存在queue中,再来一个任务又会踢掉queue中最老任务。
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { e.getQueue().poll(); e.execute(r); } }- d.总结:keepAliveTime和maximumPoolSize及BlockingQueue的类型均有关系。如果BlockingQueue是无界的,那么永远不会触发maximumPoolSize,自然keepAliveTime也就没有了意义。 反之,如果核心数较小,有界BlockingQueue数值又较小,同时keepAliveTime又设的很小,如果任务频繁,那么系统就会频繁的申请回收线程。
3.java.util.concurrent包
- 并发容器:这些容器的关键方法大部分都实现了线程安全的功能,却不使用同步关键字(synchronized)。值得注意的是Queue接口本身定义的几个常用方法的区别:
1.add方法和offer方法的区别在于超出容量限制时前者抛出异常,后者返回false; 2.remove方法和poll方法都从队列中拿掉元素并返回,但是他们的区别在于空队列下操作前者抛出异常,而后者返回null; 3.element方法和peek方法都返回队列顶端的元素,但是不把元素从队列中删掉,区别在于前者在空队列的时候抛出异常,后者返回null。阻塞队列:
BlockingQueue.class,阻塞队列接口; BlockingDeque.class,双端阻塞队列接口 ArrayBlockingQueue.class,阻塞队列,数组实现 LinkedBlockingDeque.class,阻塞双端队列,链表实现; LinkedBlockingQueue.class,阻塞队列,链表实现 DelayQueue.class,阻塞队列,并且元素是Delay的子类,保证元素在达到一定时间后才可以取得到 PriorityBlockingQueue.class,优先级阻塞队列 SynchronousQueue.class,同步队列,但是队列长度为0,生产者放入队列的操作会被阻塞,直到消费者过来取,所以这个队列根本不需要空间存放元素;有点像一个独木桥,一次只能一人通过,还不能在桥上停留非阻塞队列:
ConcurrentLinkedDeque.class,非阻塞双端队列,链表实现 ConcurrentLinkedQueue.class,非阻塞队列,链表实现转移队列:
TransferQueue.class,转移队列接口,生产者要等消费者消费的队列,生产者尝试把元素直接转移给消费者 LinkedTransferQueue.class,转移队列的链表实现,它比SynchronousQueue更快其它容器:
ConcurrentHashMap.class,并发HashMap
- 同步器:
- CountDownLatch:在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。用给定的计数 初始化 CountDownLatch。由于调用了 countDown() 方法,所以在当前计数到达零之前,await 方法会一直受阻塞。之后,会释放所有等待的线程,await 的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置。
import java.util.*; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class SyncCountDownLatch { class PokerPlayer implements Runnable { private final String[] POINTS = new String[]{"A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K",}; private String name; private CountDownLatch countDown; public PokerPlayer(String name, CountDownLatch countDown) { this.name = name; this.countDown = countDown; } @Override public void run() { try { Thread.sleep((long) (Math.random() * 5000)); // 随机抽一张牌 Random random = new Random(); String myPoint = POINTS[random.nextInt(13)]; System.out.println(name + "ready!"); // 准备就绪,等待其它玩家也就绪 countDown.countDown(); countDown.await(); // 玩家都就绪了,翻底牌 System.out.println(name + ":" + myPoint); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { final int PER_TABLE_PLAYERS = 4; // 多少人够开一桌的 CountDownLatch countDown = new CountDownLatch(PER_TABLE_PLAYERS); SyncCountDownLatch ins = new SyncCountDownLatch(); ExecutorService executorPool = Executors.newFixedThreadPool(PER_TABLE_PLAYERS); for(int i=0; i<PER_TABLE_PLAYERS; i++) { executorPool.execute(ins.new PokerPlayer(i +"号玩家", countDown)); } executorPool.shutdown(); } }- CyclicBarrier:
import java.io.IOException; import java.util.Random; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; class Runner implements Runnable { private CyclicBarrier barrier; private String name; public Runner(CyclicBarrier barrier, String name) { super(); this.barrier = barrier; this.name = name; } @Override public void run() { try { Thread.sleep(1000 * (new Random()).nextInt(8)); System.out.println(name + " 准备OK."); barrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } System.out.println(name + " Go!!"); } } public class Race { public static void main(String[] args) throws IOException, InterruptedException { CyclicBarrier barrier = new CyclicBarrier(3); ExecutorService executor = Executors.newFixedThreadPool(3); executor.submit(new Thread(new Runner(barrier, "zhangsan"))); executor.submit(new Thread(new Runner(barrier, "lisi"))); executor.submit(new Thread(new Runner(barrier, "wangwu"))); executor.shutdown(); } }CountDownLatch: 一个线程(或者多个),等待另外N个线程完成某个事情之后才能执行。就像报数一样, 线程完成一个就记一个,只不过是递减的。
CyclicBarrier: N个线程相互等待,任何一个线程完成之前,所有的线程都必须等待。像一个水闸, 线程执行就像水流, 在水闸处都会堵住, 等到水满(线程到齐)了, 才开始泄流. 【AQS】
4.生产者消费者模型
//仓库接口
public interface Storage {
void produce(int num);
void consume(int num);
}
//阻塞队列实现
public class StorageForBlockingQueue implements Storage {
private static final int MAX_STORAGE = 100;
private LinkedBlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<Integer>();
@Override
public void produce(int num) {
if (blockingQueue.size() == MAX_STORAGE) {
System.out.println("【库存量】:" + MAX_STORAGE + "/t暂时不能执行生产任务!");
}
//生产num个产品
for (int i = 0; i < num; i++) {
try {
int random = new Random().nextInt(100);
blockingQueue.put(random);
System.out.println("【已经生产产品】:" + random + "【现仓储量为】:" + blockingQueue.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("【已经生产产品数量】:" + num + "【现仓储量为】:" + blockingQueue.size());
}
@Override
public void consume(int num) {
if (blockingQueue.size() == 0) {
System.out.println("【库存量】:0,暂时不能执行消费任务!");
}
for (int i = 0; i < num; i++) {
try {
Integer take = blockingQueue.take();
System.out.println("【已经消费产品】:" + take + "【现仓储量为】:" + blockingQueue.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("【已经消费产品数】:" + num + "【现仓储量为】:" + blockingQueue.size());
}
}
//Lock锁实现
public class StorageForLock implements Storage{
private static final int MAX_STORAGE = 100;
private LinkedList<Integer> linkedList = new LinkedList<Integer>();
private final ReentrantLock lock = new ReentrantLock();
private final Condition full = lock.newCondition();
private final Condition empty = lock.newCondition();
@Override
public void produce(int num) {
try {
lock.lock();
while (linkedList.size() + num > MAX_STORAGE) {
System.out.println("【要生产的产品数量】:" + num + "【库存量】:" + linkedList.size() + "暂时不能执行生产任务!");
try {
full.await();
} catch (InterruptedException e) {
System.out.println("生产线程被打断了...");
}
}
//生产num个产品
for (int i = 0; i < num; i++) {
linkedList.add(new Random().nextInt(100));
}
System.out.println("【已经生产产品数】:" + num + "【现仓储量为】:" + linkedList.size());
empty.signalAll();
full.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
@Override
public void consume(int num) {
try {
lock.lock();
while (num > linkedList.size()) {
System.out.println("【要消费的产品数量】:" + num + "【库存量】:" + linkedList.size() + "暂时不能执行消费任务!");
try {
empty.await();
} catch (InterruptedException e) {
System.out.println("消费线程被打断了...");
}
}
for (int i = 0; i < num; i++) {
linkedList.remove();
}
System.out.println("【已经消费产品数】:" + num + "【现仓储量为】:" + linkedList.size());
full.signalAll();
empty.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
//synchronized实现
public class StorageForSync implements Storage {
private static final int MAX_STORAGE = 100;
private LinkedList<Integer> linkedList = new LinkedList<Integer>();
@Override
public synchronized void produce(int num) {
// while取代if,防止线程不安全问题
while (linkedList.size() + num > MAX_STORAGE) {
System.out.println("【要生产的产品数量】:" + num + "【库存量】:" + linkedList.size() + "暂时不能执行生产任务!");
try {
wait();
} catch (InterruptedException e) {
System.out.println("生产线程被打断了...");
}
}
//生产num个产品
for (int i = 0; i < num; i++) {
linkedList.add(new Random().nextInt(100));
}
System.out.println("【已经生产产品数】:" + num + "【现仓储量为】:" + linkedList.size());
//notifyAll取代notify防止出现死索
notifyAll();
}
@Override
public synchronized void consume(int num) {
while (num > linkedList.size()) {
System.out.println("【要消费的产品数量】:" + num + "【库存量】:" + linkedList.size() + "暂时不能执行消费任务!");
try {
wait();
} catch (InterruptedException e) {
System.out.println("消费线程被打断了...");
}
}
for (int i = 0; i < num; i++) {
linkedList.remove();
}
System.out.println("【已经消费产品数】:" + num + "【现仓储量为】:" + linkedList.size());
notifyAll();
}
}
//生产者
public class Producer extends Thread {
private int num;
private Storage storage;
public Producer(int num, Storage storage) {
this.num = num;
this.storage = storage;
}
public void run() {
produce(num);
}
private void produce(int num) {
storage.produce(num);
}
}
//消费者
public class Consumer extends Thread{
private int num;
private Storage storage;
public Consumer(int num, Storage storage) {
this.num = num;
this.storage = storage;
}
public void run() {
consume(num);
}
private void consume(int num) {
storage.consume(num);
}
}
---
public class Test {
public static void main(String[] args) {
Storage storage = new StorageForBlockingQueue();
// Storage storage = new StorageForSync();
// Storage storage = new StorageForLock();
Producer producer0 = new Producer(10, storage);
Producer producer1 = new Producer(10, storage);
Producer producer2 = new Producer(10, storage);
Producer producer3 = new Producer(10, storage);
Producer producer4 = new Producer(10, storage);
Producer producer5 = new Producer(80, storage);
Consumer consumer0 = new Consumer(30, storage);
Consumer consumer1 = new Consumer(10, storage);
Consumer consumer2 = new Consumer(10, storage);
consumer0.start();
consumer1.start();
consumer2.start();
producer0.start();
producer1.start();
producer2.start();
producer3.start();
producer4.start();
producer5.start();
}
}