文章目录
CountDownLatch
减法计数器
package org.example.count;
import java.util.concurrent.CountDownLatch;
/**
* @Author: sshdg
* @Date: 2020/9/6 8:24
*/
public class CountDownLatchTest {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(6);//设置计数器
for (int i = 0; i < 6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"->go out");
countDownLatch.countDown();//计数器减一
}, String.valueOf(i)).start();
}
countDownLatch.await();//等待计数器归0后再执行下面的代码
System.out.println("close door");
}
}
CyclicBarrier
从字面上的意思可以知道,这个类的中文意思是“循环栅栏”。大概的意思就是一个可循环利用的屏障。
它的作用就是会让所有线程都等待完成后才会继续下一步行动。
举个例子,就像生活中我们会约朋友们到某个餐厅一起吃饭,有些朋友可能会早到,有些朋友可能会晚到,但是这个餐厅规定必须等到所有人到齐之后才会让我们进去。这里的朋友们就是各个线程,餐厅就是 CyclicBarrier。
构造方法
public CyclicBarrier(int parties)
public CyclicBarrier(int parties, Runnable barrierAction)
解析:
- parties 是参与线程的个数
- 第二个构造方法有一个 Runnable 参数,这个参数的意思是最后一个到达线程要做的任务
重要方法
public int await() throws InterruptedException, BrokenBarrierException
public int await(long timeout, TimeUnit unit) throws InterruptedException, BrokenBarrierException, TimeoutException
解析:
- 线程调用 await() 表示自己已经到达栅栏
- BrokenBarrierException 表示栅栏已经被破坏,破坏的原因可能是其中一个线程 await() 时被中断或者超时
package org.example.count;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
/**
* @Author: sshdg
* @Date: 2020/9/6 8:35
*/
public class CyclicBarrierTest {
/**
* 集齐七颗龙珠召唤神龙
* @param args
*/
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, ()->{
System.out.println("召唤神龙成功");
});
for (int i = 0; i < 7; i++) {
int temp = i;
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"->收集了"+temp+"颗");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}, String.valueOf(i)).start();
}
}
}
CyclicBarrier 使用场景
可以用于多线程计算数据,最后合并计算结果的场景。
CountDownLatch 区别
- CountDownLatch 是一次性的,CyclicBarrier 是可循环利用的
- CountDownLatch 参与的线程的职责是不一样的,有的在倒计时,有的在等待倒计时结束。CyclicBarrier 参与的线程职责是一样的。
Semaphore
1. Semaphore 是什么?
Semaphore 字面意思是信号量的意思,它的作用是控制访问特定资源的线程数目。
类似抢车位一样,可以限定资源的数量来控制线程访问
2. 怎么使用 Semaphore?
2.1 构造方法
public Semaphore(int permits)
public Semaphore(int permits, boolean fair)
解析:
- permits 表示许可线程的数量
- fair 表示公平性,如果这个设为 true 的话,下次执行的线程会是等待最久的线程
2.2 重要方法
public void acquire() throws InterruptedException
public void release()
解析:
- acquire() 表示阻塞并获取许可
- release() 表示释放许可
2.3 基本使用
package org.example.count;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
* @Author: sshdg
* @Date: 2020/9/6 20:59
*/
public class SemaphoreTest {
/**
* 抢车位
* @param args
*/
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < 6; i++) {
new Thread(()->{
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"抢到车位");
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName()+"释放车位");
semaphore.release();
}
}, String.valueOf(i)).start();
}
}
}
3. Semaphore 使用场景
可用于流量控制,限制最大的并发访问数。
读写锁ReadWriteLock
写入时,只允许一个线程进行写入。读取时,可以多个线程读取。
readWriteLock.writeLock().lock();
readWriteLock.readLock().lock();
使用方法和lock
相同,业务代码try catch包裹起来,finally中unlock
package org.example.read_write_lock;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @Author: sshdg
* @Date: 2020/9/6 21:46
*/
public class ReadWriteLockTest {
public static void main(String[] args) {
MyCache myCache = new MyCache();
for (int i = 0; i < 5; i++) {
final int temp = i;
new Thread(()->{
myCache.put(temp+"", temp+"");
}, String.valueOf(i)).start();
}
for (int i = 0; i < 5; i++) {
final int temp = i;
new Thread(()->{
myCache.get(temp+"");
}, String.valueOf(i)).start();
}
}
}
class MyCache{
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
Map<String, String> map = new HashMap<>();
/**
* 写入
* @param key
* @param value
*/
public void put(String key, String value){
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"写入"+key);
map.put(key, value);
System.out.println(Thread.currentThread().getName()+"写入OK");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock();
}
}
public void get(String key){
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"读取"+key);
map.get(key);
System.out.println(Thread.currentThread().getName()+"读取OK");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
}
}
阻塞队列BlockingQueue
BlockingQueue的方法
BlockingQueue 具有 4 组不同的方法用于插入、移除以及对队列中的元素进行检查。如果请求的操作不能得到立即执行的话,每个方法的表现也不同。这些方法如下:
抛出异常 | 特殊值 | 阻塞 | 超时 | |
---|---|---|---|---|
插入 | add(e) | offer(e) | put(e) | offer(e, time, unit) |
移除 | remove() | poll() | take() | poll(time, unit) |
检查 | element() | peek() | 不可用 | 不可用 |
四组不同的行为方式解释:
1(异常)
如果试图的操作无法立即执行,抛一个异常。
2(特定值)
如果试图的操作无法立即执行,返回一个特定的值(常常是 true / false)。
3 (阻塞)
如果试图的操作无法立即执行,该方法调用将会发生阻塞,直到能够执行。
4(超时)
如果试图的操作无法立即执行,该方法调用将会发生阻塞,直到能够执行,但等待时间不会超过给定值。返回一个特定值以告知该操作是否成功(典型的是 true / false)。
package org.example.blocking_queue;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
/**
* @Author: sshdg
* @Date: 2020/9/7 9:37
*/
public class BlockingQueueTest {
public static void main(String[] args) throws InterruptedException {
BlockingQueue blockingQueue = new ArrayBlockingQueue(3);
System.out.println(blockingQueue.offer("1", 3L, TimeUnit.SECONDS));//true
System.out.println(blockingQueue.offer("2", 3L, TimeUnit.SECONDS));//true
System.out.println(blockingQueue.offer("3", 3L, TimeUnit.SECONDS));//true
System.out.println(blockingQueue.offer("4", 3L, TimeUnit.SECONDS));//false
}
}
同步队列SynchronousQueue
put进一个元素,必须先取出,否则无法再put
package org.example.blocking_queue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
/**
* @Author: sshdg
* @Date: 2020/9/7 10:39
*/
public class SynchronousQueueTest {
public static void main(String[] args) {
BlockingQueue<String> synchronousQueue = new SynchronousQueue<>();
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+" put 1");
synchronousQueue.put("1");
System.out.println(Thread.currentThread().getName()+" put 2");
synchronousQueue.put("2");
System.out.println(Thread.currentThread().getName()+" put 3");
synchronousQueue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "T1").start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+" take "+synchronousQueue.take());
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+" take "+synchronousQueue.take());
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+" take "+synchronousQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "T2").start();
}
}
线程池
阿里开发手册规定:
【强制】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors返回的线程池对象的弊端如下:
1)FixedThreadPool和SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
2)CachedThreadPool:允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。
类似数据库连接池,池技术就是存储资源的,用的时候取、用完归还。
工具类创建的三种方法
Executors.newSingleThreadExecutor();//单个线程的线程池
Executors.newFixedThreadPool(5);//自定义大小的线程池
Executors.newCachedThreadPool();//可伸缩的,使用的多就多
使用
try {
threadPool.execute(()->{
//业务
});
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();//关闭
}
但是,通过查看源码你会发现,这三种方法底层都是通过ThreadPoolExecutor
创建的线程池。阿里巴巴开发手册中也强制规定了不允许使用工具类创建线程池。
接下来,我们就来看看,如何自定义一个线程池
自定义线程池
七大参数
以银行窗口为例:
设共有5个窗口(最大),日常开启2个(默认),等候区3个位置(阻塞队列),则如果两个窗口和三个等候区的位置全部满员时,开启5个窗口。若之后还有人进来,则拒绝(拒绝策略)。若超过一定时间,多出的窗口都没人使用了,则再将其关闭,恢复到默认窗口数2两个。
new ThreadPoolExecutor(2,//默认线程池大小
5,//最大 大小
5,//超时时间,就是说如果开到最大数量的线程,而超过这些时间,多出来的没人使用,则关闭多出来的。
TimeUnit.SECONDS,//时间单位
new ArrayBlockingQueue<>(3),//阻塞队列,等候区。如果需要的线程数超过等待区中的,则会开到最大的线程数。
Executors.defaultThreadFactory(),//线程工厂
new ThreadPoolExecutor.AbortPolicy()//拒绝策略:如果开到最大数量之后,阻塞队列还是满了,则之后的请求的拒绝的策略为此。
);
拒绝策略
//不处理任务,抛出异常
new ThreadPoolExecutor.AbortPolicy();
//哪里来的回哪去,如main线程请求的线程池,则交由main线程处理
new ThreadPoolExecutor.CallerRunsPolicy();
//不处理任务,不会抛出异常
new ThreadPoolExecutor.DiscardPolicy();
//尝试和最早的请求线程去竞争,也不会抛出异常
new ThreadPoolExecutor.DiscardOldestPolicy();
最大线程数
cpu密集型:
根据cpu的参数来确定。cpu是几线程,最大就设置几个
Runtime.getRuntime().availableProcessors()
通过以上代码获取cpu线程数
IO密集型:
io十分占用资源,最大线程数大于你程序中十分占用资源的io的线程数即可