# 多线程进阶---->JUC 编程
准备环境,IDEA新建一个Maven项目,然后环境设置jdk8
# 1 、什么是JUC

JUC 就是以上的三个包 (面试高频问题JUC)
java.uitl java中的工具包
业务:普通的线程代码 Thread,Runnable 是没有返回值的,效率相对于Callable 相对较低!

# 2、线程和进程
> 线程和进程

**进程:一个程序,(QQ.exe,程序的集合 .jar包);**
一个进程可以包含多个线程,只是包含一个线程!
java默认有几个线程呢? 2 个 main线程 GC线程
**线程:开了一个进程(Typora),我们可以写字,自动保存功能 (这些都是线程负责的)**
java实现开启线程得到三个方式:Thread Runnable Callable。
**java真的能开启线程吗?**
我们通过查看线程strart().方法的底层源码,可得启动线程是调用的start0方法,此方法是native修饰的方法
表示的是这是本地方法,表示 调用的是c++底层来帮我们实现的。

> 并发、并行
并发编程:并发、并行 的区别:
并发(多线程操作同一个资源)
- CPU 如果只有一核,需要模拟出来多条线程,我们可以使用快速交替的方法 来模拟出并发
并行(多个人一起行走)
- CPU是多核的,并行是同时在多个CPU上跑;线程池
```java
public static void main(String[] args) {
new Thread().start();
//输出 cpu 核数信息
System.out.println(Runtime.getRuntime().availableProcessors());
}
```
**并发编程的本质:充分利用电脑CPU的资源,也是充分利用服务器的性能**
## 多线程回顾
> 线程的状态有哪几种? 5种
- 新建
- 就绪
- 阻塞
- 运行
- 死亡
> wait() ,和 sleep() 方法有什么区别?
- wait 方式是object类下的方法 ,sleep是Thread类下的方法
- wait方法会释放锁,一般需要和notify (唤醒线程)一起使用 sleep方法不会释放锁
- 使用的范围是不同的
- wait 必须在同步代码块中使用。如果要使用wait和notify的话,那么必须在synchronized块中,否则会抛出IllegalMonitorStateException
- sleep 可以在任何地方使用,在任何地方睡
- 是否需要捕获异常
- wait 不需要捕获异常
- sleep 必须要捕获异常
# 3、LOCK 锁(重点)
> 传统 Synchronized 锁
使用传统的Synchronized锁来,实现卖票的功能
```java
package com.wyx;
/**
* @author wyx
* @version 1.0
* @date 2020/5/27 16:07
*
* 卖票例子
*
* 线程就是一个单独的资源类,没有任何附属的操作!
* 1、属性
* 2、方法
*/
public class Test02 {
public static void main(String[] args) {
//并发 多线程操作 同一个资源类 ,把资源类丢入线程
Ticket ticket = new Ticket();
//函数式接口,lambda 表达式 (参数)->{代码}
//代表三个线程 去调用 买票的方法,并且解耦代码
new Thread(()->{
for(int i=0 ;i<60 ;i++){
ticket.sale();
}} ,"A").start();
new Thread(()->{
for(int i=0 ;i<60 ;i++){
ticket.sale();
}
} ,"B").start();
new Thread(()->{
for(int i=0 ;i<60 ;i++){
ticket.sale();
}} ,"C").start();
}
}
//线程是一个单独的资源类 没有任何附属的操作!
//oop 编程
class Ticket{
private int number=50;
//卖票的方式
public synchronized void sale(){
if (number>0){
System.out.println("卖出了第"+number--+"票"+"剩余"+number+"票");
}
}
}
```
> lock 接口
lock默认为 不公平锁(为了提高效率)
> 使用Lock 锁 来去实现卖票的操作
```java
package com.wyx;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author wyx
* @version 1.0
* @date 2020/5/27 16:07
*
* 卖票例子
*
* 线程就是一个单独的资源类,没有任何附属的操作!
* 1、属性
* 2、方法
*/
public class Test03Demo {
public static void main(String[] args) {
//并发 多线程操作 同一个资源类 ,把资源类丢入线程
Ticket2 ticket = new Ticket2();
//函数式接口,lambda 表达式 (参数)->{代码}
//代表三个线程 去调用 买票的方法,并且解耦代码
new Thread(()->{
for(int i=0 ;i<30 ;i++){
ticket.sale();
}} ,"A").start();
new Thread(()->{
for(int i=0 ;i<30 ;i++){
ticket.sale();
}
} ,"B").start();
new Thread(()->{
for(int i=0 ;i<30 ;i++){
ticket.sale();
}} ,"C").start();
}
}
//线程是一个单独的资源类 没有任何附属的操作!
//oop 编程
class Ticket2{
private int number=50;
Lock lock = new ReentrantLock();
//卖票的方式
public void sale(){
lock.lock();
try {
if (number>0){
System.out.println(Thread.currentThread().getName()+"卖出了第"+number--+"票"+"剩余"+number+"票");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//解锁
lock.unlock();
}
}
}
```
> Synchronized 和 Lock 的区别
- Synchronized 是一个内置的java 关键字, 而 Lock是一个java类、
- Synchronized 无法判断锁的状态(自动的),Lock 可以判断是否获取到了锁。
- Synchronized 会自动释放锁, Lock 必须要手动释放锁!,如果不释放锁会发生死锁!
- Synchronized 线程1(获得锁---阻塞)、线程2(等待---继续等待); 而Lock不一定会继续等待释放锁!(tryLock 方法)
- Synchronized 可重入锁,不可以中断,非公平 ; Lock ,可重入, 可以判断锁,非公平(可以自己设置)
- Synchronized 适合锁少量的代码同步问题, Lock适合锁大量的同步代码!(Lock 锁灵活度高)
> 锁是什么,如何判断锁的是谁??
简单的理解成为一把锁锁住这个线程只有当这个线程执行完毕才释放锁,在锁的过程中,其他的线程需要等待此线程执行完毕。
# 4、生产者消费者问题
> 使用Synchronized 来实现 生产者消费者模型
```java
package com.wyx.pc;
/**
* @author wyx
* @version 1.0
* @date 2020/5/28 14:48
*/
/*
* 线程之间的通信问题, 生产者消费者模型 (等待唤醒,和通知唤醒)
* A 对一个 数 +1
* B 对一个 数 -1
*
* */
public class A {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
try {
for (int i=0;i<60;i++){
data.increment();}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
new Thread(()->{
try {
for (int i=0;i<60;i++){
data.decrement();}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"B").start();
}
}
//判断是否等待 业务 通知
class Data{
private int number = 0;
//+1
public synchronized void increment() throws InterruptedException {
if(number!=0){ //0的 时候才干活
//等待
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()+"--->"+number);
//通知其他线程 +1 完毕
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException {
if(number!=1){ //1 的时候才干活
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"--->"+number);
//通知其他线程 -1 完毕
this.notifyAll();
}
}
```
> 问题存在,如果有多个线程 ABCD 四个线程,这是安全的吗?
这样的话会出现很多问题,因为使用的是if判断语句,这样的话两个线程同时进来的时候会发生冲突
会产生虚假唤醒的情况!这如何解决呢,**需要把if判断 换成while判断**!! 这样的话多个线程也能实现同步!
**防止虚假唤醒需要使用While 判断!!!!**
> 为什么wati() notfly() 方法需要和Synchronzed 搭配使用呢?
对于为啥要加Synchronized的作用,就是 wait 和 notify 方法必须是要**当前线程获得锁**才可以实行的,否则会报 IllegalMonitorStateException.
wati 和 notfly方法执行的话必须是要当前线程获得锁再能执行。
> JUC 的生产者消费者模型!!

> 代码实现
```java
package com.wyx.pc;
import java.util.concurrent.Callable;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author wyx
* @version 1.0
* @date 2020/5/28 14:48
*/
/*
* 线程之间的通信问题, 生产者消费者模型 (等待唤醒,和通知唤醒)
* A 对一个 数 +1
* B 对一个 数 -1
*
* */
public class LockA {
public static void main(String[] args) {
Data2 data = new Data2();
new Thread(()->{
try {
for (int i=0;i<60;i++){
data.increment();}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
new Thread(()->{
try {
for (int i=0;i<60;i++){
data.decrement();}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"B").start();
}
}
//判断是否等待 业务 通知
class Data2{
private int number = 0;
//使用LOCK锁
Lock lock = new ReentrantLock();
//condition 代替wait notifyAll
Condition condition = lock.newCondition();
//condition.await(); 等待 condition.signalAll();//唤醒
//+1
public void increment() throws InterruptedException {
lock.lock();
try {
//业务代码
while (number!=0){ //0的 时候才干活
//等待
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName()+"--->"+number);
//通知其他线程 +1 完毕
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void decrement() throws InterruptedException {
lock.lock();
try {
while (number!=1){
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName()+"--->"+number);
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
```
任何一个新的技术,绝对不可能是替换老技术的,有更加进阶的功能!
> 为什么要去使用Condition 去替代 wait 和 notifly 呢?
**Condition 可以精准的通知和唤醒线程!!**

想要ABC线程按照顺序执行如何实现呢?
**创建多个condition对象,condition对象可以指定唤醒线程!**代码如下
```java
package com.wyx.pc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author wyx
* @version 1.0
* @date 2020/5/28 21:46
*/
/*
* lock 锁 实现 顺序执行线程精准执行
* */
public class LockTest01 {
public static void main(String[] args) {
Data3 data3 = new Data3(); //new data 一定要写在外面 ,写在外面才能保证线程是同时执行的,在里面new对象的话 会导致每次创建一个新的对象然后他们指定的对象是不同的
//按照顺序执行 A B C 这些线程 A-B-C-A 循环执行
new Thread(()->{
for (int i =0;i<10;i++){
data3.A();
}
},"A").start();
new Thread(()->{for (int i =0;i<10;i++){
data3.B();
}},"B").start();
new Thread(()->{for (int i =0;i<10;i++){
data3.c();
}},"C").start();
}
static class Data3{
private Lock lock = new ReentrantLock();
Condition condition1 = lock.newCondition(); //需要在方法中去new newCondition 在方法外没有提示!!
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
private int number = 1; //1 A 2 B 3c
public void A(){
lock.lock();
try {
//业务 判断 --> 执行 --->通知
while (number!=1){
condition1.await();
}
System.out.println(Thread.currentThread().getName()+"-->"+"A线程执行!!");
//唤醒 B 指定
number=2;
//指定 condition2 唤醒 condition2
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void B(){
lock.lock();
try {
while (number!=2){
condition2.await();
}
System.out.println(Thread.currentThread().getName()+"-->"+"B线程执行!");
number = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void c(){
lock.lock();
try {
while (number!=3){
condition3.await();
}
System.out.println(Thread.currentThread().getName()+"-->"+"C线程执行");
number=1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
```
# 5、8 锁现象
如何判断锁的是谁? 锁到底锁的是谁?? 锁只能锁对象 和class
**深刻的理解锁**
```java
package com.wyx.Lock8;
import java.util.concurrent.TimeUnit;
/**
* @author wyx
* @version 1.0
* @date 2020/5/29 11:23
*/
/*
* 8锁,就是关于锁的8 个问题
* 1 、 标准情况下,两个线程先打印 打短信还是打电话呢? 1、发短信 2、打电话 是固定的顺序
* 2、 发短信这个方法延迟4s 那这两个线程顺序是怎么执行的呢? 1、发短信 2、打电话
*
*
* */
public class Test01 {
public static void main(String[] args) {
iphone iphone = new iphone();
//锁的存在
new Thread(()->{iphone.senSms();},"A").start();
try {
//休息1 s 钟
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{iphone.call();},"B").start();
}
}
class iphone{
//synchronized 锁的对象是方法的调用者
//以下两个方法都是同一个对象,谁先拿到锁,谁先执行!!
public synchronized void senSms(){
/*try {
//休息4s 钟
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
System.out.println("发短信===");
}
public synchronized void call(){
System.out.println("打电话===");
}
}
```
```java
package com.wyx.Lock8;
import java.util.concurrent.TimeUnit;
/**
* @author wyx
* @version 1.0
* @date 2020/5/29 12:45
*/
/*
* 3、在同一个对象中有普通方法和同步方法, 执行的话 顺序是什么呢? 先执行普通方法,在执行同步方法
* 4、两个对象,两个同步方法,执行顺序是什么呢? 如果没有延迟的话,理论上是两个不想关的方法, 有延迟的必然后执行
*
* */
public class Test02 {
public static void main(String[] args) {
iphone2 iphone = new iphone2();
//锁的存在
new Thread(()->{iphone.senSms();},"A").start();
try {
//休息1 s 钟
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{iphone.Hllo();},"B").start();
}
}
class iphone2{
//synchronized 锁的对象是方法的调用者
//以下两个方法都是同一个对象,谁先拿到锁,谁先执行!!
public synchronized void senSms(){
try {
//休息4s 钟
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信===");
}
public synchronized void call(){
System.out.println("打电话===");
}
//定义个一普通方法
public void Hllo(){
System.out.println("Hello===");
}
}
```
```java
package com.wyx.Lock8;
import java.util.concurrent.TimeUnit;
/**
* @author wyx
* @version 1.0
* @date 2020/5/29 12:49
*/
/*
* 5、增加两个静态的同步方法,在同一个对象中执行,执行顺序是?? static 类一加载就有了,所以锁的是class模板 所以执行的顺序是 发短信 打电话
* 6、两个对象 增加两个静态的同步方法,在同一个对象中执行,执行顺序是?? 两个对象的Class类模板只有一个,锁的是Class 所以执行的顺序是 发短信 打电话
*
* */
public class Test03 {
public static void main(String[] args) {
iphone3 iphone = new iphone3();
//锁的存在
new Thread(()->{iphone.senSms();},"A").start();
try {
//休息1 s 钟
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{iphone.call();},"B").start();
}
}
class iphone3{
//synchronized 锁的对象是方法的调用者
//以下两个方法都是同一个对象,谁先拿到锁,谁先执行!!
public static synchronized void senSms(){
try {
//休息4s 钟
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信===");
}
public static synchronized void call(){
System.out.println("打电话===");
}
}
```
```java
package com.wyx.Lock8;
import java.util.concurrent.TimeUnit;
/**
* @author wyx
* @version 1.0
* @date 2020/5/29 12:55
*/
/*
* 7、同一个对象,一个静态同步代码块,一个普通同步方法块,执行顺序是??打电话 发短信 他们锁的不是同一个对象,不需要去等待发短信
*
* 8、两个对象, 一个静态同步代码块,一个普通同步方法块,执行顺序是??打电话 发短信 他们锁的不是同一个对象 ,不需要去等待发短信
*
* */
public class Test04 { public static void main(String[] args) {
iphone4 iphone = new iphone4();
//锁的存在
new Thread(()->{iphone.senSms();},"A").start();
try {
//休息1 s 钟
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{iphone.call();},"B").start();
}
}
class iphone4{
//静态同步方法 锁的是class类模板
public static synchronized void senSms(){
try {
//休息4s 钟
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信===");
}
//普通同步方法 锁的是调用者
public synchronized void call(){
System.out.println("打电话===");
}
}
```
> 8锁小结
new this 是一个具体的对象
static 是一个唯一的对象模板 他们锁的对象是不同的
# 6、集合类不安全
> list 类
```java
package com.wyx.unsafe;
import java.sql.SQLOutput;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* @author wyx
* @version 1.0
* @date 2020/5/29 13:22
*/
public class ListTest {
public static void main(String[] args) {
//并发下 arraylist 是不安全的
/*
* 解决方案:
* 1.vector 是安全的 底层是synchronized
* 2.Collections.synchronizedList 来去创建new Arraylist<>
* 3.CopyOnWriteArrayList<>(); 并发包下的对象
*
* */
//CopyOnWrite 写入是复制,COW 计算机程序领域的一种优化策略
//多个线程调用的时候,list,读取的时候是固定的,写入(覆盖)
//在写入的时候避免覆盖造成的数据问题。
//CopyOnWriteArrayList 比 vector好在哪里? vertor synchronized修饰的方法 效率会低! CopyOnWriteArrayList是使用的lock锁
//List list = Collections.synchronizedList(new ArrayList<>());
List list = new CopyOnWriteArrayList<>();
for (int i = 0; i <999 ; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
}).start();
}
}
}
```
会报错:并发修改异常!!

//并发下 arraylist 是不安全的
/*
* 解决方案:
* 1.vector 是安全的 底层是synchronized
* 2.Collections.synchronizedList 来去创建new Arraylist<>
* 3.CopyOnWriteArrayList<>(); 并发包下的对象
*
* */
//CopyOnWrite 写入是复制,COW 计算机程序领域的一种优化策略
//多个线程调用的时候,list,读取的时候是固定的,写入(覆盖)
//在写入的时候避免覆盖造成的数据问题。
//CopyOnWriteArrayList 比 vector好在哪里? vertor synchronized修饰的方法 效率会低! CopyOnWriteArrayList是使用的lock锁

> set类 同理
```java
package com.wyx.unsafe;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* @author wyx
* @version 1.0
* @date 2020/5/29 19:53
*/
public class SetTest {
/*
* 同理可知 CopyOnWriteArraySet
* */
public static void main(String[] args) {
//Set set = Collections.synchronizedSet(new HashSet<>());
Set set = new CopyOnWriteArraySet<>();
for (int i = 0; i < 10; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
}).start();
}
}
}
```
hashset 的底层是什么?
```java
public HashSet() {
map = new HashMap<>();
}
```
hashset的底层就是一个hashmap,hashset add方法本质是 map.put。set的本质就是map的key无法重复!不变的值。
> Hashmap
Map也是不安全的
HashMap是怎样使用的?
- 不是通常的new出来这样使用的,工作中不使用HashMap
HashMap默认等价什么?
- new HashMap<>(16,0.75);
**ConcurrentHashMap 实现同步 Map**
```java
package com.wyx.unsafe;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author wyx
* @version 1.0
* @date 2020/5/29 20:09
*/
public class HasMapTest {
public static void main(String[] args) {
// map是这样用的吗 ? 默认等价于什么
//HashMap map = new HashMap<>();
//
Map map =new ConcurrentHashMap<>();
//加载因子,初始化容量
for (int i = 0; i < 30; i++) {
new Thread(()-> {
map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,30));
System.out.println(map);
}).start();
}
}
}
```
# 7、Callable 实现多线程

- Callable 可以有返回值
- Callable 可以抛出异常
- Callable 方法不同 all() 方法
所以使用callable 接口去实现多线程的话是最好的
**要想thread去启动callable 需要一个中间关系 Runnable**,**thread直接是无法启动Callable的**

> 代码测试
```java
package com.wyx.Callable;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* @author wyx
* @version 1.0
* @date 2020/5/29 20:36
*/
/*
*
* callable
* */
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// new Thread().start();
//new Thread().start(); //怎么启动callable? FutureTask
MyCallable myCallable = new MyCallable();
FutureTask futureTask = new FutureTask<>(myCallable);
new Thread(futureTask).start();
new Thread(new FutureTask<>(new MyCallable())).start();
//获取callable 返回值
System.out.println((String) futureTask.get()); // get方法可能会产生阻塞,一般放在最后执行get方法
}
}
class MyCallable implements Callable {
@Override
public String call() throws Exception {
System.out.println("call -->");
return "callable";
}
}
```
Callable的细节问题:
- 输出的结果是有缓存的
- 结果可能需要等待,会阻塞(get方法一般放在最后执行)
# 8、常用的辅助类(必会)
## 8.1 CounDownLatch (减法计数器)

```java
package com.wyx.util;
import java.util.concurrent.CountDownLatch;
/**
* @author wyx
* @version 1.0
* @date 2020/5/30 9:00
*/
//计数器
public class CounDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
//总数是6 ,必须要执行任务的时候再去使用 close door 必须最后执行的话!
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i <=6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"out");
countDownLatch.countDown(); //数量减1
}).start();
}
countDownLatch.await(); //等待记数器执行完毕 ,再向下执行
System.out.println("close door!");
}
}
```
> 原理
- countDownLatch.countDown(); 数量减1
- countDownLatch.await(); 等待记数器执行完毕 ,再向下执行
**每次有现车调用countDown 数量就会减1,当计数器变为0的时候,countDownLatch.await(); 就会被唤醒,再去执行下面的操作!!**
## 8.2 CyclicBarrier(加法计数器)

```java
package com.wyx.util;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
/**
* @author wyx
* @version 1.0
* @date 2020/5/30 9:12
*/
/*
* 集齐 7 颗龙珠才能召唤神龙
* */
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{ //如果这里是8的话 会一直等待第八个线程执行完毕
System.out.println("召唤神龙成功!");
});
for (int i = 1; i <=7; i++) {
final int temp =i;
//lambda 表达式 可以拿到i 吗
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"收集了第"+temp+"颗龙珠!!");
try {
//等待
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
```
## 8.3 Semaphore (信号量)

例子: 抢车位!
6个车子 --- 3个停车位置 ---->轮流等车位
```java
package com.wyx.util;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
* @author wyx
* @version 1.0
* @date 2020/5/30 9:22
*/
/*
* 停车位 6个车抢三个车位
* */
public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3);
// 线程数量,停车位 !限流!的情况下去使用这个
for (int i = 1; i <=6 ; i++) {
new Thread(()->{
try {
//acquire() 得到
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"抢到车位");
TimeUnit.SECONDS.sleep(2); //停车
System.out.println(Thread.currentThread().getName()+"离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//release() 释放
semaphore.release();
}
},String.valueOf(i)).start();
}
}
}
```
原理:
- semaphore.acquire(); 获得 假设已经满了,就会等待,等待被释放为止!!
- semaphore.release(); 释放 会将当前的信号量释放+1 然后唤醒等待的线程!!
作用:多个共享资源互斥的使用!并发的限流!控制最大的并发数!!
# 9、ReadWriteLock(读写锁)

**一写多读 ,提高效率** ,**理解为更加 细粒度的锁**
```java
package com.wyx.ReadWriteLock;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @author wyx
* @version 1.0
* @date 2020/5/30 9:35
*/
/*
ReadWriteLock
*
* 一写多度,写入的时候是不能被插队的,写是一个原子性的操作!
读取数据的话,可以被插队
*/
public class ReadWriteLock {
public static void main(String[] args) {
myCache2 myCache2 = new myCache2();
for (int i = 1; i < 10; i++) {
final int flag=i;
new Thread(()->{myCache2.put(flag+"value",flag+"value");}, String.valueOf(i)).start();
}
for (int i = 1; i < 10; i++) {
final int flag=i;
new Thread(()->{myCache2.get(flag+"value");}, String.valueOf(i)).start();
}
}
}
//加锁的 自定义的缓存
class myCache2{
private volatile Map map = new HashMap<>();
//读写锁,更加细粒度的控制锁
private ReentrantReadWriteLock readWriteLock= new ReentrantReadWriteLock();
//存,写 的时候 只希望同时只有一个线程写
public void put(String key,Object value){
//加上"写"锁!!!
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"写入"+key);
map.put(key,value);
System.out.println(Thread.currentThread().getName()+"写入完毕!");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock();
}
}
//取,读 所有的人可以去读
public void get(String key){
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"读取"+key);
map.get(key);
System.out.println(Thread.currentThread().getName()+"读取完毕!");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
}
}
/*自定义缓存 set get */
class myCache{
private volatile Map map = new HashMap<>();
//存,写
public void put(String key,Object value){
System.out.println(Thread.currentThread().getName()+"写入"+key);
map.put(key,value);
System.out.println(Thread.currentThread().getName()+"写入完毕!");
}
//取,读
public void get(String key){
System.out.println(Thread.currentThread().getName()+"读取"+key);
map.get(key);
System.out.println(Thread.currentThread().getName()+"读取完毕!");
}
}
```
**独占锁(写锁):每次只能有一个线程占用**
**共享锁(读锁):多个线程可以同时占用**
# 10、BlockingQueue(阻塞队列)


**BlockingQueue:**blockingQueQue 不是新的东西,类似与list。什么情况下我们会使用 阻塞队列呢?**
**多线程,线程池,多线并发处理会使用到阻塞队列。**
下图是blockqueque 的关系

> 使用队列
添加操作,移除操作
> 四组API
- 抛出异常
- 不会抛出异常
- 阻塞 等待
- 超时等待
| 方式 | 抛出异常 | 不会抛出异常 | 阻塞 等待 | 超时等待 |
| -------------- | --------- | ------------ | ---------- | ------------------------------------- |
| 添加操作 | add() | offer() | put() | offer("A",2, TimeUnit.SECONDS); //2s |
| 移除操作 | remove() | poll() | take() | poll(2, TimeUnit.SECONDS); //2s |
| 判断队列首操作 | element() | peek() | | |
> 抛出异常
```java
package com.wyx.BlockingQueue;
import java.util.concurrent.ArrayBlockingQueue;
/**
* @author wyx
* @version 1.0
* @date 2020/5/30 15:47
*/
/*
* 阻塞队列
* */
public class BlockingQueue {
//collection
//list
//set
//blockingQueQue 不是新的东西,类似与list
public static void main(String[] args) {
test1();
}
/*
* 抛出异常
* */
public static void test1(){
//队列的大小 3
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(arrayBlockingQueue.add("a"));
System.out.println(arrayBlockingQueue.add("b"));
System.out.println(arrayBlockingQueue.add("c"));
//java.lang.IllegalStateException: Queue full 队列已满 异常
//System.out.println(arrayBlockingQueue.add("d"));
System.out.println("-----------------------------------------");
System.out.println(arrayBlockingQueue.remove());
System.out.println(arrayBlockingQueue.remove());
System.out.println(arrayBlockingQueue.remove());
//Exception in thread "main" java.util.NoSuchElementException 队列没有元素异常
System.out.println(arrayBlockingQueue.remove());
}
}
```
> 不抛出异常,返回一个boolean 值
```java
public static void test2(){
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
//arrayBlockingQueue.offer 不抛出异常
System.out.println(arrayBlockingQueue.offer("a"));
System.out.println(arrayBlockingQueue.offer("b"));
System.out.println(arrayBlockingQueue.offer("c"));
System.out.println(arrayBlockingQueue.offer("d"));
System.out.println(arrayBlockingQueue.element()); //查看队首的元素
System.out.println(arrayBlockingQueue.peek());// 检测队首的元素
System.out.println("--------------------------------------");
//arrayBlockingQueue.poll() 不抛出异常
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
}
```
> 一直阻塞 等待
```java
public static void test3() throws InterruptedException {
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
arrayBlockingQueue.put("a");
arrayBlockingQueue.put("b");
arrayBlockingQueue.put("c");
//arrayBlockingQueue.put("d"); //如果这里超出了队列的范围,则程序不会抛出异常,会一直等待这个执行, 陷入了阻塞一直等待状态
System.out.println(arrayBlockingQueue.take());
System.out.println(arrayBlockingQueue.take());
System.out.println(arrayBlockingQueue.take());
//System.out.println(arrayBlockingQueue.take()); //如果这里超出了队列的范围,则程序不会抛出异常,会一直等待这个执行, 陷入了阻塞一直等待状态
}
```
> 阻塞超时等待(超过时间不去等待)
```java
public static void test4() throws InterruptedException {
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
arrayBlockingQueue.offer("a");
arrayBlockingQueue.offer("b");
arrayBlockingQueue.offer("c");
//超时等待,当队列发生阻塞时候,设置一个时间 超过这个时间的话就不在去进行这个操作(阻塞等待),而是去执行下一个操作
arrayBlockingQueue.offer("d",2, TimeUnit.SECONDS);
System.out.println("--------------------------------");
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
//当队列为空的时候 无法取出,我们可以设置一个时间,当超过了这个时间的话 就可以不去进行这个操作 ,而去执行下一个操作
arrayBlockingQueue.poll(2,TimeUnit.SECONDS);
System.out.println("程序执行完毕!");
}
```
>SynchronousQueque (同步队列)
没有容量,进去一个元素,必须等待取出来之后,才能往里面去放一个元素!! 只有一个容量
```java
package com.wyx.BlockingQueue;
import java.sql.Time;
import java.util.UUID;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
/**
* @author wyx
* @version 1.0
* @date 2020/5/31 9:56
*/
public class SynchronousQueueDemo {
public static void main(String[] args) {
BlockingQueue blockingQueue = new SynchronousQueue<>();
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+"--->"+"入列");
blockingQueue.put("1");
System.out.println(Thread.currentThread().getName()+"--->"+"入列");
blockingQueue.put("2");
System.out.println(Thread.currentThread().getName()+"--->"+"入列");
blockingQueue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T1").start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"---->"+blockingQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"---->"+blockingQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"---->"+blockingQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T2").start();
}
}
```
# 11、线程池(重点)
线程池:三大方法,7大参数,4种拒绝策略
> 池化技术
程序的运行,本质:占用系统的资源!优化资源的使用!-->池化技术
线程池,连接池,内存池,对象池....//创建和销毁十分浪费资源
1.最小的值
2.最大的值
**池化技术:事先在池子里准备好一些资源,有人要调用,直接从池子里拿,用完之后再还到池子里。**
> 线程池的好处
- 降低资源的消耗
- 从而提高响应的速度
- 方便管理
**线程可以复用,可以控制最大并发数,可以管理线程**
> 线程池:三大方法
- 线程池中只有单线程
- 线程池中可以自己定义线程的数量
- 线程池中的线程数量是可伸缩的
以下是阿里巴巴开发手册中的规范:

```java
package com.wyx.pocl;
import sun.nio.ch.ThreadPool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author wyx
* @version 1.0
* @date 2020/5/31 11:01
*/
/*
* 连接池 工具类: 三大方法
* Executors
* */
public class Demo01 {
public static void main(String[] args) {
//ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程
//ExecutorService threadPool = Executors.newFixedThreadPool(5);//创建一个固定的线程池的大小 最高5个线程在线程池中并发
ExecutorService threadPool = Executors.newCachedThreadPool();//创建一个 可伸缩的 线程池 发现有10个线程在线程池中并发
//使用了线程池之后,要使用线程池来创建线程
try {
for (int i = 0; i < 10; i++) {
//使用创建好的线程对象 execute 创建线程
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"ok");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 线程池用完一定要关闭线程池 放在finally里面
threadPool.shutdown();
}
}
}
```
> 7大参数
new 一个线程池,本质是new 是一个 ThreadPoolExcutor,
ThreadPoolExcutor源码如下:
```java
public ThreadPoolExecutor(int corePoolSize, //核心线程池大小
int maximumPoolSize, //最大核心线程池大小
long keepAliveTime,//超时了没有人调用就会释放
TimeUnit unit,//超时单位
BlockingQueue workQueue,//阻塞队列
ThreadFactory threadFactory) {//线程的工厂,创建线程的
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);//拒接策略
```
所以可以得知,以上的三大方法都是基于ThreadPoolExcutor 进行的封装操作!
理解:阿里巴巴开发手册。

7大参数的理解:如下图

> 理解完ThreadPoolExcutor 的7大参数,我们手动的去创建一个自定义线程池
```java
package com.wyx.pocl;
import sun.nio.ch.ThreadPool;
import java.util.concurrent.*;
/**
* @author wyx
* @version 1.0
* @date 2020/5/31 11:01
*/
/*
* 连接池 工具类: 三大方法
* Executors
* */
public class Demo01 {
public static void main(String[] args) {
//ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程
//ExecutorService threadPool = Executors.newFixedThreadPool(5);//创建一个固定的线程池的大小 最高5个线程在线程池中并发
//ExecutorService threadPool = Executors.newCachedThreadPool();//创建一个 可伸缩的 线程池 发现有10个线程在线程池中并发
//看完底层的源码 我们手动的创建自定义线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardOldestPolicy() //拒接策略 , 银行慢了 ,候客区也满了,就会进行拒绝策略,然后不处理这个人,会抛出异常 ThreadPoolExecutor
//四种拒接策略
/*new ThreadPoolExecutor.AbortPolicy() //拒接策略 , 银行慢了 ,候客区也满了,就会进行拒绝策略,然后不处理这个人,会抛出异常 ThreadPoolExecutor
* new ThreadPoolExecutor.CallerRunsPolicy() //哪里来的哪里去,队列满了 最后一个线程是从main线程执行 ,所以是main线程去执行
* new ThreadPoolExecutor.DiscardPolicy() //队列满了,会丢掉队列满出来的任务,不会抛出异常!!
* new ThreadPoolExecutor.DiscardOldestPolicy() //队列满了,尝试去和最早的竞争,等待最早的线程,若成功则执行,不成功则 丢弃,不会抛出异常
* */
);
//使用了线程池之后,要使用线程池来创建线程
try {
//最大承载; Deque + Max
for (int i = 0; i < 9; i++) {
//使用创建好的线程对象 execute 创建线程
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"ok");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 线程池用完一定要关闭线程池 放在finally里面
threadPool.shutdown();
}
}
}
```
> 四种拒绝 策略
```java
//四种拒接策略/
* new ThreadPoolExecutor.AbortPolicy() //拒接策略 , 银行慢了 ,候客区也满了,就会进行拒绝策略,然后不处理这个人,会抛出异常 ThreadPoolExecutor
* new ThreadPoolExecutor.CallerRunsPolicy() //哪里来的哪里去,队列满了 最后一个线程是从main线程执行 ,所以是main线程去执行
* new ThreadPoolExecutor.DiscardPolicy() //队列满了,会丢掉队列满出来的任务,不会抛出异常!!
* new ThreadPoolExecutor.DiscardOldestPolicy() //队列满了,尝试去和最早的竞争,等待最早的线程,若成功则执行,不成功则 丢弃,不会抛出异常
* */
```
> 小结和拓展
线程池的最大的并发数,在实际开发过程中是如何设置的呢?
- **CUP 密集型**(调优) :设置你的电脑中的最大线程数,一般在开发中使用 Runtime.getRuntime().availableProcessors(); 此方法来设置。(开发环境和服务器端的环境线程环境是不同的)
- **IO 密集型**: 首先判断你的程序中的IO线程数量在这个基础上*2 (因为IO线程是十分消耗资源的,所以设置IO线程的两倍)
# 12、四大函数式接口(重点掌握)
jdk1.8-->**必须掌握:Lambda 表达式, 链式编程,函数式接口,Stream流式计算!!**
> 函数式接口:只有一个方法的接口
Runable接口 Callbale接口 都是典型的函数式接口。在java中函数式接口是非常多的.
> Function 接口 传入参数 还有返回类型
Function 的源码如下

```java
package com.wyx.function;
import java.util.function.Function;
/**
* @author wyx
* @version 1.0
* @date 2020/5/31 20:22
*/
/*
* function 函数型接口 :有一个输入接口,有一个输出
* 只要是函数型接口,就可以使用Lambda表达式简化
* */
public class Demo01 {
public static void main(String[] args) {
//工具类:输入的值
/* Function function =new Function() {
@Override
public String apply(String str) {
return str;
}
};*/
//因为是函数式接口 ,可以使用Lambda 表达式简化书写
Function function = (str)->{return str;};
System.out.println(function.apply("wyx"));
}
}
```
> Predicate 断定型接口:只有一个输入参数,返回值只能是 boolean 值!!
**Predicate源码如下:**

测试代码:
```java
package com.wyx.function;
import java.sql.SQLOutput;
import java.util.function.Predicate;
/**
* @author wyx
* @version 1.0
* @date 2020/5/31 20:45
*/
/*
* 断定性接口:有一个输入参数,返回值只能是布尔值
*
* */
public class Demo02 {
public static void main(String[] args) {
//判断字符串是否为空
/* Predicate predicate=new Predicate() {
@Override
public boolean test(String str) {
return str.isEmpty();
}
};*/
//Lambda 表达式简化
Predicate predicate=(str)->{
return str.isEmpty();
};
System.out.println(predicate.test(""));
}
}
```
> Consumer 消费型接口 只有输入值
```java
package com.wyx.function;
import java.util.function.Consumer;
/**
* @author wyx
* @version 1.0
* @date 2020/5/31 21:16
*/
/*
* 消费型接口 :只有输入值 没有返回值!
* */
public class Demo03 {
public static void main(String[] args) {
/* Consumer consumer =new Consumer() {
@Override
public void accept(String str) {
System.out.println(str);
}
};*/
Consumer consumer = (str)->{
System.out.println(str);
};
consumer.accept("hello world");
}
}
```
> Supplier 生产型接口 只有返回值
```java
package com.wyx.function;
import java.util.function.Supplier;
/**
* @author wyx
* @version 1.0
* @date 2020/5/31 21:23
*/
/*
* 供给型接口: 没有参数 只有返回值
* */
public class Demo04 {
public static void main(String[] args) {
/* Supplier supplier =new Supplier() {
@Override
public Integer get() {
System.out.println("调用供给型函数式接口,只有返回值!");
return 1024;
}
};*/
Supplier supplier = ()->{
System.out.println("调用供给型函数式接口,只有返回值!");
return 1024;
};
System.out.println(supplier.get());
}
}
```
# 13、Stream 流式计算
> 什么是Stream 流式计算
大数据:存储+计算
集合、mysql本质就是存储东西的:
计算都应该交给流来操作!
> 整合练习 (测试)
```java
package com.wyx.stream;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author wyx
* @version 1.0
* @date 2020/6/1 10:05
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private int id;
private String name;
private int age;
}
```
```java
package com.wyx.stream;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Filter;
/**
* @author wyx
* @version 1.0
* @date 2020/6/1 10:06
*/
/*
* 要求:在一分钟内完成此题,只能用一行代码实现!
* 同时筛选
*
* 1.ID 必须是偶数
* 2.年龄必须大于23岁
* 3.用户名转为大写字母
* 4.用户名字母倒着排序
* 5.只输出一个用户!
*
* */
public class Test {
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(5, "e", 25);
//集合就是存储
List list = Arrays.asList(u1, u2, u3, u4, u5);
//filter 里面是断定型接口 注意写法
//链式编程!! 函数式接口!!Stream流计算!!
list.stream()
.filter(user -> { return user.getId()%2==0;}) //1.ID 必须是偶数
.filter(user -> {return user.getAge()>23;}) //1.age >23
.map(user -> {return user.getName().toUpperCase();})
.sorted((uu1,uu2)->{return uu2.compareTo(uu1);})
.limit(1)
.forEach(System.out::println);
}
}
```
# 14、ForkJOIN
> 什么是ForkJoin?
在JDK1.7 推出的ForkJoin。 并行的去执行任务!提高工作效率!大数据量!
在大数据中:Map Reduce(把大任务拆分为小任务)
> ForkJoin 特点: 工作窃取 ,当一个线程执行完毕,可以去执行另外没有执行完毕的线程中的内容
# 15、异步回调
> Future 设计的初衷,
# 16、JMM(Volatile 三大性质)
> 请你谈谈你对Volatile 的理解
Volatile是java虚拟机提供的 **轻量级的同步机制!!**(类似Synchronized)
1.保证可见性
2.**不保证原子性**
3.禁止指令重排
> JMM
JVM:java虚拟机
JMM:java内存模型,不存在的东西,是一个约定,一个概念。
**JMM同步的约定:**
- 线程解锁前,必须把共享变量**立刻** 刷会主存。
- 线程加锁前,必须读取(复制)主存中的最新值到线程(工作)内存中!
- 加锁和加锁是同一把锁。
**线程 分为 工作内存 和 主内存**
# 17、Volatile
> volatile 的 **可见性** 测试代码
```java
package com.wyx.testvolatile;
import java.util.concurrent.TimeUnit;
/**
* @author wyx
* @version 1.0
* @date 2020/6/2 16:08
*/
public class JMMDemo {
// volatile 不加的话程序就会死循环,线程感知不到外面的值被修改了,没有可见性!!
//加上 volatile 的话线程就会有可见性 ,线程可以感知外面的值
private volatile static int num =0;
public static void main(String[] args) {
new Thread(()->{
while (num==0){
}
}).start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (Exception e) {
e.printStackTrace();
}
num =1;
System.out.println(num);
}
}
```
> volatile **不保证原子性** 测试代码
原子性:不可分割。
线程A 在执行任务的时候,不能被打扰的,也不能被分割。要么同时成功,要么同时失败。
```java
package com.wyx.testvolatile;
/**
* @author wyx
* @version 1.0
* @date 2020/6/3 20:14
*/
/*
* Volatile 不保证原子性
* */
public class VolatileDemo02 {
//volatile 不能保证原子性
private volatile static int num = 0;
//synchronized 可以保证原子性
public synchronized static void add(){
num++;
}
public static void main(String[] args) {
for (int i = 1; i <= 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
//理论上 num 为2w
add();
}
}).start();
}
while (Thread.activeCount()>2){
//java 默认有两个 main gc
Thread.yield();
}
//说明线程跑完了
System.out.println(Thread.currentThread().getName()+num);
}
}
```
以上可以得知要保证原子性必须要加上lock 或者 synchronized 来保证原子性!
如果不加的话,怎么样去保证原子性?
> 使用原子类去解决原子性问题
```java
package com.wyx.testvolatile;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author wyx
* @version 1.0
* @date 2020/6/3 20:14
*/
/*
* Volatile 不保证原子性
* */
public class VolatileDemo02 {
//volatile 不能保证原子性
// AtomicInteger 解决原子性问题!!!
private static AtomicInteger num =new AtomicInteger();
//synchronized 可以保证原子性
public synchronized static void add(){
//num++;
num.getAndIncrement(); //AtomicInteger 加1 方法
}
public static void main(String[] args) {
for (int i = 1; i <= 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
//理论上 num 为2w
add();
}
}).start();
}
while (Thread.activeCount()>2){
//java 默认有两个 main gc
Thread.yield();
}
//说明线程跑完了
System.out.println(Thread.currentThread().getName()+num);
}
}
```
这些原子类的底层都直接和操作系统挂钩!在内存中去修改值!unsafe类是一个很特殊的存在!!(c++编写)
> 禁止 指令重排
什么是指令重排呢?
你写的程序,计算机并不是按照你写的那样去执行的。
因为运行代码的话:源代码-->编译器优化的重排-->指令并行也可能会重排-->内存系统也会重排-->执行
**处理器在进行指令重排的时候,要考虑:数据之间的依赖性!**
```java
int x =1; //1
int y =2; //2
x=x+5; //3
y=x*x; //4
我们所期望的 1234是这样去运行的 可不可能是4123!循序执行? 第4行代码是依赖与第1行代码的,所以是不行的
```
可能造成影响的结果:a b x y 这四个值默认都是0
| 线程A | 线程B |
| ----- | ----- |
| x=a | y=b |
| b=1 | a=2 |
| | |
正常的结果: x=0; y=0 可能由于指令重排,结果顺序可能会混乱!,指令重排导致的诡异结果!
虽然指令重排发生的几率很小,但是在逻辑上还是存在的!
**volatile 可以避免指令重排**
内存屏障
- 保证特定的操作的执行顺序
- 保证某些变量的内存可见性(利用这些特性volatile 实现了可见性)
底层的实现图

> 总结
volatile 可以保证可见性,不能保证原子性,可以保证避免指令重排现象的发生!!
而内存屏障在哪里实现的最多呢? -->单例模式
# 18、单例模式 深究
饿汉式,DCL懒汉式,深究!
> 饿汉式
```java
package com.wyx.single;
/**
* @author wyx
* @version 1.0
* @date 2020/6/4 20:38
*/
/*
* 单例模式 饿汉式
* */
public class Hungry {
//私有构造方法
private Hungry(){
}
//私有 静态 一个对象 hungry
private static final Hungry HUNGRY = new Hungry();
// 静态方法 返回值是 Hungry 返回私有静态HUNGRY
public static Hungry getHungry(){
return HUNGRY;
}
public static void main(String[] args) {
//通过 getHungry 去创建对象
System.out.println(getHungry().hashCode());
}
}
```
> 懒汉式
```java
package com.wyx.single;
/**
* @author wyx
* @version 1.0
* @date 2020/6/4 20:43
*/
//懒汉式单例模式
public class LazyMan {
//构造器 私有
private LazyMan(){
}
private static LazyMan lazyMan;
public static LazyMan getInstance(){
//懒汉式 需要判断,若创建的话就返回之前创建的,没有创建的话在创建对象
if (lazyMan==null) {
lazyMan = new LazyMan();
}
return lazyMan;
}
}
```
> DCL懒汉式(双重锁)
当懒汉式多线程并发的时候,可能有小概率导致 指令重排 ,为了避免此现象,需要对懒汉式进行改进!
可以使用Synchronized 加上锁去避免并发问题。
```java
package com.wyx.single;
/**
* @author wyx
* @version 1.0
* @date 2020/6/4 20:43
*/
//懒汉式单例模式
public class LazyMan {
//构造器 私有
private LazyMan(){
}
private static LazyMan lazyMan;
public static LazyMan getInstance(){
//加上锁 避免 指令重排
if(lazyMan==null){
synchronized (LazyMan.class){
if (lazyMan==null) {
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
//在单线程下确实可以实现
//在多线程并发下实现呢?
public static void main(String[] args) {
}
}
```
> 静态内部类 (实现单例模式)
```java
package com.wyx.single;
/**
* @author wyx
* @version 1.0
* @date 2020/6/5 11:06
*/
/*
* 静态内部类 实现单例模式 是线程安全的
* */
public class SingleTon {
private SingleTon(){}
public static class SingleTonHolder{
private static SingleTon Holder =new SingleTon();
}
public static SingleTon getSingleTon(){
return SingleTonHolder.Holder;
}
}
```
但是使用java的反射机制可以很容易的破坏单例模式。如何防止反射破坏单例模式呢?
**可以使用枚举类型,单例模式是不能被破坏的**
> 枚举单例模式
```java
package com.wyx.single;
import sun.security.jca.GetInstance;
/**
* @author wyx
* @version 1.0
* @date 2020/6/5 10:45
*/
/*
* 使用枚举类型,创建单例模式
* */
//enum 是什么? 枚举本身就是一个class类,
public enum EnumSingle {
INSTANCE;
public static EnumSingle getInstance(){
return INSTANCE;
};
}
class Test {
public static void main(String[] args) {
EnumSingle getInstance1 = EnumSingle.getInstance();
EnumSingle getInstance2= EnumSingle.getInstance();
System.out.println(getInstance1==getInstance2); // true
}
}
```
单例模式的区别:
- 饿汉式:线程安全(不排除反射),调用效率高,不能延时加载
- 懒汉式:线程安全(不排除反射),调用效率不高,可以延时加载
- DCL懒汉式:由于JVM底层模型原因,偶尔出现问题,不建议使用
- 静态内部类式:线程安全(不排除反射),调用效率高,可以延时加载
- 枚举单例:线程安全,调用效率高,不能延时加载
# 19、深入理解CAS
> 什么是CAS
CAS:比较当前工作内存中的值,和主内存中的值,如果这个值是期望的,那么执行操作!如果不是就一直循环!(底层是自旋锁)
```java
package com.wyx.cas;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author wyx
* @version 1.0
* @date 2020/6/6 11:40
*/
/*
* 什么是CAS
* */
public class CASDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger =new AtomicInteger(2020);
//期望 , 更新 : 如果我期望的值相同,否则就更新, 没达到就不更新
/*boolean b = atomicInteger.compareAndSet(2020, 2021);
System.out.println( b+"|"+ atomicInteger.get());*/
boolean c = atomicInteger.compareAndSet(2019, 2021);
System.out.println( c +"|"+ atomicInteger.get());
//如果和期望的值相同就更新,不相同就不更新 CAS 是CPU的并发原语
}
}
```
缺点:
- 循环会耗时
- 一次性只能保证一个共享变量的原子性
- 会存在ABA问题
> CAS :ABA 问题 (狸猫换太子)
在上述的代码中,存在ABA的问题:在执行compareAndSet方法中间 如果再去执行了compareAndSet 把值改变了一此,但是最后执行compareAndSet 方法是无法去判断这个值是否已经被人改变过的。

如上图所示:如果在多线程并发的情况下,第四个是无法知道中间执行了什么操作。其实中间的值已经被修改过,然后在修改回来了。这就是ABA问题。
> 如何解决ABA问题呢? 乐观锁
ABA问题,类似与数据库中的乐观锁。我们也可以去使用乐观锁的方式去解决ABA的问题,在compareAndSet方法参数后面,加上一个类似Version 的字段。当每次修改数值成功后,就会version+1。通过version去判断此数值是否被修改过!
# 20、各种锁的理解
## 1、公平锁 、非公平锁
公平锁:非常公平,不能够插队,线程来的话 必须先来后到!
非公平锁:不公平,可以插队(默认都是非公平的锁)
Lock默认就是非公平锁:以下是源码
```java
public ReentrantLock() {
sync = new NonfairSync();
}
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
//如何改为公平锁呢? 读上面的源码可知,fair布尔值控制的公平锁或者非公平锁 ,设置为true就是公平锁了!!
Lock lock =new ReentrantLock(true);
```
## 2、可重入锁
可重入锁(递归锁):概念如下图

概念:**广义上的可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁(前提得是同一个对象或者class)** 所以Synchroni 和Lock都属于可重入锁!
> synchronized
```java
package com.wyx.locktype;
/**
* @author wyx
* @version 1.0
* @date 2020/6/6 20:14
*/
/*
* Synchronized 锁 可重入锁
* */
public class Demo01 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{phone.sms();},"A").start();
new Thread(()->{phone.sms();},"B").start();
}
}
class Phone{
public synchronized void sms(){
System.out.println(Thread.currentThread().getName()+"sms");
call();
};
public synchronized void call(){
System.out.println(Thread.currentThread().getName()+"call");
};
}
```
> lock
```java
package com.wyx.locktype;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author wyx
* @version 1.0
* @date 2020/6/6 20:14
*/
/*
* lock 锁 可重入锁
* */
public class Demo2 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{phone.sms();},"A").start();
new Thread(()->{phone.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();
}
};
}
```
## 3、自旋锁
spinlock

注:自旋锁,就是拿不到锁的情况会不停自旋循环检测来等待,不进入内核态沉睡,而是在用户态自旋尝试
作用:避免死锁!
> 自定义自旋锁的 加锁 和解锁方式
```java
package com.wyx.locktype;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
/**
* @author wyx
* @version 1.0
* @date 2020/6/6 20:45
*/
/*
* 自旋锁
* */
public class spimlockDemo {
//thread 默认是null
AtomicReference AtomicReference= new AtomicReference<>();
//加锁
public void myLock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() +"--->mylock 加锁!!");
//自旋锁 不成立(如果是没有线程就放入一个线程)
while (!AtomicReference.compareAndSet(null,thread)){
}
}
//解锁 不需要自旋
public void myUnLock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() +"--->mylock 解锁!!");
AtomicReference.compareAndSet(thread,null);
}
}
class test{
public static void main(String[] args) throws InterruptedException {
//模拟线程
spimlockDemo lock = new spimlockDemo();
new Thread(()->{
//自定义加锁
lock.myLock();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//自定义解锁
lock.myUnLock();
}
},"T1").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
//自定义加锁
lock.myLock();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//自定义解锁
lock.myUnLock();
}
},"T2").start();
}
}
```
**上述的测试可以得知,t2线程一致在循环中等待t1解锁,当t1解锁完毕后,t2线程才能解锁!!!**
## 4、死锁 (如何排查)
> 死锁是什么?

```java
package com.wyx.locktype;
import java.util.concurrent.TimeUnit;
/**
* @author wyx
* @version 1.0
* @date 2020/6/6 22:07
*/
/*
* 死锁
* */
public class DeadLockDemo {
public static void main(String[] args) {
//必须保证一个对象 !!
String lockA="lockA";
String lockB="lockB";
new Thread(new myThread(lockA,lockB),"T1").start();
new Thread(new myThread(lockB,lockA),"T2").start();
}
}
class myThread implements Runnable{
private String lockA;
private String lockB;
public myThread(String lockA, String lockB) {
this.lockA = lockA;
this.lockB = lockB;
}
@Override
public void run() {
synchronized (lockA){
System.out.println(Thread.currentThread().getName()+"lock:"+lockA+"想要获取--->"+lockB);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB){
System.out.println(Thread.currentThread().getName()+"lock:"+lockB+"想要获取--->"+lockA);
}
}
}
}
```
> 如何解决死锁?
1、使用 `jps -l ` 定位进程号
2、使用`jstack 进程号`找到死锁的问题
一键复制
编辑
Web IDE
原始数据
按行查看
历史