1. ArrayList 多线程不安全
在多条线程下,对同一个ArrayList执行add方法时,可能会报java.util.ConcurrentModificationException
,即为并发修改时错误。解决方法有3种:
- List list = new Vector<>();
- List list = Collections.synchronizedList(new ArrayList<>());
- List list = new CopyOnWriteArrayList<>();
面试问题:使用CopyOnWriteArrayList,不使用Vector的原因?
Vector 使用的是 synchronizeid 效率比较低
CopyOnWriteArrayList 使用的是 lock锁 效率比较高
package com.mango.unsafe;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
public class Test1 {
public static void main(String[] args) {
List<Object> list = Collections.synchronizedList(new ArrayList<>());
for(int i = 0;i < 100;i++){
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
2. Set 多线程不安全
在多条线程下,对同一个HashSet执行add方法时,可能会报java.util.ConcurrentModificationException
,即为并发修改时错误。解决方法有2种:
- Set< String > set = Collections.synchronizedSet(new HashSet<>());
- Set< String > set = new CopyOnWriteArraySet<>();
package com.mango.unsafe;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;
public class Test2 {
public static void main(String[] args) {
// HashSet<String> set = new HashSet<>();
// Set<String> set = Collections.synchronizedSet(new HashSet<>());
Set<String> set = new CopyOnWriteArraySet<>();
for (int i = 0; i < 10 ; i++){
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
3. 关于Map
在多条线程下,对同一个HashMap执行put方法时,可能会报java.util.ConcurrentModificationException
,即为并发修改时错误。解决方法有2种:
- Set< String > set = Map<Object, Object> map = Collections.synchronizedMap(new HashMap<>());
- Set< String > set = new ConcurrentHashMap<>();
package com.mango.unsafe;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class Test3 {
public static void main(String[] args) {
// map 是这样的嘛?不是,工作中不用HashMap
// 默认等价于什么? new HashMap<>(16,0.75);
Map<Object, Object> map = new ConcurrentHashMap<>();
// Map<Object, Object> map = Collections.synchronizedMap(new HashMap<>());
// 加载因子,初始化容量
for (int i = 0; i < 30; i++) {
new Thread(()->{
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
System.out.println(map);
},String.valueOf(i)).start();
}
}
}
4. 关于Callable
- 可以有返回值。
- 可以抛出异常。
- 方法不同。run() / call()
package com.mango.callable;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> stringFutureTask = new FutureTask<String>(new MyThread());
new Thread(stringFutureTask,"A").start(); // 怎么启动callable
String s = stringFutureTask.get();
System.out.println(s);
}
}
class MyThread implements Callable<String>{
@Override
public String call() throws Exception {
System.out.println("call()");
return "1024";
}
}
new Thread无法直接执行Callable 所以需要一个Runnable的父级FutureTask,作为一个适配器,这个类需要一个Callable实现类作为参数。
5. 常用的辅助类
5.1 CountDownLatch
countDownLatch.countDown();
计数器 -1。
countDownLatch.await();
等待计数器归零,然后向下执行。
每次有线程调用countDown()数量-1,假设计数器变为0,countDownLatch.await();就会被唤醒。
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
// 倒计时 总数是6
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 0; i < 6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"GO OUT");
countDownLatch.countDown(); // 数量-1
},String.valueOf(i)).start();
}
countDownLatch.await(); // 等待计数器归零,然后再向下执行
System.out.println(1);
}
}
5.2 CyclicBarrier
加法计数器
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
System.out.println("召唤神龙成功");
});
for (int i = 1; i <= 7; i++) {
final 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();
}
}).start();
}
}
}
5.3 Semaphore
semaphore.acquire();
获得,假设已经满了,等待,等到被释放为止!
semaphore.release();
释放,会将当前的信号量+1,然后唤醒等待线程!
作用:多个共享资源互斥的作用!并发限流,控制最大的线程数量!
public class SemaphoreDemo {
public static void main(String[] args) {
// 线程数量:停车位!限流
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < 6; i++) {
new Thread(()->{
// semaphore.acquire();
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"抢到车位");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+"离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
}
// semaphore.release(); 释放
},String.valueOf(i)).start();
}
}
}
6. ReentrantReadWriteLock
独占锁(写锁):一次只能被一个线程占有
共享锁(读锁):多个线程可以同时占有
读-读 可以共存!
读-写 不能共存!
写-写 不能共存!
ReentrantReadWriteLock 可以使写的时候只能一条线程写,读的时候一起读,如果使用lock,读的时候也只能一个一个线程读。
package com.mango.rw;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class Test {
public static void main(String[] args) {
MyCache2 myCache = new MyCache2();
// 写入
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{
private volatile Map<String,Object> map = new HashMap<>();
// 存,写
public void put(String key,Object value){
System.out.println(Thread.currentThread().getName()+"写入"+key);
map.put(key,value);
System.out.println(Thread.currentThread().getName()+"结束");
}
// 取,读
public void get(String key){
System.out.println(Thread.currentThread().getName()+"读取"+key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName()+"读取结束");
}
}
// 加锁
class MyCache2{
private volatile Map<String,Object> map = new HashMap<>();
// 读写锁:更加细粒度的控制
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
// 存,写,写入的时候,只希望同时只有一个线程写
public void put(String key,Object value){
lock.writeLock().lock();
System.out.println(Thread.currentThread().getName()+"写入"+key);
map.put(key,value);
System.out.println(Thread.currentThread().getName()+"结束");
lock.writeLock().unlock();
}
// 取,读,所有人都可以读
public void get(String key){
lock.readLock().lock();
System.out.println(Thread.currentThread().getName()+"读取"+key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName()+"读取结束");
lock.readLock().unlock();
}
}
7. 阻塞队列
7.1 阻塞
写入:如果队列满了,就必须阻塞等待。
7.2 队列
取:如果队列是用空,必须阻塞等待生产。
7.3 阻塞队列
是Collection的子类,和List、Set同级。
BlockingQueue不是新的东西。
什么情况下我们会使用阻塞队列:多线程并发处理、线程池!
7.4 四组API
(1)ArrayBlockingQueue
方式 | 抛出异常 | 有返回值,不抛出异常 | 阻塞等待 | 超时等待 |
---|---|---|---|---|
添加 | add | offer | put | offer( , , ) |
移除 | remove | poll | take | poll( , ) |
判断队首元素 | element | peek | - | - |
两种队列异常:IllegalStateException
和NoSuchElementException
(2)SynchronousQueue
SynchronousQueue 是同步队列,和其他的BlockingQueue不一样,SynchronousQueue 不存储元素,put了一个元素,必须从里面先take取出来。
public class Test02 {
public static void main(String[] args) {
SynchronousQueue<String> blockingQueue = new SynchronousQueue<>(); // 同步队列
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+"put 1");
blockingQueue.put("1");
System.out.println(Thread.currentThread().getName()+"put 2");
blockingQueue.put("2");
System.out.println(Thread.currentThread().getName()+"put 3");
blockingQueue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t1").start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + "=>" + blockingQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + "=>" + blockingQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + "=>" + blockingQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t2").start();
}
}
8. 线程池(重点)
三大方法、7大参数、4种拒绝策略
池化奇数
程序的运行,本质:占用系统资源!优化资源的使用!=》池化奇数
线程池、连接池(JDBC)、内存池、对象池…创建、销毁。十分浪费资源
池化技术:实现准备好一些资源,有人要用,就来我这里拿,用完之后还给我,下个人再来用。
好处:
- 降低资源消耗。
- 提高响应的速度。
- 方便管理。
线程复用、可以控制最大并发数、管理线程
8.1 三大方法
Executors 工具类中的三大方法。
(1)Executors.newSingleThreadExecutor();
单个线程。
// 使用线程池之后,使用线程池创建
public class Test1 {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newSingleThreadExecutor();
// Executors.newFixedThreadPool(5);
// Executors.newCachedThreadPool();
try{
for (int i = 0; i < 10; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"ok");
});
}
}catch(Exception ee) {
ee.printStackTrace();
}finally {
threadPool.shutdown();
}
// 线程池用完要关闭
}
}
(2)Executors.newFixedThreadPool(5);
创建一个固定的线程池大小。
// 使用线程池之后,使用线程池创建
public class Test1 {
public static void main(String[] args) {
// ExecutorService threadPool = Executors.newSingleThreadExecutor();
ExecutorService threadPool = Executors.newFixedThreadPool(5);
// Executors.newCachedThreadPool();
try{
for (int i = 0; i < 10; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"ok");
});
}
}catch(Exception ee) {
ee.printStackTrace();
}finally {
threadPool.shutdown();
}
// 线程池用完要关闭
}
}
(3) Executors.newCachedThreadPool();
可伸缩的,遇强则强,遇弱则弱。
// 使用线程池之后,使用线程池创建
public class Test1 {
public static void main(String[] args) {
// ExecutorService threadPool = Executors.newSingleThreadExecutor();
// ExecutorService threadPool = Executors.newFixedThreadPool(5);
ExecutorService threadPool = Executors.newCachedThreadPool();
try{
for (int i = 0; i < 10; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"ok");
});
}
}catch(Exception ee) {
ee.printStackTrace();
}finally {
threadPool.shutdown();
}
// 线程池用完要关闭
}
}
8.2 七大参数
本质:ThreadPoolExecutor ();
public ThreadPoolExecutor(int corePoolSize, // 核心线程池大小
int maximumPoolSize, // 最大线程池大小
long keepAliveTime, // 超时了没有人调用,自动释放
TimeUnit unit, // 超时单位
BlockingQueue<Runnable> workQueue, // 阻塞队列
ThreadFactory threadFactory, // 线程工厂:创建线程,一般不用动
RejectedExecutionHandler handler) { // 拒绝策略
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
8.3 四种拒绝策略
(1) new ThreadPoolExecutor.AbortPolicy()
银行满了 候客区也满了 还有人进来 不处理这个人的,抛出异常 RejectedExecutionException
会报错
// 使用线程池之后,使用线程池创建
public class Test1 {
public static void main(String[] args) {
// 自定义线程池!
ExecutorService threadPool = new ThreadPoolExecutor(
2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
try{
for (int i = 1; i <= 10; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"ok");
});
}
}catch(Exception ee) {
ee.printStackTrace();
}finally {
threadPool.shutdown();
}
// 线程池用完要关闭
}
}
(2) new ThreadPoolExecutor.CallerRunsPolicy()
哪来的去哪里
假如线程池超过了最大值(阻塞队列+最大线程池的值),会回到main方法调用。
不会报错
public class Test1 {
public static void main(String[] args) {
// 自定义线程池!
ExecutorService threadPool = new ThreadPoolExecutor(
2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy() // 哪里来的去哪里
);
try{
for (int i = 1; i <= 10; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"ok");
});
}
}catch(Exception ee) {
ee.printStackTrace();
}finally {
threadPool.shutdown();
}
// 线程池用完要关闭
}
}
(3) new ThreadPoolExecutor.DiscardPolicy()
队列满了不继续执行任务
假如线程池超过了最大值(阻塞队列+最大线程池的值),会回到main方法调用。
不会报错
public class Test1 {
public static void main(String[] args) {
// 自定义线程池!
ExecutorService threadPool = new ThreadPoolExecutor(
2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardPolicy() // 队列满了
);
try{
for (int i = 1; i <= 10; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"ok");
});
}
}catch(Exception ee) {
ee.printStackTrace();
}finally {
threadPool.shutdown();
}
// 线程池用完要关闭
}
}
(4) new ThreadPoolExecutor.DiscardOldestPolicy()
队列满了尝试去和最早竞争
假如线程池超过了最大值(阻塞队列+最大线程池的值),会回到main方法调用。
不会报错
public class Test1 {
public static void main(String[] args) {
// 自定义线程池!
ExecutorService threadPool = new ThreadPoolExecutor(
2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardOldestPolicy() // 队列满了
);
try{
for (int i = 1; i <= 10; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"ok");
});
}
}catch(Exception ee) {
ee.printStackTrace();
}finally {
threadPool.shutdown();
}
// 线程池用完要关闭
}
}
9. 小结
最大线程该如何定义?
9.1 CPU 密集型
几核就是几,可以保持CPU的效率最高!
获取CPU的方法Runtime.getRuntime().availableProcessors()
9.2 IO 密集型
判断你程序中十分耗IO的线程。
程序 15个大型任务 io十分占用资源!