一,基础概念
什么是线程
进程里最小的执行单元,程序里不通的执行路径
线程实现
继承thread
实现runnable
实现callable
用过线程池创建
ExecutorServices=Executors.newCachedThreadPool();
s.execute(()->{System.out.println(Thread.currentThread().getName());});
常用方法
yield 指让出一下CPU
sleep 指休眠固定时间
join 只让固定线程插队执行
线程状态
new
runnable包含两个状态ready running
timedWaiting
t.sleep(500);
o.wait(500);
t.join(500);
LockSupport.parkNanos(500);
LockSupport.parkUntil(200);
waiting
o.wait();
o.notify();
o.notifyAll();
t.join(t1);
LockSupport.park();
LockSupport.unpark(t1);
blocked
线程同步
synchronized,保证数据一致性
方法
锁一个方法
public class T{
privateint count=10;
public synchronized void m(){
count--;
System.out.println(Thread.currentThread().getName()+"count="+count);
}
}
对象加锁
synchronized(this)锁当前对象
public class T{
private int count=10;
private Object o=new Object();
public void m(){
synchronized(o){
count--;
System.out.println(Thread.currentThread().getName()+"count="+count);
}
}
}
synchronized锁升级
偏向锁,对象为32位,前两位记录线程id,有线程进来的话进行判断,有过线程id相同,就继续执行
自旋锁,如果线程id不同就等待,自旋10圈
重量级锁,自旋10圈之后直接请求操作系统
执行时间短,线程数少用自旋锁
执行时间长,线程数多,用系统锁
synchronized同步方法与非同步方法
可以相互调用
synchronized锁重入
都是可重入的,sleep,yield,join
异常与锁
异常发生后释放锁,不影响其他线程
volatile
保证线程间可见,MESI缓存一致性协议
防止指令重排序
AtomicXXX
本身是线程安全的,针对加锁专门设计的
wait notify(面试高频)
等待,唤醒
可以替代为LockSupport的park和unpark
二,JUC同步工具
cas自旋原理
AtomicInteger底层是调用的UnSafe调用的是CompareAndSetI方法
ReentrantLock可重入锁
和syncronized一样的功能,只是在需要加锁的代码前写lock.lock()在finally里面释放锁lock.unlock()
public class T01_ReentrantLock2 {
Lock lock= new ReentrantLock();
void m1(){
try {
lock.lock();//synchronized (this)
for (int i = 0; i < 10; i++) {
SleepUtil.sleepSecond(1);
System.out.println(i);
}
}finally {
lock.unlock();
}
}
void m2(){
try {
lock.lock();
System.out.println("m2...");
}finally {
lock.unlock();
}
}
public static void main(String[] args) {
T01_ReentrantLock2 r1= new T01_ReentrantLock2();
new Thread(r1::m1).start();
SleepUtil.sleepSecond(1);
new Thread(r1::m2).start();
}
}
ReentrantLock有tryLock()方法进行尝试锁定,用返回值判断是否锁定,不管锁定与否,方法继续执行
public class T01_ReentrantLock3 {
Lock lock= new ReentrantLock();
void m1(){
try {
//加锁
lock.lock();//synchronized (this)
for (int i = 0; i < 10; i++) {
SleepUtil.sleepSecond(1);
System.out.println(i);
}
}finally {
//finally解锁
lock.unlock();
}
}
/**
* ReentrantLock比较强大,可以使用tryLock进行尝试锁定,不管锁定与否,方法继续执行,
* 可以根据ReentrantLock返回值来判断是否锁定
* 也可以指定truLock的时间
*/
void m2(){
boolean locked =false;
try {
//尝试获取锁
locked = this.lock.tryLock(5, TimeUnit.SECONDS);
System.out.println("m2..."+locked);
//lock.lockInterruptibly();//可以对interrupt方法做出响应
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//finally解锁
if (locked)lock.unlock();
}
}
public static void main(String[] args) {
T01_ReentrantLock3 r1= new T01_ReentrantLock3();
new Thread(r1::m1).start();
SleepUtil.sleepSecond(1);
new Thread(r1::m2).start();
//t2.interrupt();//打断线程2的等待
}
}
condition条件等待与通知
一个condition等待与通知,与wait(),notify类似
private Lock lock=new ReentrantLock();
private Condition condition=lock.newCondition();
condition.await();
condition.signalAll();
多个condition等待与通知和生产者消费者类似
/**
* 写一个固定容量同步容器,拥有put()get()方法,以及getCount方法,
* 能够支持两个生产者线程以及10个消费者线程阻塞调用
* @param <T>
*/
public class MyContainer2<T> {
final private LinkedList<T> lists= new LinkedList<>(); //初始化容器
final private int MAX=10;//最大容量
private int count=0;//表示当前容量
private Lock lock= new ReentrantLock();
private Condition producer=lock.newCondition();
private Condition consumer=lock.newCondition();
/**
* 生产者
* @param t
*/
public void put(T t){
try {
lock.lock();
while (lists.size()==MAX){//用while的目的是要保证list的size不能为负数
producer.await();//生产者等待
}
lists.add(t);
++count;
consumer.signalAll();//通知消费者线程进行消费
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
/**
* 消费者
* @return
*/
public T get(){
T t= null;
try {
lock.lock();
while (lists.size()==0) {//用while的目的是要保证list的size不能为负数
consumer.await();
}
t=lists.removeFirst();//消费第一个
count--;
producer.signalAll();//通知生产者线程进行生产
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
return t;
}
public static void main(String[] args) {
MyContainer2<String> c= new MyContainer2();
//启动消费者线程
for (int i = 0; i < 10; i++) {
new Thread(()->{
for (int j = 0; j < 5; j++) {
System.out.println(c.get());
}
},"c"+i).start();
}
SleepUtil.sleepSecond(2);
//启动生产者线程
for (int i = 0; i < 2; i++) {
new Thread(()->{
for (int j = 0; j < 25; j++) {
c.put(Thread.currentThread().getName()+" "+j);
}
},"p"+i).start();
}
}
}
CountDownLatch门闩
latch.countDown();主要用于线程间倒数
public class T05_CountDownLatch {
volatile List list = new ArrayList();
// volatile List list = Collections.synchronizedList(new ArrayList<>());
public void add (Object o){
list.add(o);
}
public int size(){
return list.size();
}
public static void main(String[] args) {
T05_CountDownLatch c= new T05_CountDownLatch();
CountDownLatch latch= new CountDownLatch(1);
new Thread(()->{
System.out.println("t2 启动");
if (c.size()!=5){
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("t2 结束");
},"t2").start();
SleepUtil.sleepSecond(1);
new Thread(()->{
for (int i = 0; i < 10; i++) {
c.add(new Object());
System.out.println("add "+i);
if (c.size()==5){
latch.countDown();
//释放锁,让t2执行
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
SleepUtil.sleepMillis(100);
}
},"t1").start();
}
}
CyclicBarrier分批执行,每一批执行一次
/**
* 一个栅栏,循环使用,什么时候人满了,栅栏打开,放大家过关
* CyclicBarrier循环栅栏,一直监听,到达指定容量,就放行
* barrier.await();监听是否到达设置的容量20,够20人就发一次车,不到就一直等着
*/
public class T07_TestCyclicBarrier {
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(20, () -> System.out.println("满人发车"));
for (int i = 0; i < 105; i++) {
new Thread(()->{
try {
barrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
三,同步容器
各个容器的特性
list可以按索引位置取数据
set去重
map键值对存取数据
让容器变得安全(加锁工具类)
Collections.synchronizedList(list);
Collections.synchronizedSet(set);
Collections.synchronizedMap(map);
Queue为高并发设计
![]()
四,线程池
线程池7个参数介绍
ThreadPoolExecutor tpe=new ThreadPoolExecutor(
2,//核心线程数
4,//最大线程数
60,//生存时间
TimeUnit.SECONDS,//生存时间的单位
new ArrayBlockingQueue<Runnable>(4),//任务队列,最多可以装4个任务
Executors.defaultThreadFactory(),//线程工厂,可以指定线程名称
new ThreadPoolExecutor.CallerRunsPolicy()
/**拒绝策略,四种:
*abort,抛异常
*discard,扔掉,不抛异常
*discardOldest,扔掉排队时间最久的
*callerRuns调用者处理服务
*/
);
for (int i = 0; i < 8; i++) {
tpe.execute(new Task(i));
}
//缓存线程池,没有核心线程数,最大线程数可以有好多,60秒没有调用就回收线程,使用默认工厂和拒绝策略,来一个任务我给你启动一个线程,
ExecutorService service = Executors.newCachedThreadPool();
Future<String> submit = service.submit(call);
//单线程线程池,保证扔进去的任务按顺序执行
ExecutorService s = Executors.newSingleThreadExecutor();
s.execute(()->{System.out.println(j+Thread.currentThread().getName());})
//固定大小线程池,可以并行计算,把计算任务分成多个小段执行
ExecutorService service = Executors.newFixedThreadPool(4);
MyTask t1 = new MyTask(1, 80000); //MyTask实现了Callable
Future<List<Integer>> f1 = service.submit(t1);
//定时任务线程池
scheduleAtFixedRate间隔多长时间在一定的频率上来执行这个任务
第一个参数是delay
第二个参数是间隔多长时间period
第三个参数是时间单位timeunit
ScheduledExecutorService service = Executors.newScheduledThreadPool(4);
service.scheduleAtFixedRate(()->{System.out.println(Thread.currentThread().getName());},0,500, TimeUnit.MILLISECONDS);
MyTask t1 = new MyTask(1, 80000);
Future<List<Integer>> f1 = service.submit(t1);
ExecutorService service = Executors.newWorkStealingPool();
service.execute(new R(1000));
ForkJoinPool
public class T12_ForkJoinPool {
static int[] nums = new int[1000000];
static final int MAX_NUM = 50000;
static Random r = new Random();
static {
for(int i=0; i<nums.length; i++) {
nums[i] = r.nextInt(100);
}
System.out.println("---" + Arrays.stream(nums).sum()); //stream api
}
static class AddTask extends RecursiveAction {
int start, end;
public AddTask(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected void compute() {
if (end-start<=MAX_NUM){
long sum=0L;
for (int i = start ; i <end ; i++) {
sum+=nums[i];
}
System.out.println("from "+start+" to: "+end+" = "+sum);
}else {
int middle = start+(end-start)/2;
AddTask addTask1= new AddTask(start, middle);
AddTask addTask2= new AddTask(middle, end);
addTask1.fork();
addTask2.fork();
}
}
}
static class AddTaskRet extends RecursiveTask<Long> {
private static final long serialVersionUID = 1L;
int start, end;
public AddTaskRet(int start, int end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
if (end-start<=MAX_NUM){
long sum=0L;
for (int i = start ; i <end ; i++) {
sum+=nums[i];
}
return sum;
}
int middle = start+(end-start)/2;
AddTaskRet addTask1= new AddTaskRet(start, middle);
AddTaskRet addTask2= new AddTaskRet(middle, end);
addTask1.fork();
addTask2.fork();
return addTask1.join()+addTask2.join();
}
}
public static void main(String[] args) {
T12_ForkJoinPool temp = new T12_ForkJoinPool();
ForkJoinPool fjp = new ForkJoinPool();
AddTaskRet task= new AddTaskRet(0, nums.length);
fjp.execute(task);
Long result = task.join();
System.out.println(result);
}
}
线程池execute和submit用法
1.execute提交的是Runnable类型的任务,而submit提交的是Callable或者Runnable类型的任务
2.execute的提交没有返回值,而submit的提交会返回一个Future类型的对象
3.execute提交的时候,如果有异常,就会直接抛出异常,而submit在遇到异常的时候,通常不会立马抛出异常,而是会将异常暂时存储起来,等待你调用Future.get()方法的时候,才会抛出异常
五,JMH(Java Microbenchmark Harness)
创建JMH测试
新建maven项目,引入依赖
<dependencies>
<!-- https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-core -->
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>1.21</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-generator-annprocess -->
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>1.21</version>
<scope>test</scope>
</dependency>
</dependencies>
idea安装JMH插件 JMH plugin v1.0.3
由于用到了注解,打开运行程序注解配置:compiler -> Annotation Processors -> Enable Annotation Processing
定义需要测试类PS (ParallelStream)
public class PS {
static List<Integer> nums = new ArrayList<>();
static {
Random r = new Random();
for (int i = 0; i < 10000; i++) nums.add(1000000 + r.nextInt(1000000));
}
static void foreach() {
nums.forEach(v->isPrime(v));
}
static void parallel() {
nums.parallelStream().forEach(PS::isPrime);
}
static boolean isPrime(int num) {
for(int i=2; i<=num/2; i++) {
if(num % i == 0) return false;
}
return true;
}
}
写单元测试(在test下,与ps类同路径)
public class PSTest {
@Benchmark
public void testForEach() {
PS.foreach();
}
}
运行测试类,如果遇到下面的错误: Exception while trying to acquire the JMH lock (C:\WINDOWS\/jmh.lock): C:\WINDOWS\jmh.lock (拒绝访问。), exiting. Use -Djmh.ignoreLock=true to forcefully continue.
这个错误是因为JMH运行需要访问系统的TMP目录,解决办法是:
打开RunConfiguration -> Environment Variables -> include system environment viables
阅读测试报告
1. Warmup 预热,由于JVM中对于特定代码会存在优化(本地化),预热对于测试结果很重要
2. Mesurement 总共执行多少次测试
3. Timeout
4. Threads 线程数,由fork指定
5. Benchmark mode 基准测试的模式
6. Benchmark 测试哪一段代码
更多例子:http://hg.openjdk.java.net/code-tools/jmh/file/tip/jmh-samples/src/main/java/org/openjdk/jmh/samples/
六,Disruptor
Disruptor的特点
对比ConcurrentLinkedQueue : 链表实现
JDK中没有ConcurrentArrayQueue
Disruptor是数组实现的
无锁,高并发,使用环形Buffer,直接覆盖(不用清除)旧的数据,降低GC频率
实现了基于事件的生产者消费者模式(观察者模式)
RingBuffer
环形队列
RingBuffer的序号,指向下一个可用的元素
采用数组实现,没有首尾指针
对比ConcurrentLinkedQueue,用数组实现的速度更快
假如长度为8,当添加到第12个元素的时候在哪个序号上呢?用12%8决定
当Buffer被填满的时候到底是覆盖还是等待,由Producer决定
长度设为2的n次幂,利于二进制计算,例如:12%8 = 12 & (8 - 1) pos = num & (size -1)
Disruptor开发步骤
1. 定义Event - 队列中需要处理的元素
2. 定义Event工厂,用于填充队列
这里牵扯到效率问题:disruptor初始化的时候,会调用Event工厂,对ringBuffer进行内存的提前分配
GC产频率会降低
3. 定义EventHandler(消费者),处理容器中的元素
源码地址:
https://github.com/koukay/JUC.git
https://github.com/koukay/TestJMH.git