文章目录
提示:以下是本篇文章正文内容,下面案例可供参考
一、集合线程安全问题
几种常用的Java集合如ArrayList等,在插入数据时,均存在线程安全问题
1. ArrayList解决方案
- Vector:JDK1.0的解决方案
List<String> list = new Vector<>();
- Collections中的同步链表方法
List<String> list = Collections.synchronizedList(new ArrayList<>());
-
List<String> list = new CopyOnWriteArrayList<>();
比较常用的方法
CopyOnWriteArrayList原理
写时复制技术:读的时候支持并发读,写时支持独立写
在写入的时候先将原先的数据读出,然后修改数据,在其它线程读的时候直接读取写入的新内容
2. HashMap解决方案
ConcurrentHashMap
Map<String,String> map = new ConcurrentHashMap<>();
3. HashSet解决方案
- 使用
CopyOnWriteArraySet
解决线程不安全问题
Set<String> set = new CopyOnWriteArraySet<>();
二、多线程锁
1. sync同步锁
- 当sync加到方法上时,锁的是当前对象,也就是this,调用方法的那个对象
- 当方法上同时加了
static sync
关键字时,这是锁的是Class对象了 - 当代码块上加了sync,锁的是sync括号内配置的对象
2. 公平锁和非公平锁
- 可重入锁默认为非公平锁
- 非公平锁可能导致所有活都让一个线程干了,部分线程分不到活。 优点在于其执行的效率高
- 公平锁效率相对低一点,每个线程都能分配到工作
3. 可重入锁
sync(隐式) Lock(显式)都是可重入锁
可以*进出同步代码块,逐层进入
4. 死锁
什么是死锁 两个或两个以上的进程,因为争夺资源而造成的相互等待的现象
死锁的原因: 资源不足,资源分配不当,进程推进顺序不合适
死锁判定方法:
- 第一步:jps命令 类似Linux的ps -ef
jps -l
- 第二步:jstack 查看堆栈信息 JVM自带的堆栈跟踪工具 jstack 10860 10860是线程号
三、JUC辅助类
1. 减少计数 CountDownLatch
static void testCountDownLatch() throws InterruptedException {
//使用CountDownLatch时
//设置减少计数的初始值
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i <= 6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"离开教室");
countDownLatch.countDown(); //计数器-1
},String.valueOf(i)).start();
}
countDownLatch.await(); //当countDownLatch初始值减为0时,await()才终止并继续执行后续代码
System.out.println(Thread.currentThread().getName()+"锁门");
}
2. 循环栅栏 CyclicBarrier
//触发条件
public static final int NUMBER = 7;
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(NUMBER,()->{
System.out.println("CyclicBarrier条件触发");
});
//创建7个线程
for (int i = 1; i <= NUMBER; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName());
try {
//如果没有达到触发条件,创建的线程就一直等待
// 也就是说await方法要等待7次之后CyclicBarrier条件才能触发
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
}
3. 信号灯 Semaphore
//创建Semaphore对象,设置许可数量 (停车位)
Semaphore semaphore = new Semaphore(3);
//模拟6辆汽车争夺三个停车位
for (int i = 1; i <= 6; i++) {
new Thread(()->{
try {
semaphore.acquire();//抢占信号灯资源
System.out.println(Thread.currentThread().getName()+"抢到了车位");
//随机生成0-5的休眠时间
TimeUnit.SECONDS.sleep(new Random().nextInt(5));
System.out.println(Thread.currentThread().getName()+"离开了车位**");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//必须要释放,否则程序不结束
semaphore.release();
}
},String.valueOf(i)).start();
}
四、线程池
1. 自定义线程池
当线程池最大线程数和阻塞队列都满了,此时会直接执行拒绝策略
当常驻线程和阻塞队列都满了之后,如果再来请求,此次请求则会被优先处理
1. 4种拒绝策略
- AbortPolicy:直接抛出异常,阻止系统正常运行
- CallerRunsPolicy:将任务回退给调用者
- DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入到队列中,并尝试再次提交
- DiscardPolicy:抛弃无法处理的任务
2. 自定义线程池
在实际开发中,一般使用自定义的线程池
// 线程池
public class ThreadPoolUtil {
private static ExecutorService threadPool;
private static int coreNum;
private static int MAX_NUM = 150; //单核cpu最大承载量
static {
// 在项目尚未运行,JVM正在加载类的时候就获取到系统CPU核数
coreNum = Runtime.getRuntime().availableProcessors();
}
public synchronized static ExecutorService getInstance(){
if (threadPool == null){
threadPool = new ThreadPoolExecutor(MAX_NUM,coreNum * MAX_NUM,5,
TimeUnit.SECONDS,new LinkedBlockingQueue<>(50),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardOldestPolicy());
}
return threadPool;
}
}