JUC

Posted 小田mas

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JUC相关的知识,希望对你有一定的参考价值。

JUC

多线程的三种方法
1.继承Thread这个类,重写run方法。

2.实现Runnable接口,实现run方法。(Thread是Runnable的实现类)

(以上两种方法都 无法抛出异常 不拥有返回值)

3.实现Callable接口,实现call方法。(这个方法可对外抛出异常和拥有返回值)
java.util.concurrent

接口不可以new

匿名内部类
在这里插入图片描述
runnable函数式接口
JDK1.8 lambda表达式:(参数)->{代码}

synchronized 在 run 方法前面加
在这里插入图片描述

wait 和 sleep的区别

  • 来自不同的类 wait obiect类 sleep Thread类
  • 释放锁也有区别 wait会释放锁,sleep不会释放锁
  • 两个都要捕获 异常,notify,notifyall不需要
  • 使用的范围不同,wait只能在同步代码块中,sleep可以在任何地方

并发和并行

  • 单核,多个线程同时操作一个资源
  • 多核,多个人一起走,

线程和进程

  • 进程资源的最小单位,一个程序
  • 线程操作的最小单位,多个线程可以组成一个进程

Lock锁

import java.util.concurrent.locks.Lock;
在这里插入图片描述
公平锁与非公平锁:是否可以插队。
在这里插入图片描述
使用方法,用之前加锁,用完之后解锁。
在这里插入图片描述

将lock锁与synchronized的区别展示下方
在这里插入图片描述
Lock锁的步骤

  1. 先new ReentrantLock()
  2. 再Lock.lock();加锁
  3. 再在finally里面lock.unlock();

还是需要注意一个问题,往new的线程里面扔资源,再如之前一样实现多线程。

在这里插入图片描述
大哥说的比较简化的代码,就是把大括号去了,能往一行挤得就挤到一行。

Lock锁和synchronized的区别

  • synchronized是一个内置的关键字,Lock是
  • synchronized是无法判断获取锁的状态,Lock可以判断是否获取到了锁
  • synchronized可以自动释放锁,Lock必须要手动释放锁,如果不释放锁就会导致死锁。
  • synchronized当一个线程用,另一个线程就会一直等待,Lock锁不会这样,Lock.tryLock().
  • synchronized默认是可重入的,不可以中断的,非公平;Lock锁,可重入锁,可以判断锁,非公平(可以自己设置)。
  • 适用范围 s适合少量的代码块重复问题,Lock锁适合大量的同步代码。

那 锁 是什么?如何判断锁的是谁?

还是先来了解一下

生产者消费者问题

一个口诀:判断等待,处理业务,进行通知
就是wait ,notify的操作
在这里插入图片描述
但是要注意,两个线程调用资源的时候不会出现问题,但是当线程个数更多,就需要等待放在while中,因为会出现虚假唤醒问题,不能再放在if中!

虚假唤醒:当一个条件满足时,很多线程都被唤醒了,但是只有其中部分是有用的唤醒,其它的唤醒都是无用功
因为if只会执行一次,执行完会接着向下执行if()外边的
而while不会,直到条件满足才会向下执行while()外边的,参考了什么是Java虚假唤醒及如何避免虚假唤醒?

写一个资源类
然后再由线程去操作资源类。
在这里插入图片描述
单例模式、排序算法、生产者消费者、

虚假唤醒,wait语句应该在循环之中。
在这里插入图片描述

Condition

在这里插入图片描述
在这里插入图片描述
左右等价
在这里插入图片描述
采用condition解决该问题。
在这里插入图片描述
所谓的精准就是指对于condition的await和signal精准实施。
在这里插入图片描述
起初给每一个方法定义属于其自己的condition,然后再利用标记字段实施特定的手段。

在这里插入图片描述
扔Runnable接口,现在也不用了,就是在大括号里面放上 资源的方法。

conditioni.await();
conditioni.signal();//注意i是自己之前定义的!!!

8锁现象

判断锁的是谁?知道什么是锁?

对象、class

其实就是关于锁的8个问题:

//实现相应的延迟
import java.util.concurrent.Timeunit;
Timeunit.SECONDS.sleep(1);//要补货异常
  1. synchronized锁的对象是方法的调用者,谁先拿到谁先执行。
    在这里插入图片描述
    需要有延迟,同步方法(synchronized)。

static 静态方法(类一加载就有了,是一个模板)的话就是 锁的是Class。
不管是几个对象,只要是一个类模板就有相应的顺序。
注意跟普通的方法进行区分。

出现了一个小问题:

	一个是静态方法,一个是普通方法,
而这里锁住了同步代码块,锁不住另一个对象

在这里插入图片描述
两个不是一个锁。这里需要重要理解。

集合类不安全

在这里插入图片描述

list.foreach(system.out::println);//也没啥就是建议学习下打印方式

嗯,不安全,报错了
在这里插入图片描述
前两种解决方案
在这里插入图片描述

//两种解决方法
vector+collections.synchronizedList

第三种方法

CopyOnWriteArrayList
在这里插入图片描述

List<String> list = new CopyOnWriteArrarList<>();

CopyOnWrite 写入时复制,COW 计算机程序设计领域的的一种优化策略。
多个线程调用的时候,读的时候固定的,写入的时候复制一份,copyof再set回去,避免覆盖造成数据问题。
读写分离。
CopyOnWrite比vector更好在:
只要有synchronzied方法,效率就相对较低。

set不安全

在这里插入图片描述
在这里插入图片描述
hashset的底层就是hashmap
在这里插入图片描述
在这里插入图片描述

Map不安全

在这里插入图片描述
三个重载方法,容量+影响因子
在这里插入图片描述
在这里插入图片描述

Map<String,String> map = new HashMap<>();

可以用concurrentHashMap
在这里插入图片描述

Callable

  1. 有返回值
  2. 可以抛出异常
  3. 方法不同,call方法,run方法

在这里插入图片描述
泛型的参数等于方法的返回值。
在这里插入图片描述
在这里插入图片描述
callable通过runnable去和Thread连接。

在这里插入图片描述
而Runnable的实现类跟callable是有关系的。

在这里插入图片描述
可以通过这个图进行理解。
在这里插入图片描述
futuretask中有一个带callable类型参数的构造器。

在这里插入图片描述
传说中的 适配类 就是futuretask。
获得返回参数就要看下图啦!
在这里插入图片描述
get方法可能会产生阻塞。
所以把他放到最后,或者采用异步通信。

在这里插入图片描述
最终只会出现一个调用的结果,因为结果会被缓存,效率更高。

细节:

  1. 有缓存
  2. 结果可能需要等待,存在阻塞。

常用的辅助类

CountDownLatch
在这里插入图片描述
减法计数器!
在这里插入图片描述
主要就是减一和归零向下操作:

countDownLatch.countDown();
countDownLatch.await();

每次有线程要用,就减一,到零await()就会激活。

CyclicBarrier
在这里插入图片描述
计数器加一。

插播插播:
lambda是不能拿到循环里面的i的!需要用到final
在这里插入图片描述
在这里插入图片描述
其实源码就是–count。

Semaphore并发里面用得很多!
在这里插入图片描述
友友发的弹幕
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
并发限流,控制最大的线程数!
在这里插入图片描述
在这里插入图片描述
读是read,写是write。

阻塞队列

把这个词分开来理解
阻塞+队列
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

阻塞队列的四种API

在这里插入图片描述
在这里插入图片描述

同步队列

在这里插入图片描述

线程池

创建线程就要使用线程池了!
在这里插入图片描述
线程池的执行,程序完成之后需要关闭结束线程池。

threadpool.shutdown();//放到finally里面。

带cached的遇墙则强型。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class test {
    public static void main(String[] args) {
        ExecutorService threadpool = Executors.newCachedThreadPool();//遇墙则强型
        //ExecutorService threadpool = Executors.newSingleThreadExecutor();//单个型
        //ExecutorService threadpool = Executors.newFixedThreadPool(5);//单个型
        for (int i = 0; i < 100; i++) {
            final int tmep = i;
            threadpool.execute(()-> {
                        System.out.println(Thread.currentThread().getName() + "  ok");
                    }
            );

        }
    }
}

单个源码

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

在这里插入图片描述
举个栗子!
在这里插入图片描述
四种拒绝策略
在这里插入图片描述
7大参数
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
嗯嗯,一个小总结:
在这里插入图片描述
池的最大大小应该如何设置:

  1. cpu密集型 看自己电脑是几核,最后就是几,去设备管理器里面去找,更好的方式就是通过代码去获取Runtime,getRuntime().availableprocessors()
  2. IO密集型,判断程序中十分耗费IO的线程,在这个基础上要大于这个数,一般是两倍。

四大函数式接口

函数式接口:只有一个方法的接口。functionalinterface,简化编程模型,新版本的框架底层大量应用。

  1. Consumer .foreach
  2. Function
  3. Predicate
  4. Supplier

Function,传入参数T,返回参数R。
在这里插入图片描述
new一下,匿名内部类,没有名字的类!
把上面的源代码实例化一下!
在这里插入图片描述
在这里插入图片描述
只要是函数式接口,就可以采用lambada表达式简化。

断定式接口
在这里插入图片描述
消费型接口只输入不返回。
供给型接口只返回不输入。

传说的链式编程!
在这里插入图片描述
stream流做运算!

ForkJoin

分支合并,JDK1.7之后,主要就是并行执行任务,提高效率的。
特点:工作窃取。维护的都是双端队列。
在这里插入图片描述
早干完活了可以帮别人干。
跟线程池差不多,都是通过。。pool实现。
在这里插入图片描述
又来一条插播:
在这里插入图片描述
求中间值的时候建议 使用

start + (end-start)/2 可以使用这个式子来求解值。

求和的三种方式:(三种程序员
在这里插入图片描述
在这里插入图片描述
stream的并行流计算:
在这里插入图片描述
注意:大数据量中使用,采用了分而治之的思想。

异步回调

在这里插入图片描述
在这里插入图片描述

volatile

volatile是java虚拟机提供的轻量级的同步机制。

  1. 保证可见性
  2. 不保证原子性
  3. 禁止指令重排

JMM,java内存模型,不存在的东西,概念,约定。

关于JMM的一些同步的约定:

  1. 线程解锁前,必须把共享变量立即刷回主存;拷贝的方式修改,完成之后再把值弄回去。
  2. 线程加锁前,必须读取主存中的最新值到工作内存中;
  3. 加锁和解锁是同一把锁。
    在这里插入图片描述
    关于主内存与工作内存之间的交互协议,即一个变量如何从主内存拷贝到工作内存。如何从工作内存同步到主内存中的实现细节。java内存模型定义了8种操作来完成。这8种操作每一种都是原子操作。8种操作如下:

lock(锁定):作用于主内存,它把一个变量标记为一条线程独占状态;

read(读取):作用于主内存,它把变量值从主内存传送到线程的工作内存中,以便随后的load动作使用;

load(载入):作用于工作内存,它把read操作的值放入工作内存中的变量副本中;

use(使用):作用于工作内存,它把工作内存中的值传递给执行引擎,每当虚拟机遇到一个需要使用这个变量的指令时候,将会执行这个动作;

assign(赋值):作用于工作内存,它把从执行引擎获取的值赋值给工作内存中的变量,每当虚拟机遇到一个给变量赋值的指令时候,执行该操作;

store(存储):作用于工作内存,它把工作内存中的一个变量传送给主内存中,以备随后的write操作使用;

write(写入):作用于主内存,它把store传送值放到主内存中的变量中。

unlock(解锁):作用于主内存,它将一个处于锁定状态的变量释放出来,释放后的变量才能够被其他线程锁定;

Java内存模型还规定了执行上述8种基本操作时必须满足如下规则:

(1)不允许read和load、store和write操作之一单独出现(即不允许一个变量从主存读取了但是工作内存不接受,或者从工作内存发起会写了但是主存不接受的情况),以上两个操作必须按顺序执行,但没有保证必须连续执行,也就是说,read与load之间、store与write之间是可插入其他指令的。

(2)不允许一个线程丢弃它的最近的assign操作,即变量在工作内存中改变了之后必须把该变化同步回主内存。

(3)不允许一个线程无原因地(没有发生过任何assign操作)把数据从线程的工作内存同步回主内存中。

(4)一个新的变量只能从主内存中“诞生”,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量,换句话说就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作。

(5)一个变量在同一个时刻只允许一条线程对其执行lock操作,但lock操作可以被同一个条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。

(6)如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值。

(7)如果一个变量实现没有被lock操作锁定,则不允许对它执行unlock操作,也不允许去unlock一个被其他线程锁定的变量。

(8)对一个变量执行unlock操作之前,必须先把此变量同步回主内存(执行store和write操作)。

很可能出现一个问题:
程序不知道主内存的值已经修改了!!!
在这里插入图片描述
结果:输出1,但是程序还是不会停下来,这个时候就要用到volatile!

保证可见性
在这里插入图片描述

不保证原子性
如果不加lock和sychronized,怎么保证原子性?在这里插入图片描述
使用原子类,
在这里插入图片描述
在这里插入图片描述
避免指令重排
在这里插入图片描述
在这里插入图片描述

CAS

Java层面:
在这里插入图片描述

对于unsafe类的研究
在这里插入图片描述
对于加一的底层探究:
在这里插入图片描述
在这里插入图片描述
CAS:比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那就执行操作,如果不是就一直循环。(do-while)cpu并发原语
缺点:
1.循环会耗时
2.一次只能保证一个共享变量的原子性
3.存在ABA问题

CAS:ABA问题
狸猫换太子
就是拿到的东西看着没有变,实际上是变了!诸如下图的情况发生。
其实就是乐观锁、悲观锁的问题。什么是乐观锁,什么是悲观锁,解决并发控制的问题。
在这里插入图片描述
那怎么解决这个问题呢?需要采用带版本号的原子操作!
这样!
在这里插入图片描述
给一个时间戳,相当于版本号。

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

自旋锁

在这里插入图片描述
我们来自定义一个锁测试:
在这里插入图片描述

死锁

定义:

以上是关于JUC的主要内容,如果未能解决你的问题,请参考以下文章

006-多线程-JUC线程池-并发测试程序

JUC-java8特性

什么是JUC

多线程进阶=;JUC编程

JUC-线程创建的四种方式+原理分析

JUC-线程创建的四种方式+原理分析