集合安全问题
List
使用List在多线程的情况下是不安全的里面的方法并没有加锁
public class ListTest {
public static void main(String[] args) {
/**
* 下面三种方法解决
* List<String> list = new Vector<>();
* List<String> list = Collections.synchronizedList(new ArrayList<>());
* List<String> list = new CopyOnWriteArrayList<>();
*/
List<String> list = new ArrayList<>();
// 用10个线程往list中add值,使用普通的ArrayList会报错:java.util.ConcurrentModificationException并发修改异常
for (int i = 0; i < 10; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(list);
}, String.valueOf(i)).start();
}
}
}
1、Vector会在方法中使用synchronized
关键字,效率比较低
public synchronized void addElement(E var1) {
++this.modCount;
this.ensureCapacityHelper(this.elementCount + 1);
this.elementData[this.elementCount++] = var1;
}
2、synchronizedList的许多方法使用synchronized
加锁,但是遍历没有加锁,所以遍历要自己手动同步
public void add(int var1, E var2) {
Object var3 = this.mutex;
synchronized(this.mutex) {
this.list.add(var1, var2);
}
}
3、CopyOnWriteArrayList使用private transient volatile Object[] array;
两个关键字和可重入锁,在写的时候复制,避免数据覆盖
public boolean add(E var1) {
ReentrantLock var2 = this.lock;
var2.lock();
boolean var6;
try {
Object[] var3 = this.getArray();
int var4 = var3.length;
Object[] var5 = Arrays.copyOf(var3, var4 + 1);
var5[var4] = var1;
this.setArray(var5);
var6 = true;
} finally {
var2.unlock();
}
return var6;
}
Set
public class SetTest {
public static void main(String[] args) {
/**
* 下面两种方法解决
* Set<String> set = Collections.synchronizedSet(new HashSet<>());
* Set<String> set = new CopyOnWriteArraySet<>();
*/
Set<String> set = new HashSet<>();
// 用20个线程往set中add值,使用普通的HashSet会报错:java.util.ConcurrentModificationException并发修改异常
for (int i = 0; i < 20; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(set);
}, String.valueOf(i)).start();
}
}
}
HashMap
使用ConcurrentHashMap
Callable创建线程
-
使用Callable创建线程,方法有返回值,可抛出异常
测试
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// Thread和Runnable有关系,Callable要想和Thread扯上关系需要用到Runnable,Runnable有一个实现类FutureTask,FutureTask可以传入Callable对象
MyThread myThread = new MyThread();
FutureTask futureTask = new FutureTask(myThread);
// 有缓存
new Thread(futureTask, "A").start();
new Thread(futureTask, "B").start();
// get方法可能有阻塞,要等call方法执行完
String s = (String) futureTask.get();
// 打印call方法返回值
System.out.println(s);
}
}
class MyThread implements Callable<String>{
@Override
public String call() throws Exception {
System.out.println("耗时方法开始");
TimeUnit.SECONDS.sleep(5);
return "结果";
}
}
CountDownLatch
- 初始化给定线程数
count
countDown
方法使count
减1await
方法会等待count
减到0时,执行后续代码,保证一些线程先完成
测试
public class CountDownTest {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(7);
for (int i = 1; i <= 7; i++) {
// 使lambda表达式可以获取i的值
final int temp = i;
new Thread(()->{
System.out.println(Thread.currentThread().getName() + ":第" + temp + "个龙珠");
// 计数器-1
countDownLatch.countDown();
}, String.valueOf(i)).start();
}
// 等待7减到0
countDownLatch.await();
System.out.println("集齐龙珠");
}
}
CyclicBarrier
- 给定
parties
,当执行完parties
个线程后执行指定线程barrierAction
await
方法,等待其它parties
个线程执行完
测试
public class CyclicBarrierTest {
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 {
// 等待parties+1(计数器+1),如果没加到会阻塞
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}, String.valueOf(i)).start();
}
}
}
Semaphore
可以限制线程的执行数
acquire
阻塞release
释放
测试
public class SemaphoreTest {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(4);
for (int i = 0; i < 8; 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();
}
}
}
线程池
使用线程池的好处
- 降低资源的消耗:可以重复利用已经创建好的线程,减少了线程创建和销毁造成的消耗
- 提高响应速度:线程已经创建好,使用时少去了创建线程的时间消耗
- 提高线程的可管理性:对线程进行统一的管理、调优和监控
三个方法
public class ThreeMethodTest {
public static void main(String[] args) {
// 单一线程的线程池
ExecutorService executorService = Executors.newSingleThreadExecutor();
// 固定线程数的线程池
ExecutorService executorService1 = Executors.newFixedThreadPool(5);
// 自动伸缩的线层池
ExecutorService executorService2 = Executors.newCachedThreadPool();
try {
for (int i = 0; i < 100; i++) {
executorService1.execute(()->{
System.out.println(Thread.currentThread().getName());
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭线程池
executorService1.shutdown();
}
}
}
七个参数
public ThreadPoolExecutor(int var1, int var2, long var3, TimeUnit var5, BlockingQueue<Runnable> var6, ThreadFactory var7, RejectedExecutionHandler var8) {
this.ctl = new AtomicInteger(ctlOf(-536870912, 0));
this.mainLock = new ReentrantLock();
this.workers = new HashSet();
this.termination = this.mainLock.newCondition();
if (var1 >= 0 && var2 > 0 && var2 >= var1 && var3 >= 0L) {
if (var6 != null && var7 != null && var8 != null) {
this.acc = System.getSecurityManager() == null ? null : AccessController.getContext();
this.corePoolSize = var1;
this.maximumPoolSize = var2;
this.workQueue = var6;
this.keepAliveTime = var5.toNanos(var3);
this.threadFactory = var7;
this.handler = var8;
} else {
throw new NullPointerException();
}
} else {
throw new IllegalArgumentException();
}
}
corePoolSize
核心线程池大小maximumPoolSize
最大线程池大小keepAliveTime
非核心线程空闲,存活时间unit
时间单位workQueue
阻塞队列threadFactory
创建线程的线程工厂handler
拒绝策略AbortPolicy
抛弃任务,抛出异常CallerRunsPolicy
拒绝后由提交该任务的线程执行被拒绝的任务DiscardOldestPolicy
抛弃最早任务,重新提交被拒绝的任务DiscardPolicy
抛弃任务,不抛出异常
使用Executors
创建newCachedThreadPool
时最大线程数约为21亿,会造成OOM,所以推荐使用ThreadPoolExecutor
创建线程池
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue());
}
使用ThreadPoolExecutor
创建线程池
public class ThreadPoolTest {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
2, // 核心线程池大小
5, // 最大线程池
5, // 超时时间
TimeUnit.SECONDS, // 超时时间单位
new LinkedBlockingDeque<>(3), // 阻塞队列(容量)
Executors.defaultThreadFactory(), // 默认线程工厂
new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);
try {
// 最大线程数5+3=8
for (int i = 0; i < 8; i++) {
threadPoolExecutor.execute(()->{
System.out.println(Thread.currentThread().getName());
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭线程池
threadPoolExecutor.shutdown();
}
}
}
当用满2个核心线程,接下来的任务会进入阻塞队列容量为3,二者都满了之后还有任务进入,则会启用剩下的线程直到最大线程数,超过之后则会通过拒绝策略采取措施
如何确定maximumPoolSize
的大小:
- CPU密集型:使用
Runtime.getRuntime().availableProcessors()
获取可利用核心数,即CPU的核数 - I/O密集型:判断消耗大量IO资源的任务数,给出至少等于此的最大线程池,一般给两倍