狂神说JUC
线程和进程
并发与并行线程状态wait/sleep synchronizedlock虚假唤醒(notify All())condition8锁死锁的必要条件callable辅助类(高并发必会)
1.CountDownLatch 减法计数器2.CyclickBarrier 栅栏3.Semaphore 信号量 锁升级读写锁 **ReadWriteLock**阻塞队列
1.BlockQueue2.SynchronousQueue 线程池(重点)
1)线程池:好处2)线程池:三大方法3)线程池:七大参数4)线程池:四大拒绝策略5)如何设置线程池最大核心线程池大小 函数式接口
1)Function 函数型接口
线程和进程进程是操作系统是资源分配的基本单位,线程是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 | 不释放 | 任何地方 | 需要 |
内置关键字,重量级锁,异常自动释放
lock//lock三部曲
//1、 Lock lock=new ReentrantLock(); -->默认非公平锁
//2、 lock.lock() 加锁
//3、 finally=> 解锁:lock.unlock();
虚假唤醒(notify All())用if判断的话,唤醒后线程会从wait之后的代码开始运行,导致虚假唤醒
conditioncondition.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()+":调用方法三");
}
}
死锁的必要条件
callable 1、互斥: 某种资源一次只允许一个进程访问,即该资源一旦分配给某个进程,其他进程就不能再访问,直到该进程访问结束。
2、占有且等待: 一个进程本身占有资源(一种或多种),同时还有资源未得到满足,正在等待其他进程释放该资源。
3、不可抢占: 别人已经占有了某项资源,你不能因为自己也需要该资源,就去把别人的资源抢过来。
4、循环等待: 存在一个进程链,使得每个进程都占有下一个进程所需的至少一种资源。
- 可以有返回值;可以抛出异常;方法不同,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()).start();
//new Thread(new FutureTask( 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 {
@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.BlockQueueBlockingQueue 有四组api
| 方式 | 抛出异常 | 不会抛出异常,有返回值 | 阻塞(队列满/空) | 超时等待 |
|---|---|---|---|---|
| 添加 | add | offer | put | offer(timenum.timeUnit) |
| 移出 | remove | poll | take | poll(timenum,timeUnit) |
| 判断队首元素 | element | peek | - | - |
进去一个元素,必须等待取出来之后,才能再往里面放入一个元素;
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 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)线程池:四大拒绝策略5)如何设置线程池最大核心线程池大小
CPU密集型 :CPU核数=maximumPoolSizeIO密集型 :大型IO任务数 * 2 = maximumPoolSize
函数式接口
1)Function 函数型接口 Function Predicate 参数T,返回boolean Supplier 没有参数,返回T Consumer 参数T,没有返回 ForkJoinTask 表示一个任务,ForkJoinTask 的子类中有 RecursiveAction 和 RecursiveTask。 RecursiveAction 无返回结果RecursiveTask 有返回结果
重写 RecursiveAction 或 RecursiveTask 的 compute(),完成计算或者可以进行任务拆分 调用 ForkJoinTask 的 fork() 的方法,可以让其他空闲的线程执行这个 ForkJoinTask 使用方法 实现就是创建新线程处理事务 CompletableFuture 2)有返回值的supplyAsync异步回调 Volatile 是 Java 虚拟机提供 轻量级的同步机制 1、保证可见性 如何实现可见性 volatile变量修饰的共享变量在进行写操作的时候回多出一行汇编: 0x01a3de1d:movb $0×0,0×1104800(%esi);0x01a3de24**:lock** addl $0×0,(%esp); Lock前缀的指令在多核处理器下会引发两件事情。 将当前处理器缓存行的数据写回到系统内存。这个写回内存的操作会使其他cpu里缓存了该内存地址的数据无效。
intel手册对lock前缀的说明如下: 带有lock前缀的指令在执行期间会锁住总线,使得其它处理器暂时无法通过总线访问内存,很显然,这个开销很大。在新的处理器中,Intel使用缓存锁定来保证指令执行的原子性,缓存锁定将大大降低lock前缀指令的执行开销。禁止该指令与前面和后面的读写指令重排序。把写缓冲区的所有数据刷新到内存中。
多处理器总线嗅探: 为了提高处理速度,处理器不直接和内存进行通信,而是先将系统内存的数据读到内部缓存后再进行操作,但操作不知道何时会写到内存。如果对声明了volatile的变量进行写操作,JVM就会向处理器发送一条lock前缀的指令,将这个变量所在缓存行的数据写回到系统内存。但是在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己的缓存值是不是过期了,如果处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据库读到处理器缓存中。 JMM:JAVA内存模型,不存在的东西,是一个概念,也是一个约定! 关于JMM的一些同步的约定: 1、线程解锁前,必须把共享变量立刻刷回主存; 2、线程加锁前,必须读取主存中的最新值到工作内存中; 3、加锁和解锁是同一把锁; 线程中分为 工作内存、主内存 8种操作: 读->加载->使用->赋值(assign)->写->存储 加锁、解锁 内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可再分的且必须成对出现 使用java.util.concurrent.atomic(使用CAS) 加锁(lock,sychronized) 那么你知道在哪里用这个内存屏障用得最多呢?双重检锁单例模式 防止 instance = new Singleton() 这行语句中,赋值在实例创建之前执行 饿汉式 懒汉式 双重检锁 静态内部类 枚举 自己找视频看,不是重点 测试 t2进程必须等待t1进程Unlock后,才能Unlock,在这之前进行自旋等待。。。。 1、使用jps定位进程号,jdk的bin目录下: 有一个jps 命令:jps -l 2、使用jstack 进程号,找到死锁问题 面试:工作中排查问题: 整理自JUC并发编程笔记(狂神说) - 你我不在年少 - 博客园 (cnblogs.com)
调用 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;
public class TestForkJoinPool {
public static void main(String[] args) throws Exception {
testHasResultTask();//测试使用 ForkJoinPool 有返回值的任务执行,对结果进行合并。计算 1 到 200 的累加和
}
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
异步回调
public class Demo01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// completableFuture.get(); // 获取阻塞执行结果
CompletableFuture
JMM (java内存模型)
1)对Volatile 的理解
2、不保证原子性
3、禁止指令重排
反射破坏单例模式
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;
public class SpinlockDemo {
//初始: int -> 0; 引用类型 Thread -> null
AtomicReference
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();
}
}
查看日志堆栈信息



