一个简单的demo
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
@Slf4j
public class CountExample1 {
// 请求总数
public static int clientTotal = 5000;
// 同时并发执行的线程数
public static int threadTotal = 200;
public static int count = 0;
public static void main(String[] args) throws Exception {
// 线程池
ExecutorService executorService = Executors.newCachedThreadPool();
// 200个线程并发
final Semaphore semaphore = new Semaphore(threadTotal);
// 线程技术,await时线程处于等待状态直到countDownLatch 为0
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal ; i++) {
executorService.execute(() -> {
try {
semaphore.acquire();
add();
semaphore.release();
} catch (Exception e) {
log.error("exception", e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("count:{}", count);
}
private static void add() {
count++;
}
}
以上代码用于测试线程并发,不了解的类就去百度下。
实现线程安全的几种方式
1、线程原子类Automic
automic一个更为高效的方式CAS (compare and swap) ,避免了synchronized的高开销,执行效率大为提升;适用于并发不是很大的场景
2、synchronize 线程加锁
线程解锁前,必须把共享变量的最新值刷新到主内存中
线程加锁时, 必须将共享变量清空,从主内存中获取最新的值
3、 volatile 可见性
保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
volatile 变量的内存可见性是基于内存屏障(Memory Barrier)实现
注意:在对volatile 变量进行操作时不能保证线程时安全的
适用于做线程通信的状态码
线程安全策略
1、不可变对象
- 挡final 修饰变量是,一旦初始化就不能被修改; 修饰对象时,对象的属性可以修改,但对象的引用不可变
- 集合对象可以用Collections.unmodifiableXXXX包装一下变为不可变对象
2、线程封闭(ThreadLocal)
从接收请求到返回响应所经过的所有程序调用都同属于一个线程。将一些非线程安全的变量以ThreadLocal存放,对象所访问的同一ThreadLocal变量都是当前线程所绑定的。
实验: 当 ThreadLocal set 赋值一个单例对象时, 再取出对对象内部属性进行修改,并不能保证线程对象安全;对于基本数据类型, 可保证线程隔离。(待说明)
线程安全类和写法
1、StringBuilder -> StringBuffer
- StringBuilder 线程不安全的
- StringBuffer 线程安全的, 每个方法都加有synchronized 锁
2、SimpleDateFormat -> joda-time
- SimpleDateFormat 当多线程使用同一个对象时会报错,
- 解决办法:声明一个局部的对象 joda-time线程安全的,有空多了解下
3、HashSet、HashSet、ArrayList 线程不安全的
- ArrayList -> Vector,Stack
- HashMap -> HashTable(key、value 不能为null)
- Collections.synchronizedXXX(List,Set,Map)
- 同步容器,影响到性能
4、并发容器 J.U.C.
- 实现了List接口
- 内部持有一个ReentrantLock lock = new ReentrantLock();
- 底层是用volatile transient声明的数组 array
- 读写分离,写时复制出一个新的数组,完成插入、修改或者移除操作后将新数组赋值给array
- ArrayList -> CopyOnWriteArrayList
- HashSet、TreeSet -> CopyOnWriteArraySet、ConcurrentSkipListSet
- HashMap、TreeMap -> ConcurrentHashMap、ConcurrentSkipListMap
J.U.C.并发编程
AQS 抽象队列化同步器(Abustact Queued Synchronizer )
1、CountDownLatch**
可用于线程计数或线程状态监控
例如: 子线程任务执行完了之后执行主线程任务
注意: 在线程中最好放在final中
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
countDownLatch.countDown();
countDownLatch.await();
2、Semaphore 信号量
final Semaphore semaphore = new Semaphore(threadTotal);
semaphore.acquire();
semaphore.release();
用于控制线程的并行, 例如线程池
3、CyclicBarrier
描述: 描述所有线程被栅栏挡住了,当都达到时,一起跳过栅栏执行,也算形象。我们可以把这个状态就叫做barrier
代码:
@Log4j2
public class CyclicBarrierDemo {
private static CyclicBarrier cyclicBarrier = new CyclicBarrier(5);
public static void main(String[] args) throws Exception {
ExecutorService executors = Executors.newCachedThreadPool();
for(int i =0 ;i<10;i++){
Thread.sleep(1000);
final int index = i;
executors.execute(()->{
try {
show(index);
} catch (Exception e) {
e.printStackTrace();
}
});
}
}
public static void show(int i) throws BrokenBarrierException, InterruptedException {
log.info("正在集合,{}:已经到达",i);
cyclicBarrier.await();
log.info("集合完毕,{}:开始跑了",i);
}
}
输出:
22:53:40.636 [pool-1-thread-1] INFO c.r.p.c.J.CyclicBarrierDemo - [show,34] - 正在集合,0:已经到达
22:53:41.622 [pool-1-thread-2] INFO c.r.p.c.J.CyclicBarrierDemo - [show,34] - 正在集合,1:已经到达
22:53:42.622 [pool-1-thread-3] INFO c.r.p.c.J.CyclicBarrierDemo - [show,34] - 正在集合,2:已经到达
22:53:43.623 [pool-1-thread-4] INFO c.r.p.c.J.CyclicBarrierDemo - [show,34] - 正在集合,3:已经到达
22:53:44.625 [pool-1-thread-5] INFO c.r.p.c.J.CyclicBarrierDemo - [show,34] - 正在集合,4:已经到达
22:53:44.626 [pool-1-thread-4] INFO c.r.p.c.J.CyclicBarrierDemo - [show,36] - 集合完毕,3:开始跑了
22:53:44.626 [pool-1-thread-3] INFO c.r.p.c.J.CyclicBarrierDemo - [show,36] - 集合完毕,2:开始跑了
22:53:44.626 [pool-1-thread-2] INFO c.r.p.c.J.CyclicBarrierDemo - [show,36] - 集合完毕,1:开始跑了
22:53:44.626 [pool-1-thread-1] INFO c.r.p.c.J.CyclicBarrierDemo - [show,36] - 集合完毕,0:开始跑了
22:53:44.626 [pool-1-thread-5] INFO c.r.p.c.J.CyclicBarrierDemo - [show,36] - 集合完毕,4:开始跑了
22:53:45.625 [pool-1-thread-5] INFO c.r.p.c.J.CyclicBarrierDemo - [show,34] - 正在集合,5:已经到达
22:53:46.626 [pool-1-thread-1] INFO c.r.p.c.J.CyclicBarrierDemo - [show,34] - 正在集合,6:已经到达
22:53:47.626 [pool-1-thread-3] INFO c.r.p.c.J.CyclicBarrierDemo - [show,34] - 正在集合,7:已经到达
4、ReentrantLock 重入锁
synchronize 是jvm 实现的,是非公平锁,
reentrankLock 是通过JDK 实现的,既有公平锁又有非公平锁
ReentrankLock 优点:
1、既有公平锁又有非公平锁
2、提供一个condition类,可分组唤醒需要唤醒的线程
3、 提供能够中断等待锁的线程机制,lock.lockInterruptibly();
synchronize 优点:
1、不用去关心怎样释放锁
2、因为是jvm实现的, 调试的时候容易找出哪些线程的问题,比如死锁等问题;
5、ReentrantReadWriteLock读写锁
特征:
读锁和写锁不能同时执行,必须等其中一个锁执行完毕之后才能执行;
缺陷: 当读操作特别多的时候,写锁会一直等待
public class ReentrantLockDemo {
private final ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
private final Lock readLock = reentrantReadWriteLock.readLock();
private final Lock writeLock = reentrantReadWriteLock.writeLock();
Map<String,Object> map = new HashMap<>();
public Object setValue(){
readLock.lock();
try {
return "aa";
}finally {
readLock.unlock();
}
}
public void setValue(Object a){
writeLock.lock();
try {
map.put("key",a);
}finally {
writeLock.unlock();
}
}
}
6、StampedLock
J.U.C. 并发组件
1、Future 返回参数的线程
@Log4j2
public class FutureDemo {
static class MyCallable implements Callable<String>{
@Override
public String call() throws Exception {
log.info("正在加载子线程数据");
Thread.sleep(5000);
return "OK";
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
Future<String> future = executorService.submit(new MyCallable());
log.info("加载主线程");
String s = future.get(); // 当子线程在这一步没有执行完毕是,需要等待
log.info("最后的结果:{}",s);
executorService.shutdown();
log.info("结束");
}
}
2、FutureTask
演示源码:
@Log4j2
@RunWith(SpringRunner.class)
public class FutureTaskDemo {
@Test
public void show() throws ExecutionException, InterruptedException {
FutureTask<String> futureTask = new FutureTask<>(()->{
log.info("正在加载子线程数据");
Thread.sleep(5000);
return "OK";
});
new Thread(futureTask).start();
Thread.sleep(1000);
log.info("加载主线程");
String s = futureTask.get(); // 当子线程在这一步没有执行完毕是,需要等待
log.info("最后的结果:{}",s);
log.info("结束");
}
}
并发最佳实现
推荐使用方式
- 宁可同步,也不要使用wait和notify
- 使用BlockingQueue实现生产-消费模式
- 使用并发集合而不是加了锁的同步集合
- 使用semaphore创建有界的资源
- 宁可使用同步代码快也不使用同步方法
- 避免使用静态变量
线程池
1、线程池参数:
corePoolSize : 核心线程数量
maximumPoolSize : 线程最大线程数
workQueue : 阻塞对列,存储等待执行的任务,很重要,会对线程池运行过程产生重大影响
2、poolSize、corePoolSize、maximumPoolSize三者的关系是如何的呢?
当新提交一个任务时:
(1)如果poolSize<corePoolSize,新增加一个线程处理新的任务。
(2)如果poolSize=corePoolSize,新任务会被放入阻塞队列等待。
(3)如果阻塞队列的容量达到上限,且这时poolSize<maximumPoolSize,新增线程来处理任务。
(4)如果阻塞队列满了,且poolSize=maximumPoolSize,那么线程池已经达到极限,会根据饱和策略RejectedExecutionHandler拒绝新的任务。