狂神说JUC
文章目录
线程和进程
进程是操作系统是资源分配的基本单位,线程是CPU调度的最小单位
一个进程往往可以包含多个线程,至少包含一个
对于Java而言:Thread、Runable、Callable进行开启线程的。
Java默认有几个线程?2个线程! main线程、GC线程
thead/runable/callable/线程池,execute()适用于实现Runnable接口创建的线程
提问?JAVA真的可以开启线程吗? 开不了的!
Java是没有权限去开启线程、操作硬件的,这是一个native的一个本地方法,它调用的底层的C++代码。
并发与并行
并发:一个CPU轮次执行任务
并行:多个CPU同时执行任务
获取CPU核数
public class Test1 {
public static void main(String[] args) {
//获取cpu的虚拟核数
System.out.println(Runtime.getRuntime().availableProcessors());
}
}
线程状态
public enum State {
//新生
NEW,
//运行
RUNNABLE,
//阻塞
BLOCKED,
//等待
WAITING,
//超时等待
TIMED_WAITING,
//终止
TERMINATED;
}
操作系统层面线程状态:创建、就绪、运行、阻塞、销毁
wait/sleep
所属类 | 锁释放 | 使用范围 | 异常捕获 | |
---|---|---|---|---|
wait | Thread | 释放 | 同步代码块 | 需要 |
sleep | Object | 不释放 | 任何地方 | 需要 |
synchronized
内置关键字,重量级锁,异常自动释放
lock
//lock三部曲
//1、 Lock lock=new ReentrantLock(); -->默认非公平锁
//2、 lock.lock() 加锁
//3、 finally=> 解锁:lock.unlock();
虚假唤醒(notify All())
用if判断的话,唤醒后线程会从wait之后的代码开始运行,导致虚假唤醒
condition
condition.await() //阻塞这个condition
condition.signal() //唤醒这个condition
// 使用方法
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
8锁
class Fun{
public synchronized void funOne(){ // 锁对象
System.out.println(Thread.currentThread().getName()+":调用方法一");
}
public static synchronized void funTwo(){ // 锁class
System.out.println(Thread.currentThread().getName()+":调用方法二");
}
}
public void funTwo(){ // 不会被claa锁或者对象锁影响
System.out.println(Thread.currentThread().getName()+":调用方法三");
}
}
死锁的必要条件
1、互斥: 某种资源一次只允许一个进程访问,即该资源一旦分配给某个进程,其他进程就不能再访问,直到该进程访问结束。
2、占有且等待: 一个进程本身占有资源(一种或多种),同时还有资源未得到满足,正在等待其他进程释放该资源。
3、不可抢占: 别人已经占有了某项资源,你不能因为自己也需要该资源,就去把别人的资源抢过来。
4、循环等待: 存在一个进程链,使得每个进程都占有下一个进程所需的至少一种资源。
callable
- 可以有返回值;
- 可以抛出异常;
- 方法不同,run()/call()
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//new Thread(new Runnable()).start();
//new Thread(new FutureTask<V>()).start();
//new Thread(new FutureTask<V>( Callable )).start();
MyThread thread = new MyThread();
FutureTask futureTask = new FutureTask(thread);//适配类
new Thread(futureTask,"A").start();
new Thread(futureTask,"B").start();// 结果会被缓存,效率高,结果只打印一次
//获取Callable的返回结果
String o = (String) futureTask.get();//这个get 方法可能会产生阻塞!把他放到最后 或者使用异步通信来处理!
System.out.println(o);
}
}
class MyThread implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("jjjj");
// 耗时的操作
return "hello";
}
}
FutureTask 存在状态概念,
new Thread(futureTask,"A").start();
语句导致FutureTask
不再是new状态,则不会处理
new Thread(futureTask,"A").start()
,所以结果只会打印一次
辅助类(高并发必会)
1.CountDownLatch 减法计数器
保证线程都执行完毕,只能使用一次
主要方法:
//调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
public void await() throws InterruptedException { };
//和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };
//将count值减1
public void countDown() { };
使用示例
public class CountDownLatchTest {
public static void main(String[] args) {
final CountDownLatch latch = new CountDownLatch(2);
System.out.println("主线程开始执行…… ……");
//第一个子线程执行
ExecutorService es1 = Executors.newSingleThreadExecutor();
es1.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000);
System.out.println("子线程:"+Thread.currentThread().getName()+"执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
latch.countDown();
}
});
es1.shutdown();
//第二个子线程执行
ExecutorService es2 = Executors.newSingleThreadExecutor();
es2.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子线程:"+Thread.currentThread().getName()+"执行");
latch.countDown();
}
});
es2.shutdown();
System.out.println("等待两个线程执行完毕…… ……");
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("两个子线程都执行完毕,继续执行主线程");
}
}
结果
主线程开始执行…… ……
等待两个线程执行完毕…… ……
子线程:pool-1-thread-1执行
子线程:pool-2-thread-1执行
两个子线程都执行完毕,继续执行主线程
2.CyclickBarrier 栅栏
所有线程都到达,然后继续执行,计数器递增,提供reset功能,可以多次使用
使用await() 阻塞线程,到达count数量一起放行
3.Semaphore 信号量
控制最大线程数
semaphore.acquire(); //获得,假设如果已经满了,等待,等待被释放为止!
semaphore.release(); //释放,会将当前的信号量释放 + 1,然后唤醒等待的线程!
锁升级
读写锁 ReadWriteLock
读锁是共享锁
写锁是互斥锁,写的时候不能读
阻塞队列
1.BlockQueue
BlockingQueue 有四组api
方式 | 抛出异常 | 不会抛出异常,有返回值 | 阻塞(队列满/空) | 超时等待 |
---|---|---|---|---|
添加 | add | offer | put | offer(timenum.timeUnit) |
移出 | remove | poll | take | poll(timenum,timeUnit) |
判断队首元素 | element | peek | - | - |
2.SynchronousQueue
进去一个元素,必须等待取出来之后,才能再往里面放入一个元素;
SynchronousQueue 的take是使用了lock锁保证线程安全的
线程池(重点)
必会: 线程池:三大方法、7大参数、4种拒绝策略
池化技术
程序的运行,本质:占用系统的资源!我们需要去优化资源的使用 ===> 池化技术
线程池、JDBC的连接池、内存池、对象池 等等。。。。
资源的创建、销毁十分消耗资源
池化技术:事先准备好一些资源,如果有人要用,就来我这里拿,用完之后还给我,以此来提高效率。
1)线程池:好处
- 线程复用
- 控制最大并发数
- 管理线程
2)线程池:三大方法
-
ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程
-
ExecutorService threadPool2 = Executors.newFixedThreadPool(5); //创建一个固定的线程池的大小
-
ExecutorService threadPool3 = Executors.newCachedThreadPool(); //可伸缩的
3)线程池:七大参数
// 本质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;
}
核心线程都在运行且阻塞队列满了才会增加线程
4)线程池:四大拒绝策略
/**
* new ThreadPoolExecutor.AbortPolicy() // 银行满了,还有人进来,不处理这个人的,抛出异常
* new ThreadPoolExecutor.CallerRunsPolicy() // 哪来的去哪里!
* new ThreadPoolExecutor.DiscardPolicy() //队列满了,丢掉任务,不会抛出异常!
* new ThreadPoolExecutor.DiscardOldestPolicy() //队列满了,尝试去和最早的竞争,也不会抛出异常!
*/
5)如何设置线程池最大核心线程池大小
- CPU密集型 :CPU核数=maximumPoolSize
- IO密集型 :大型IO任务数 * 2 = maximumPoolSize
函数式接口
1)Function 函数型接口 <T, V>
Function <T, V> 参数T,返回V
2)Predicate 断定型接口
Predicate 参数T,返回boolean
3)Supplier 供给型接口
Supplier 没有参数,返回T
4)Consumer 消费型接口
Consumer 参数T,没有返回
ForkJoinTask 表示一个任务,ForkJoinTask 的子类中有 RecursiveAction 和 RecursiveTask。
- RecursiveAction 无返回结果
- RecursiveTask 有返回结果
重写 RecursiveAction 或 RecursiveTask 的 compute(),完成计算或者可以进行任务拆分
调用 ForkJoinTask 的 fork() 的方法,可以让其他空闲的线程执行这个 ForkJoinTask
调用 ForkJoinTask 的 join() 的方法,将多个小任务的结果进行汇总
使用方法
package constxiong.interview;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
import java.util.concurrent.TimeUnit;
/**
* 测试 ForkJoinPool 线程池的使用
* @author ConstXiong
* @date 2019-06-12 12:05:55
*/
public class TestForkJoinPool {
public static void main(String[] args) throws Exception {
testHasResultTask();//测试使用 ForkJoinPool 有返回值的任务执行,对结果进行合并。计算 1 到 200 的累加和
}
/**
* 测试使用 ForkJoinPool 有返回值的任务执行,对结果进行合并。计算 1 到 200 的累加和
* @throws Exception
*/
public static void testHasResultTask() throws Exception {
int result1 = 0;
for (int i = 1; i <= 200; i++) {
result1 += i;
}
System.out.println("循环计算 1-200 累加值:" + result1);
ForkJoinPool pool = new ForkJoinPool();
ForkJoinTask<Integer> task = pool.submit(new CalculateTask(1, 200));
int result2 = task.get();
System.out.println("并行计算 1-200 累加值:" + result2);
pool.awaitTermination(2, TimeUnit.SECONDS);
pool.shutdown();
}
}
/**
* 有返回值的计算任务
* @author ConstXiong
* @date 2019-06-12 12:07:25
*/
class CalculateTask extends RecursiveTask<Integer> {
private static final long serialVersionUID = 1L;
private static final int THRESHOLD = 49;
private int start;
private int end;
public CalculateTask(int start, int end) {
super();
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
//当结束值比起始值 大于 49 时,按数值区间平均拆分为两个任务,进行两个任务的累加值汇总;否则直接计算累加值
if (end - start <= THRESHOLD) {
int result = 0;
for (int i = start; i <= end; i++) {
result += i;
}
return result;
} else {
int middle = (start + end) / 2;
CalculateTask firstTask = new CalculateTask(start, middle);
CalculateTask secondTask = new CalculateTask(middle + 1, end);
firstTask.fork();
secondTask.fork();
return firstTask.join() + secondTask.join();
}
}
}
异步回调
实现就是创建新线程处理事务
CompletableFuture
1)没有返回值的runAsync异步回调
2)有返回值的supplyAsync异步回调
public class Demo01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// completableFuture.get(); // 获取阻塞执行结果
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + "supplyAsync=>Integer");
// int i = 10 / 0;
return 1024;
});
System.out.println(completableFuture.whenComplete((t, u) -> {
// t正常的返回结果 u 错误信息
}).exceptionally((e) -> {// 可以获取到错误的返回结果
}).get());
}
}
JMM (java内存模型)
1)对Volatile 的理解
Volatile 是 Java 虚拟机提供 轻量级的同步机制
1、保证可见性
2、不保证原子性
3、禁止指令重排
如何实现可见性
volatile变量修饰的共享变量在进行写操作的时候回多出一行汇编:
0x01a3de1d:movb $0×0,0×1104800(%esi);0x01a3de24**:lock** addl $0×0,(%esp);
Lock前缀的指令在多核处理器下会引发两件事情。
- 将当前处理器缓存行的数据写回到系统内存。
- 这个写回内存的操作会使其他cpu里缓存了该内存地址的数据无效。
intel手册对lock前缀的说明如下:
- 带有lock前缀的指令在执行期间会锁住总线,使得其它处理器暂时无法通过总线访问内存,很显然,这个开销很大。在新的处理器中,Intel使用缓存锁定来保证指令执行的原子性,缓存锁定将大大降低lock前缀指令的执行开销。
- 禁止该指令与前面和后面的读写指令重排序。
- 把写缓冲区的所有数据刷新到内存中。
多处理器总线嗅探:
为了提高处理速度,处理器不直接和内存进行通信,而是先将系统内存的数据读到内部缓存后再进行操作,但操作不知道何时会写到内存。如果对声明了volatile的变量进行写操作,JVM就会向处理器发送一条lock前缀的指令,将这个变量所在缓存行的数据写回到系统内存。但是在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己的缓存值是不是过期了,如果处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据库读到处理器缓存中。
2)什么是JMM?
JMM:JAVA内存模型,不存在的东西,是一个概念,也是一个约定!
关于JMM的一些同步的约定:
1、线程解锁前,必须把共享变量立刻刷回主存;
2、线程加锁前,必须读取主存中的最新值到工作内存中;
3、加锁和解锁是同一把锁;
线程中分为 工作内存、主内存
8种操作:
读->加载->使用->赋值(assign)->写->存储
加锁、解锁
内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可再分的且必须成对出现
3)如何保证原子性
-
使用java.util.concurrent.atomic(使用CAS)
-
加锁(lock,sychronized)
那么你知道在哪里用这个内存屏障用得最多呢?双重检锁单例模式 防止 instance = new Singleton() 这行语句中,赋值在实例创建之前执行
单例模式
-
饿汉式
-
懒汉式
-
双重检锁
-
public class SingleTon{ private SingleTon(){} private static class SingleTonHoler{ private static SingleTon INSTANCE = new SingleTon(); } public static SingleTon getInstance(){ return SingleTonHoler.INSTANCE; } }
-
枚举
public enum SingleTon{ INSTANCE; public void method(){ //TODO } }
反射破坏单例模式
自己找视频看,不是重点
自定义自旋锁
package com.zzy.lock;
import java.util.concurrent.atomic.AtomicReference;
/**
* 自旋锁 //AtomicStampedReference 可带版本号的
*/
public class SpinlockDemo {
//初始: int -> 0; 引用类型 Thread -> null
AtomicReference<Thread> atomicReference = new AtomicReference<>();
// 加锁
public void myLock() {
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + "==>myLock");
//自旋锁
while (!atomicReference.compareAndSet(null, thread)) { // 对比and交换, 很巧妙
}
}
// 解锁
public void myUnlock() {
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + "==>myUnlock");
//自旋锁
atomicReference.compareAndSet(thread, null);
}
}
测试
public class TestSpinLock {
public static void main(String[] args) {
// 底层使用的自旋锁CAS
SpinlockDemo lock = new SpinlockDemo();
new Thread(()->{
lock.myLock();
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.myUnlock();
}
},"T1").start();
new Thread(()->{
lock.myLock();
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.myUnlock();
}
},"T2").start();
}
}
t2进程必须等待t1进程Unlock后,才能Unlock,在这之前进行自旋等待。。。。
如何解开死锁
1、使用jps定位进程号,jdk的bin目录下: 有一个jps
命令:jps -l
2、使用jstack 进程号
,找到死锁问题
面试:工作中排查问题:
- 查看日志
- 堆栈信息