狂神-JUC笔记
### 什么是JUC
是java.util.concurrent包下的内容
### 线程和进程
进程里面包含有多条线程
### Synchronized和Lock区别
1,Synchronized是java的关键字,Lock是接口
2,Synchronized不能判断锁的状态,Lock是可以判断是否获取锁
3,Synchronized不需要手动释放锁,Lock需要手动释放锁
4,Synchronized:非公平锁,Lock默认是非公平锁,可以设置成公平锁
### 生产者和消费者的问题
### 8锁现象
总结:
```java
首先是判断锁的对象是谁,然后看一下是几把锁,如果是一把锁:就是谁先拿到锁谁就先执行,如果是2把锁,就是不公平锁,谁先执行完就是谁
```
### 集合类不安全
List同set集合
```java
/** 并发下ArrayList不安全报错:ConcurrentModificationException
* 解决方案:
* 1. List<String> arrayList =new Vector<>();
* 2. List<String> arrayList = Collections.synchronizedList(new ArrayList<>());
* 3,List<String> arrayList = new CopyOnWriteArrayList<>();
*/
```
Map
```java
* 1,Map<String,String> map = Collections.synchronizedMap(new HashMap<>());
* 2,Map<String,String> map = new ConcurrentHashMap<>();
```
### Callable
是一个接口,和Runable接口不同的是,Callable接口有返回值,有异常,Runable接口么有返回值和异常
```java
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//FutureTask的本质就是Runnable,FutureTask是一个适配器,来适配Callable
FutureTask myThreadFutureTask = new FutureTask(new MyThread());
new Thread(myThreadFutureTask,"A").start();
//这里会有缓存,所以执行结果之会输出一次
new Thread(myThreadFutureTask,"B").start();
//这里通过FutureTask.get()方法可以获取到Callable的返回值
Object o = myThreadFutureTask.get();
System.out.println(o);
}
}
class MyThread implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("Call()");
return 10;
}
}
```
### 常用辅助类
```
* 辅助类:
* countDownLatch 减法计数器
* CyclicBarrier 加法计数器
* Semaphore 相当于信号量 这个是可以用来做并发限流的
```
### 读写锁
ReadWriteLock 读写锁
维护一堆关联的Locks,read lock可以有多个线程同时进行,write lock 只有一个
读锁也叫 共享锁(一次可以多个线程共享)
写锁 也叫独占锁 ,排他锁,(一次只能是一个线程独有)
### 阻塞队列
Queue:队列:阻塞队列(BlockingQueue)、非阻塞队列(AbstractQueue)、双端队列(Deque)
```java
BlockingQueue`常用的实现类:`ArrayBlockingQueue`,`LinkedBlockingQueue`,`SynchronousQueue
```
```
什么情况下会使用阻塞队列:多线程并发处理,线程池
队列是先去先出
```
### 线程池
```
总结:线程可复用,可以控制最大并发数,可以管理线程
```
> 线程池——三大方法
1. `Executors.newSingleThreadExecutor()`单个线程
2. `Executors.newFixedThreadPool(3)`自定义最大线程数
3. `Executors.newCachedThreadPool()`可伸缩的
### 四大函数式接口
lambda表达式、函数式接口、stream、链式编程
只有一个方法的接口,Runable接口,简化编码模型,在新版本的框架大量应用
>四大函数式接口:`Consumer`(消费,只有输入)、`Function`(函数,一个输入一个输出)、`Predicate`(断定,一个输入,返回boole)、`Supplier`(生成,只有输出)
>
>```
>list.forEach();消费者的函数式接口
>```
>
>```java
>一、函数式接口源码: 一个输入参数,一个输出参数
>public interface Function<T, R> {
>R apply(T t);
>
>}
>二、断定型参数 :有一个参数,返回值是布尔类型
> @FunctionalInterface
>public interface Predicate<T> {
>boolean test(T t);
>}
>
>三、生产消费型
>```
### Stream流式计算
大数据:存储+计算
存储:集合、Mysql 本质上存储
计算都应该交给流来操作
Stream接口
```java
public class Demo {
/**
* 一分钟内完成此题,只能用一行代码实现
* 现在有5个用户,筛选:
* 1. ID 必须是偶数
* 2. 年龄必须大于23岁
* 3. 用户名转大写字母
* 4. 用户名字母倒序
* 5. 只输出一个用户!
*/
public static void main(String[] args) {
User u1 = new User(1,"a",21);
User u2 = new User(2,"b",22);
User u3 = new User(3,"c",23);
User u4 = new User(4,"d",24);
User u5 = new User(6,"e",25);
//集合就是存储的
List<User> list = Arrays.asList(u1, u2, u3, u4, u5);
//计算交给流
list.stream()
.filter(u->{return u.getId()%2==0;})
.filter(uu->{return uu.getAge()>=23;})
.map(u->{return u.getName().toUpperCase();})
.sorted((uu1,uu2)->{return uu2.compareTo(uu1);})
.limit(1)
.forEach(System.out::println);
}
}
```
### ForkJoin
在jdk1.7,并行执行任务,提高效率,大数据量
1,分支合并,是将一个大任务,拆分为几个小任务,然后把这些小任务的结果总和起来,就是思想
大事化小,小事化了
2,特点是 工作窃取:
这个里面维护的都是双端队列
```java
public class ForkJoinTest extends RecursiveTask<Long> {
private Long start;
private Long end;
private Long temp=10000L; -- 可以优化
public ForkJoinTest(Long start, Long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
Long sum=0L;
if((end-start<temp)){
//正常计算
for (long i = start; i <=end ; i++) {
sum+=i;
}
return sum;
}else {//分支计算
//1,先去求一下中间数
long middle = (end+start) / 2;
ForkJoinTest forkJoinTest1= new ForkJoinTest(start, middle);
ForkJoinTest forkJoinTest2 = new ForkJoinTest(middle+1, end);
forkJoinTest1.fork();//把任务压入线程队列
forkJoinTest2.fork();
return forkJoinTest1.join()+forkJoinTest2.join();
}
}
}
```
test类
```java
public static void test1() {
Long sum=0L;
long statTime= System.currentTimeMillis();
for (int i = 0; i <=10_0000_0000; i++) {
sum +=i;
}
long endTime= System.currentTimeMillis();
System.out.println("sum="+sum+"时间用了="+(endTime-statTime));
//结果 :sum=500000000500000000时间用了=5603
}
public static void test2() throws ExecutionException, InterruptedException {
long statTime= System.currentTimeMillis();
ForkJoinPool forkJoinPool =new ForkJoinPool();
ForkJoinTask<Long> task = new ForkJoinTest(0L,10_0000_0000L);
//提交任务
ForkJoinTask<Long> submit = forkJoinPool.submit(task);
Long sum = submit.get();
long endTime= System.currentTimeMillis();
System.out.println("sum="+sum+"时间用了="+(endTime-statTime));
//sum=500000000500000000时间用了=4970
}
public static void test3() {
long statTime= System.currentTimeMillis();
//并行流rangeClosed(】 range()包含不包含的意思
long sum = LongStream.rangeClosed(0, 10_0000_0000L).parallel().reduce(0, Long::sum);
long endTime= System.currentTimeMillis();
System.out.println("sum="+sum+"时间用了="+(endTime-statTime));
//sum=500000000500000000时间用了=275
}
}
```
### 异步回调
Futuer:对将来的某个事件的结果进行建模
```java
public static void main(String[] args) throws ExecutionException, InterruptedException {
//1,没有返回的异步
// CompletableFuture<Void>future = CompletableFuture.runAsync(() -> {
// try {
// TimeUnit.SECONDS.sleep(3);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println("异步回调");
//
// });
// System.out.println("我先输出....");
// //这里会阻塞等待结果返回
// future.get();
//2,异步回调,有返回值
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName()+"supplyAsync==>Integer");
int i=10/0;
return 1024;
});
System.out.println(completableFuture.whenComplete((u,t) -> {
//u :这里如果是正确就会是正确的返回值,如果错误就为null
System.out.println("u==>"+u);
//t :如果有错误 这里输出错误 如java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
System.out.println("t==>"+t);
}).exceptionally((e)->{
//如果有错误 这里输出
System.out.println(e.getMessage());
return 222;
}).get());
}
```
### JMM :
java内存模型,约定,如下:
1,线程解锁前,必须把共享变量 立刻 刷回主内存
2,线程加锁前,必须读取主内存的最新值到工作内存中!
3,加锁和解锁必须是同一把锁
线程:工作内存、主内存,执行引擎
8中、4组操作
read(读取)load(加载)|use(使用)、assign(刷新)|write(写入主内存)store(存储)|lock(加锁)、unlock(解锁) 必须成对出现
问题:线程B修改了内容,线程A不能及时可见----引入volatile
### Volatitle
volatile:是java虚拟机提供的 轻量级的同步机制
1,保证可见性
2,不保证原子型,
3,禁止指令重排
保证可见性代码:
```java
public class JMMDemo {
//不加volatile,线程1,不知道内容修改了
private static volatile Integer num=0;
public static void main(String[] args) {
//主线程
new Thread(()->{ //线程1,对主内存的变化不知道
while (num==0){
}
},"A").start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
num=1;
System.out.println(num);
}
}
```
不保证原子性:
```java
public class VTest {
//验证volatile的没有原子型的特点
// private static volatile int num=0;
private static AtomicInteger num =new AtomicInteger(0);
public synchronized static void add(){
num.getAndIncrement();//num++
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
for (int j = 0; j <10000 ; j++) {
add();
}
}).start();
}
//线程数大于2说明没有执行完,多线程下一定不要用if。
while (Thread.activeCount()>2){
Thread.yield();
}
System.out.println("计算结果="+num);
//运行结果 不是每次100000 所以不能保证原子性
//解决方案:
//1,加锁,synchronized或者是Lock,如果不用这个那????
//2,使用的是原子包装类型也能解决这个问题
// 这些类的底层都直接和操作系统挂钩!在内存中修改值,Unsafe类是一个很特殊的存在
// Volatile在单例模式中使用的最多。
}
}
```
防止指令重排
好比你写的代码,计算机并不是按照你写的代码的顺序去执行的。
源代码—->>编译器优化重排—->>指令并行也可能重排—->>内存系统也会重排—->>执行
`volatile`可以避免指令重排
内存屏障,CPU指令。作用:
1、保证特定的操作的执行顺序!
2、可以保证某些变量的内存可见性。(利用这些特性volatile就可以实现可见性)
```
加了volatile关键字的操作,都会在上方和下方形成一个屏障来保证代码的顺序,防止指令重排
```
### 单例模式
饿汉式
```java
public class Hungry {
//浪费内存
private byte[] data1=new byte[1024*1024];
private byte[] data2=new byte[1024*1024];
private byte[] data3=new byte[1024*1024];
private byte[] data4=new byte[1024*1024];
private byte[] data5=new byte[1024*1024];
private final static Hungry hungry=new Hungry();
public static Hungry getInstancd(){
return hungry;
}
private Hungry() {
}
}
```
懒汉式 :产生问题:单线程是没有问题的,但多线程会出现问题
```java
public class LazyMan {
private LazyMan(){
System.out.println(Thread.currentThread().getName()+"ok");
}
private volatile static LazyMan LAZY_MAN ;
public static LazyMan getInstance(){
if (LAZY_MAN==null){
LAZY_MAN=new LazyMan();
}
}
}
return LAZY_MAN;
}
```
双重校验锁(DCL懒汉)
```java
public class LazyMan {
private LazyMan(){
}
private volatile static LazyMan LAZY_MAN ;
//双层检测锁模式DCL懒汉
public static LazyMan getInstance(){
if (LAZY_MAN==null){
synchronized (LazyMan.class){
if (LAZY_MAN==null){
//不是原子操作
/**
* 1.分配内存空间
* 2.执行构造方法,初始化对象
* 3,吧这个对象指向这个空间
*
* ------
* 解决:加volatile
*/
LAZY_MAN=new LazyMan();
}
}
}
return LAZY_MAN;
}
//单线程线程没有问题
//并发产生多个对象
public static void main(String[] args) throws Exception {
LazyMan instance = LazyMan.getInstance();
//获取类对象
Class<LazyMan> lazyManClass = LazyMan.class;
//获取构造方法
Constructor<LazyMan> constructors = lazyManClass.getDeclaredConstructor();
//打破私有的构造方法
constructors.setAccessible(true);
//创建实例
LazyMan instance2 = constructors.newInstance();
System.out.println(instance);
System.out.println(instance2);
//com.atguigu.java.thread.single.LazyMan@30946e09
//com.atguigu.java.thread.single.LazyMan@5cb0d902
}
}
```
静态的内部类写法:
```java
public class Holder {
public Holder getInstance(){
return InnerClass.holder;
}
private Holder(){
}
public static class InnerClass{
private static final Holder holder=new Holder();
}
}
```
### CAS
比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环!
java是:compareAndSet
```java
public class Test01 {
public static void main(String[] args) {
//CAS compareAndSet:比较并交换!
AtomicInteger atomicInteger= new AtomicInteger(1);
//如果是期望的值就更新,如果不是就不去更新
System.out.println(atomicInteger.compareAndSet(1,2));
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(1,3));
System.out.println(atomicInteger.get());
}
}
```
源码:
```java
public final boolean compareAndSet(int expect, int update) {
//java无法操作内存需要通过native调用C++去操作内存,Java还可以通过Unsafe这个类去操作内存
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
```
缺点:
1,底层是自旋锁,循环会耗时
2,一次只能保证一个共享变量的原子性
3,ABA问题
ABA问题就像西游记里面的真假葫芦一样,真的已经被人掉包了,而他自己还傻傻分不清。
```java
public class Test01 {
public static void main(String[] args) {
//CAS compareAndSet:比较并交换!
AtomicInteger atomicInteger= new AtomicInteger(1);
//如果是期望的值就更新,如果不是就不去更新
//这个是 ABA 问题
//捣乱+++++++++++++++++
System.out.println(atomicInteger.compareAndSet(1,2));
System.out.println(atomicInteger.compareAndSet(2,1));
System.out.println(atomicInteger.get());
//正常+++++++++++++++++
System.out.println(atomicInteger.compareAndSet(1,3));
System.out.println(atomicInteger.get());
//解决方法就是原子引用
}
}
```
### 原子引用
带版本号的原子操作
`AtomicReference`不带标记的原子引用
`AtomicStampedReference`带标记的原子引用
> 原子引用可以解决ABA问题,对应的思想就是 ** 乐观锁**
```java
public static void main(String[] args) {
//正常业务场景,是一个对象 USer
AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<>(6, 1);
//CAS会出现一个ABA问题解决的方案是 :使用原子引用 : 带有版本号的原子操作 :AtomicStampedReference
//如果泛型是包装类,主要对象的引用问题
new Thread(()->{
System.out.println("A1->"+stampedReference.getStamp());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(stampedReference.compareAndSet(6, 7, stampedReference.getStamp(), stampedReference.getStamp() + 1));
System.out.println("A2->"+stampedReference.getStamp());
System.out.println(stampedReference.compareAndSet(7, 6, stampedReference.getStamp(), stampedReference.getStamp() + 1));
System.out.println("A3->"+stampedReference.getStamp());
},"A").start();
new Thread(()->{
System.out.println("B1->"+stampedReference.getStamp());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("B2->"+stampedReference.compareAndSet(6, 10, stampedReference.getStamp(), stampedReference.getStamp() + 1));
System.out.println("B2->"+stampedReference.getStamp());
},"B").start();
}
打印:
A1->1
B1->1
B2->true
B2->2
false
A2->2
false
A3->2
```
### 锁的理解
公平锁:不能插队
非公平锁:可以插队,默认的都是非公平锁
```java
//设置为公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
//设置为非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
```
可重入锁:是指的获取到了外面的锁,就能自动获得里面的锁。
所有的锁都是可重入锁,(递归锁)
```java
public class Demo02 {
public static void main(String[] args) {
Phone2 phone1 = new Phone2();
new Thread(()->{
phone1.sms();
},"A").start();
new Thread(()->{
phone1.sms();
},"B").start();
}
}
class Phone2{
//主要的细节点:锁必须是成对出现的,否则会出现死锁
Lock lock= new ReentrantLock();
public void sms(){
lock.lock();//加锁
try {
System.out.println(Thread.currentThread().getName()+"--->sms");
call();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();//解锁
}
}
public void call(){
lock.lock();//加锁
try {
System.out.println(Thread.currentThread().getName()+"--->call");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
```
自旋锁:会一直执行,直到执行有符合条件的时候才结束
```java
public class MyThread {
public static void main(String[] args) throws Exception{
//自旋锁的自我实现
SpinLockDemo spinLockDemo = new SpinLockDemo();
new Thread(()->{
spinLockDemo.lock();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
spinLockDemo.unLock();
}
},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
spinLockDemo.lock();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
spinLockDemo.unLock();
}
},"B").start();
}
}
```
```java
public class SpinLockDemo {
AtomicReference<String> atomicReference = new AtomicReference<String>();
//加锁
public void lock(){
System.out.println(Thread.currentThread().getName()+"加锁中");
while (!atomicReference.compareAndSet(null,"哈哈~~")){
}
}
//解锁
public void unLock(){
System.out.println(Thread.currentThread().getName()+"解锁中");
atomicReference.compareAndSet("哈哈~~",null);
}
}
```
死锁:两个线程互相调用
```java
public class Deadlock {
public static void main(String[] args) {
String lockA="lockA";
String lockB="lockB";
new Thread(new Dead(lockA,lockB),"线程T1").start();
new Thread(new Dead(lockB,lockA),"线程T2").start();
}
}
class Dead implements Runnable {
String lockA;
String lockB;
public Dead(String lockA, String lockB) {
this.lockA = lockA;
this.lockB = lockB;
}
@Override
public void run() {
synchronized (lockA) {
System.out.println(Thread.currentThread().getName()+"===>"+lockA+"试图获取:"+lockB);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
}
synchronized (lockB){
}
}
}
}
```
排查死锁:
1.通过jdk自带的工具命令`jps -l`排查存活的进程
D:\lei2020\Code2020\cloud2020>jps -l
15952 org.jetbrains.kotlin.daemon.KotlinCompileDaemon
16420 com.atguigu.java.thread.lock8.Deadlock
8740
18120 org.jetbrains.jps.cmdline.Launcher
11660 sun.tools.jps.Jps
2,执行命令:jstack 16420
"线程T2" #12 prio=5 os_prio=0 tid=0x0000000019d9d800 nid=0x3ae4 waiting for monitor entry [0x000000001a9be000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.atguigu.java.thread.lock8.Dead.run(Deadlock.java:57)
- waiting to lock <0x00000000d62bc008> (a java.lang.String)
- locked <0x00000000d62bc040> (a java.lang.String)
at java.lang.Thread.run(Thread.java:745)
"线程T1" #11 prio=5 os_prio=0 tid=0x0000000019d9b000 nid=0x2098 waiting for monitor entry [0x000000001a8bf000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.atguigu.java.thread.lock8.Dead.run(Deadlock.java:57)
- waiting to lock <0x00000000d62bc040> (a java.lang.String)
- locked <0x00000000d62bc008> (a java.lang.String)
at java.lang.Thread.run(Thread.java:745)
-
读锁(共享锁) :多个线程可以同时操作
写锁(独占锁、排他锁):一个线程操作

浙公网安备 33010602011771号