欢迎大家来到IT世界,在知识的湖畔探索吧!
背景
本人通过狂神说的【JUC的学习】,做了一下的实践与总结
1、什么是JUC
JUC:java.util.concurrent包的简称,学会这个包内的并发思想与会使用API即可
Juc结构
2、为什么要使用JUC
- 为了更好的支持高并发的任务
- 编程多线程时减少竞争与闭锁
3、学习前需了解
3.1、线程&进程
3.1.1、线程&进程是区别
- 线程:是操作系统能够运算调度的最小单位
- 进程:是一个运行中的程序的集合,一个进程往往由多个线程组成,至少有一个线程
比如:
你打开一个进程语雀
现在在写字,待会自动保存,就是一个线程的执行
3.1.2、java默认有几个线程&分别是什么
- 2个线程 分别是 main & GC
3.1.3、java可以开启线程吗?
- 不可以!通过源码可以观测到 执行的是 private native void start0();本地方法,本地方法是由c++操作,java运行在虚拟机之上,不能直接操作硬件
3.1.4、线程有多少状态
- 6个,从源码上可以观测到
public enum State {
// 新生
NEW,
// 运行
RUNNABLE,
// 阻塞
BLOCKED,
// 等待 ==》 死死的等
WAITING,
//超时等待 ==》 时间到了,就不等了
TIMED_WAITING,
//终止
TERMINATED;
}
欢迎大家来到IT世界,在知识的湖畔探索吧!
3.2、并发&并行
- 并发:多线程操作同一个资源,交替执行(CPU一核,可以模拟出多个线程,快速交替执行)
- 并行:多个线程同时执行 (CPU多核:多个线程同时执行,使用线程池)
CPU有多少核,线程就可以并行执行多少个??
3.2.1、如何查看CPU核数
- 计算机=》管理=》设备管理 =》处理器
- 代码查看
欢迎大家来到IT世界,在知识的湖畔探索吧!public class Test1 { public static void main(String[] args) { //获取cpu的核数 //cpu密集型,io密集型 System.out.println(Runtime.getRuntime().availableProcessors()); }}
3.2.2、并发的本质
- 充分利用CPU资源
3.3、wait&sleep
1.来自不同的类
- wait:来着Object类
- sleep:来着Thread类
2.关于锁的释放
- wait:会释放锁
- sleep:不会释放锁,抱着锁睡觉
3.使用性质不同
- wait:必须在同步代码块中
- sleep:任何地方
4.异常情况【中断异常】
- wait:会捕获异常
- sleep:会捕获异常,存在超时等待
3.4、什么是锁,锁的是什么?
通过8锁现象说明,通过打印【发短信、打电话】来理解
锁的是 对象与Class
先总结:
- 只要是使用同一把锁,谁先调用,谁就先执行
- 不管睡眠多久,只要不释放锁,其他线程就一直等待
- 普通不会等待,不受锁的影响
- 2把锁,互不影响
- 一个class模板就一个class对象,不管静态同步方法有几个对象调用,谁先调用谁先执行
- new 多少个对象,就有多少把锁,一个class对象就一把锁
- 标准情况下
package com.cnl.juc.test2;
/**
* 标准情况下,先发短信还是打电话
*/
public class Lock01 {
public static void main(String[] args) {
Lock1 lock1 = new Lock1();
new Thread(() -> lock1.send()).start();
new Thread(() -> lock1.call()).start();
}
}
class Lock1 {
//synchronized锁的对象是方法的调用者
//两个方法用的是同一个锁,谁先拿到谁先执行
public synchronized void send() {
System.out.println("发短信");
}
public synchronized void call() {
System.out.println("打电话");
}
}
结果:先发短语,再打电话
原因:该锁是锁对象,同一把锁,所以谁先调用先执行
总结:只要是使用同一把锁,谁先调用,谁就先执行
2、先执行的延迟4s
欢迎大家来到IT世界,在知识的湖畔探索吧!package com.cnl.juc.test2;
import java.util.concurrent.TimeUnit;
/**
* 发短信延迟4s
*/
public class Lock02 {
public static void main(String[] args) {
Lock2 lock2 = new Lock2();
new Thread(() -> lock2.send()).start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> lock2.call()).start();
}
}
class Lock2 {
public synchronized void send() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call() {
System.out.println("打电话");
}
}
结果:发短信,打电话
原因:该锁是锁对象,同一把锁,所以谁先调用先执行
总结:只要是使用同一把锁,谁先调用,谁就先执行,不管睡眠多久,只要不释放锁,其他线程就一直等待
3、加入一个普通方法
package com.cnl.juc.test2;
import java.util.concurrent.TimeUnit;
public class Lock03 {
public static void main(String[] args) {
Lock3 lock = new Lock3();
new Thread(() -> lock.send()).start();
new Thread(() -> lock.print()).start();
new Thread(() -> lock.call()).start();
}
}
class Lock3 {
public synchronized void send() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call() {
System.out.println("打电话");
}
//这里没有锁,不是同步方法,不受锁的影响
public void print() {
System.out.println("hello");
}
}
结果:hello、发短信、打电话
原因:普通方法,不受锁的影响
总结:只要是使用同一把锁,谁先调用,谁就先执行,不管睡眠多久,只要不释放锁,其他线程就一直等待,但是普通不会等待,不受锁的影响
4、两个对象,两个同步方法
package com.cnl.juc.test2;
import java.util.concurrent.TimeUnit;
public class Lock04 {
public static void main(String[] args) {
Lock4 lock1 = new Lock4();
Lock4 lock2 = new Lock4();
new Thread(() -> lock1.send()).start();
new Thread(() -> lock2.call()).start();
}
}
class Lock4 {
public synchronized void send() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call() {
System.out.println("打电话");
}
}
结果:打电话、发短信
原因:2个对象,就相当于2把锁,互不影响,由于发短信睡眠2s,所以才后面
总结:2把锁,互不影响
5、2个静态同步方法
package com.cnl.juc.test2;
import java.util.concurrent.TimeUnit;
public class Lock05 {
public static void main(String[] args) {
Lock5 lock5 = new Lock5();
new Thread(() -> lock5.send()).start();
new Thread(() -> lock5.call()).start();
}
}
class Lock5 {
//synchronized锁的对象是方法的调用者
//static 静态方法 类一加载就有了!class模板,锁的是class对象Class<Phone3> phone3Class = Phone3.class;
//两个方法用的是同一个锁
public static synchronized void send() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static synchronized void call() {
System.out.println("打电话");
}
}
结果:发短信、打电话
原因:静态同步方法是锁Class模板,一把锁,谁先调用谁先执行
总结:只要是使用同一把锁,谁先调用,谁就先执行,不管睡眠多久,只要不释放锁,其他线程就一直等待
6、2个静态同步方法,2个对象
package com.cnl.juc.test2;
import java.util.concurrent.TimeUnit;
public class Lock06 {
public static void main(String[] args) {
Lock6 lock1 = new Lock6();
Lock6 lock2 = new Lock6();
new Thread(() -> lock1.send()).start();
new Thread(() -> lock2.call()).start();
}
}
class Lock6 {
public static synchronized void send() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static synchronized void call() {
System.out.println("打电话");
}
}
结果:发短信、打电话
原因:静态方法,在类加载是就加载好了,class模板锁的是class对象,一个class模板就一个class对象,所以使用的是同一把锁
总结:一个class模板就一个class对象,不管静态同步方法有几个对象调用,谁先调用谁先执行
7、一个静态同步方法,一个普通同步方法,一个对象
package com.cnl.juc.test2;
import java.util.concurrent.TimeUnit;
public class Lock07 {
public static void main(String[] args) {
Lock7 lock = new Lock7();
new Thread(() -> lock.send()).start();
new Thread(() -> lock.call()).start();
}
}
class Lock7 {
public static synchronized void send() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call() {
System.out.println("打电话");
}
}
结果:打电话、发短信
原因:一个对象,一个class对象,2把锁,由于发短信睡眠4s,所以先打电话
总结:new 多少个对象,就有多少把锁,一个class对象就一把锁
8、两个对象,一个静态同步方法,一个普通同步方法
package com.cnl.juc.test2;
import java.util.concurrent.TimeUnit;
public class Lock08 {
public static void main(String[] args) {
Lock8 lock1 = new Lock8();
Lock8 lock2 = new Lock8();
new Thread(() -> lock1.send()).start();
new Thread(() -> lock2.call()).start();
}
}
class Lock8 {
public static synchronized void send() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call() {
System.out.println("打电话");
}
}
结果:打电话、发短信
原因:一个对象,一个class对象,2把锁,由于发短信睡眠4s,所以先打电话
总结:new 多少个对象,就有多少把锁,一个class对象就一把锁
4、Lock锁
- 默认非公平锁,可以插队,也可以设置公平
4.1、传统的Synchronized锁&Lock锁对比
4.1.1、不加锁
package com.cnl.juc.test1;
/**
* @author cnl
*/
public class Demo01 {
public static void main(String[] args) {
Data data = new Data();
new Thread(()-> {
for (int i = 0; i < 60; i++) {
data.sail();
}
}, "A").start();
new Thread(()-> {
for (int i = 0; i < 60; i++) {
data.sail();
}
}, "B").start();
new Thread(()-> {
for (int i = 0; i < 60; i++) {
data.sail();
}
}, "C").start();
}
}
class Data {
private int number = 70;
public void sail() {
if (number > 0) {
System.out.println(Thread.currentThread().getName() + ":" + (number--) + ",剩余:" + number);
}
}
}
4.1.2、Synchronized版
public synchronized void sail() { if (number > 0) { System.out.println(Thread.currentThread().getName() + ":" + (number--) + ",剩余:" + number); } }
4.1.3、Lock版
package com.cnl.juc.test1;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;public class Demo2 { public static void main(String[] args) { Data1 data = new Data1(); new Thread(()-> { for (int i = 0; i < 60; i++) { data.sail(); } }, "A").start(); new Thread(()-> { for (int i = 0; i < 60; i++) { data.sail(); } }, "B").start(); new Thread(()-> { for (int i = 0; i < 60; i++) { data.sail(); } }, "C").start(); }}class Data1 { private int number = 70; private Lock lock = new ReentrantLock(); public void sail() { lock.lock(); try { if (number > 0) { System.out.println(Thread.currentThread().getName() + ":" + (number--) + ",剩余:" + number); } } finally { lock.unlock(); } }}
4.1.3、总结
Sychronized |
Lock |
java关键字 |
java类 |
无法获取锁状态 |
可以获取锁状态 tryLock |
自动释放锁 |
手动释放锁 unLock |
2个线程同时执行,2号线程会傻傻的等待1号线程执行完 |
不一定等待下去 |
可重入锁,不可以中断,非公平 |
可重入锁,不可以中断,【非公平(可选择)】 |
适合锁少量的代码同步问题 |
适合锁大量的代码同步问题 |
4.2、生产者与消费者问题
4.2.1、Sychronized版
package com.cnl.juc.test1;
public class Demo03 {
public static void main(String[] args) {
Print print = new Print();
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
print.incr();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "A").start();
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
print.dec();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "B").start();
}
}
//等待 业务 通知
class Print {
private int number = 0;
public synchronized void incr() throws InterruptedException {
//注意:根据官方文档描述,如果使用if的话,会存在虚假唤醒
//因为if只会判断一次,当被唤醒时,不会在意条件释放满足,会执行下面逻辑
//所以要使用while
if (number != 0) { // 该测试只有2个线程,所以不会出现虚假唤醒,当存在多个时,就会出现问题
this.wait();
}
number = 1;
this.notifyAll();
System.out.println(Thread.currentThread().getName() + ":" + number);
}
public synchronized void dec() throws InterruptedException {
//注意:根据官方文档描述,如果使用if的话,会存在虚假唤醒
//因为if只会判断一次,当被唤醒时,不会在意条件释放满足,会执行下面逻辑
//所以要使用while
if (number != 1) { // 该测试只有2个线程,所以不会出现虚假唤醒,当存在多个时,就会出现问题
this.wait();
}
number = 0;
this.notifyAll();
System.out.println(Thread.currentThread().getName() + ":" + number);
}
}
等待 业务 通知 ,按照这个逻辑
4.2.2、Lock版
- 使用lock中的Condition
Sychronized |
Lock |
wait() |
contion.await() |
notifyAll() |
contion.signalAll() |
package com.cnl.juc.test1;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo04 {
public static void main(String[] args) {
Print01 print = new Print01();
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
print.incr();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "A").start();
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
print.dec();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "B").start();
}
}
class Print01 {
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
private int number = 0;
public void incr() throws InterruptedException {
lock.lock();
try {
//注意:根据官方文档描述,如果使用if的话,会存在虚假唤醒
//因为if只会判断一次,当被唤醒时,不会在意条件释放满足,会执行下面逻辑
//所以要使用while
while (number != 0) {
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName() + ":" + number);
condition.signalAll();
} finally {
lock.unlock();
}
}
public void dec() throws InterruptedException {
lock.lock();
try {
//注意:根据官方文档描述,如果使用if的话,会存在虚假唤醒
//因为if只会判断一次,当被唤醒时,不会在意条件释放满足,会执行下面逻辑
//所以要使用while
while (number != 1) {
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName() + ":" + number);
condition.signalAll();
} finally {
lock.unlock();
}
}
}
4.2.3、condition实现精准通知唤醒
package com.cnl.juc.test1;import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;public class Demo05 { public static void main(String[] args) { Print02 print = new Print02(); new Thread(() -> { try { for (int i = 0; i < 10; i++) { print.printA(); } } catch (InterruptedException e) { e.printStackTrace(); } }, "A").start(); new Thread(() -> { try { for (int i = 0; i < 10; i++) { print.printC(); } } catch (InterruptedException e) { e.printStackTrace(); } }, "C").start(); new Thread(() -> { try { for (int i = 0; i < 10; i++) { print.printB(); } } catch (InterruptedException e) { e.printStackTrace(); } }, "B").start(); }}class Print02 { private final Lock lock = new ReentrantLock(); private final Condition conditionA = lock.newCondition(); private final Condition conditionB = lock.newCondition(); private final Condition conditionC = lock.newCondition(); private int number = 0; public void printA() throws InterruptedException { lock.lock(); try { while (number != 0) { conditionA.await(); } number = 1; System.out.println(Thread.currentThread().getName() + ":AAAA"); conditionB.signal(); } finally { lock.unlock(); } } public void printB() throws InterruptedException { lock.lock(); try { while (number != 1) { conditionB.await(); } number = 2; System.out.println(Thread.currentThread().getName() + ":BBBB" ); conditionC.signal(); } finally { lock.unlock(); } } public void printC() throws InterruptedException { lock.lock(); try { while (number != 2) { conditionC.await(); } number = 0; System.out.println(Thread.currentThread().getName() + ":CCCC"); conditionA.signal(); } finally { lock.unlock(); } }}
5、集合
5.1、List集合
问题:
- List的底层是什么
- 数组,数组的特征就不多说了
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
5.1.1、单线程【安全】
package com.cnl.juc.test3;
import java.util.ArrayList;
import java.util.List;
/**
* @author cnl
*/
public class ListTest {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 20; i++) {
list.add(i);
System.out.println("list = " + list);
}
}
}
总结:单线程安全
5.1.2、多线程
- 普通使用
package com.cnl.juc.test3;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* @author cnl
*/
public class ListTest {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 20; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println("list = " + list);
}).start();
}
}
}
总结:普通的List的集合是不安全的
- 解决
1、使用 Vector 【不推荐使用】
package com.cnl.juc.test3;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.Vector;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* @author cnl
*/
public class ListTest {
public static void main(String[] args) {
List<String> list = new Vector<>();
for (int i = 0; i < 20; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println("list = " + list);
}).start();
}
}
}
源码:
2、使用 CopyOnWriteArrayList
package com.cnl.juc.test3;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* @author cnl
*/
public class ListTest {
public static void main(String[] args) {
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 20; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println("list = " + list);
}).start();
}
}
}
源码:
3、使用 Collections.synchronizedList
package com.cnl.juc.test3;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* @author cnl
*/
public class ListTest {
public static void main(String[] args) {
List<String> list = Collections.synchronizedList(new ArrayList<>());
for (int i = 0; i < 20; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println("list = " + list);
}).start();
}
}
}
源码
5.2、Set集合
问题:
1、hashSet的底层是什么
- 就是一个hashMap的key,因为key是无序的,key是不可重复的,所以这也是HashSet是无序与不可重复的原因
public HashSet() {
map = new HashMap<>();
}
// add 的本质就是 map 的 key key是无法重复的
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
private static final Object PRESENT = new Object();//这是一个不变的值
5.2.1、单线程【安全】
public class SetTest {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
for (int i = 0; i < 20; i++) {
for (int i = 0; i < 20; i++) {
set.add(i +"");
System.out.println("set = " + set);
}
}
}
}
5.2.2、多线程
- 普通使用 【不安全】
package com.cnl.juc.test3;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
public class SetTest {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
for (int i = 0; i < 20; i++) {
new Thread(() -> {
set.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println("set = " + set);
}).start();
}
}
}
- 解决
1、使用 CopyOnWriteArraySet
public class SetTest {
public static void main(String[] args) {
Set<String> set = new CopyOnWriteArraySet<>();
for (int i = 0; i < 20; i++) {
new Thread(() -> {
set.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println("set = " + set);
}).start();
}
}
}
源码:
2、使用 Collections.synchronizedSet
public class SetTest {
public static void main(String[] args) {
Set<String> set = Collections.synchronizedSet(new HashSet<>());
for (int i = 0; i < 20; i++) {
new Thread(() -> {
set.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println("set = " + set);
}).start();
}
}
}
源码:
5.3、Map集合
问题:
- Map的底层是什么
- 暂时不写
- 为什么负载因子是 0.75
- 为什么初始化扩容是16
5.3.1、单线程【安全】
public class MapTest {
public static void main(String[] args) {
HashMap<String, String> map = new HashMap<>();
for (int i = 0; i < 20; i++) {
map.put(UUID.randomUUID().toString().substring(0, 5), UUID.randomUUID().toString().substring(0, 5));
System.out.println("map = " + map);
}
}
}
5.3.2、多线程
- 普通方法【不安全】
public class MapTest {
public static void main(String[] args) {
HashMap<String, String> map = new HashMap<>();
for (int i = 0; i < 30; i++) {
new Thread(() -> {
map.put(UUID.randomUUID().toString().substring(0, 5), UUID.randomUUID().toString().substring(0, 5));
System.out.println("map = " + map);
}).start();
}
}
}
- 解决
1、使用 Collections.synchronizedMap
public class MapTest {
public static void main(String[] args) {
Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
for (int i = 0; i < 30; i++) {
new Thread(() -> {
map.put(UUID.randomUUID().toString().substring(0, 5), UUID.randomUUID().toString().substring(0, 5));
System.out.println("map = " + map);
}).start();
}
}
}
源码:
2、使用 ConcurrentHashMap
public class MapTest {
public static void main(String[] args) {
Map<String, String> map = new ConcurrentHashMap<>();
for (int i = 0; i < 30; i++) {
new Thread(() -> {
map.put(UUID.randomUUID().toString().substring(0, 5), UUID.randomUUID().toString().substring(0, 5));
System.out.println("map = " + map);
}).start();
}
}
}
源码:
6、并发辅助类
6.1、CountDownLatch
1、概述
2、原理
countDownLatch.countDown(); //数量减1
countDownLatch.await();// 等待计数器归零,然后再向下执行
每次有线程调用countDown()数量-1,假设计数器变为0,countDownLatch.await();就会被唤醒,继续执行
package com.cnl.juc.test4;
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
//相当于计数器
//每次调用countDown =》 -1
//当 计数器 = 0 时,才会往下走
//若是没有达到0, 会一直阻塞
CountDownLatch countDownLatch = new CountDownLatch(5);
for (int i = 1; i <= 5; i++) {
new Thread(()-> {
System.out.println(Thread.currentThread().getName() + "开始了");
countDownLatch.countDown();
}).start();
}
countDownLatch.await();
System.out.println("main 执行完");
}
}
6.2、cyclicBarrier
1、概述 【加法计数器】
2、案列
package com.cnl.juc.test4;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
public static void main(String[] args) {
//线程阻塞,当达到指定的数量时,才会释放
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
System.out.println("卡片收集完毕");
});
for (int i = 1; i <= 5; i++) {
int temp = i;
new Thread(()-> {
System.out.println(Thread.currentThread().getName() + "收集卡片"+ temp);
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
System.out.println("结束了");
}
}
6.3、Semaphore
1、概述
2、原理
semaphore.acquire(); //获取信号量,假设如果已经满了,等待信号量可用时被唤醒
semaphore.release(); //释放信号量
作用: 多个共享资源互斥的使用!并发限流,控制最大的线程数
3、案列
package com.cnl.juc.test4;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
* @author cnl
*/
public class SemaphoreDemo {
public static void main(String[] args) {
//案列 抢车位
//设置3个车位,7辆车
//当3个车位占满时,其他的车需要等待
//车子离开时,才会抢占车位
Semaphore semaphore = new Semaphore(3);
for (int i = 1; i <= 7; i++) {
int finalI = i;
new Thread(()-> {
try {
semaphore.acquire();
System.out.println(finalI + "抢到车位");
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
System.out.println(finalI + "离开车位");
}
}).start();
}
}
}
7、读写锁ReadWritelock
1、概述
- 读可以被多个线程同时读,写的时候只能有一个线程去写
- 独占锁(写锁) 一次只能被一个线程占有
- 共享锁(读锁) 可以同时被多个线程占有
2、案列
- 未加锁
package com.cnl.juc.test5;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWritelockDemo {
public static void main(String[] args) {
MyCatch myCatch = new MyCatch();
//写入
for (int i = 0; i <= 5; i++) {
final int temp = i;
new Thread(()->{
myCatch.write(temp+"",temp+"");
},String.valueOf(i)).start();
}
//读取
for (int i = 0; i <=5; i++) {
final int temp = i;
new Thread(()->{
myCatch.read(temp+"");
},String.valueOf(i)).start();
}
}
}
class MyCatch {
private volatile Map<String,Object> map = new HashMap<>(0);
//存
public void write(String key,Object value) {
System.out.println(Thread.currentThread().getName()+"写入"+value);
map.put(key, value);
System.out.println(Thread.currentThread().getName()+"写入成功");
}
//取
public void read(String key) {
System.out.println(Thread.currentThread().getName()+"读取"+key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName()+"读取成功");
}
}
- 加锁
package com.cnl.juc.test5;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWritelockDemo {
public static void main(String[] args) {
MyCatchLock myCatch = new MyCatchLock();
//写入
for (int i = 0; i <= 5; i++) {
final int temp = i;
int finalI = i;
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
myCatch.write(finalI);
},String.valueOf(i)).start();
}
//读取
for (int i = 0; i <=5; i++) {
final int temp = i;
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
myCatch.read();
},String.valueOf(i)).start();
}
}
}
class MyCatchLock {
private volatile int num = 0;
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public void read() {
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"读取"+num);
} finally {
readWriteLock.readLock().unlock();
}
}
public void write(int newNum){
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"写入"+ (newNum));
num+=newNum;
System.out.println(Thread.currentThread().getName()+"写入成功");
} finally {
readWriteLock.writeLock().unlock();
}
}
}
8、队列Queue
- 概述
描述
- 写入:如果队列满了,就必须阻塞等待
- 取出:如果队列是空的就必须阻塞等待生产
结构
8.1、阻塞队列BlockingQueue
1、问题:
- 什么情况下会使用阻塞队列:多线程并发处理,线程池
2、四组api
方式 |
抛出异常 |
有返回值,不抛出异常 |
阻塞等待 |
超时等待 |
添加 |
add |
offer |
put |
offer(,) |
移除 |
remove |
poll |
take |
poll(,) |
判断队列首 |
element |
peek |
– |
– |
3、分析与验证
- 抛出异常
package com.cnl.juc.test5;
import java.util.concurrent.ArrayBlockingQueue;
public class BlockingQueueDemo01 {
public static void main(String[] args) {
test01();
}
public static void test01() {
//队列的大小
ArrayBlockingQueue<String> blockingQueue = new ArrayBlockingQueue<String>(3);
System.out.println("===新增a,b,c===");
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
System.out.println(blockingQueue.add("c"));
//判断队首
System.out.println("===判断队首===");
System.out.println(blockingQueue.element());
System.out.println("===移除===");
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
//当队列中无数据时,再移除,会抛异常
System.out.println(blockingQueue.remove());
}
}
- 有返回值,不抛出异常
package com.cnl.juc.test6;
import java.util.concurrent.ArrayBlockingQueue;
public class BlockingQueueDemo02 {
public static void main(String[] args) {
test01();
}
public static void test01() {
//队列的大小
ArrayBlockingQueue<String> blockingQueue = new ArrayBlockingQueue<String>(3);
System.out.println("===新增a,b,c===");
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
//判断队首
System.out.println("===判断队首===");
System.out.println(blockingQueue.peek());
System.out.println("===移除===");
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
//当队列中无数据时,再获取,返回null
System.out.println(blockingQueue.poll());
}
}
- 阻塞等待
package com.cnl.juc.test6;
import java.util.concurrent.ArrayBlockingQueue;
public class BlockingQueueDemo03 {
public static void main(String[] args) throws InterruptedException {
test01();
}
public static void test01() throws InterruptedException {
//队列的大小
ArrayBlockingQueue<String> blockingQueue = new ArrayBlockingQueue<String>(3);
System.out.println("===新增a,b,c===");
blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c");
System.out.println("===移除===");
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
//当队列中无数据时,再获取,会一直等待存值取出
System.out.println(blockingQueue.take());
}
}
- 超时等待
package com.cnl.juc.test6;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
public class BlockingQueueDemo04 {
public static void main(String[] args) throws InterruptedException {
test01();
}
public static void test01() throws InterruptedException {
//队列的大小
ArrayBlockingQueue<String> blockingQueue = new ArrayBlockingQueue<String>(3);
System.out.println("===新增a,b,c===");
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
blockingQueue.offer("d", 2,TimeUnit.SECONDS);
//判断队首
System.out.println("===判断队首===");
System.out.println(blockingQueue.peek());
System.out.println("===移除===");
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
//当队列中无数据时,等待2s,若是没有值返回null
System.out.println(blockingQueue.poll(2, TimeUnit.SECONDS));
}
}
8.2、同步队列SychronizedQueue
1、概述
没有容量,进去一个元素,必须等待取出来之后,才能再往里面放一个元素
有且只有一个元素
2、案列
package com.cnl.juc.test7;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
public class SyncQueueDemo {
public static void main(String[] args) {
//同步队列
SynchronousQueue<String> synchronousQueue = new SynchronousQueue<>();
new Thread(()-> {
try {
System.out.println(Thread.currentThread().getName() + "put 1");
synchronousQueue.put("1");
System.out.println(Thread.currentThread().getName() + "put 2");
synchronousQueue.put("2");
System.out.println(Thread.currentThread().getName() + "put 3");
synchronousQueue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "A").start();
new Thread(()-> {
try {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() +"=>" + synchronousQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() +"=>" + synchronousQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() +"=>" + synchronousQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "B").start();
}
}
9、线程池
9.1、池化技术
- 程序的运行本质:占用系统的资源!优化资源的使用
- 池化技术:事先准备好一些资源,有人要用就来拿,用完之后归还,就不需要频繁的创建与销毁。
9.2、池化的优点
- 降低资源的消耗
- 提高相应速度
- 方便管理
- 线程池可以复用,可以控制最大的并发量,管理线程
9.3、三大方法、七大参数、四大拒绝策略
9.3.1、三大方法
从阿里的开发文档中,可以得知,线程池不允许使用Executors去创建,而是使用它的子类ThreadPoolExecutor的方式,因为这样的方式可以让自己明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors各个方法的弊端
1、方法一:newFixedThreadPool()
- 线程的个数固定
- 最大的线程个数与核心线程个数相同
package com.cnl.juc.test8;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class NewFixedThreadPoolDemo {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i < 100; i++) {
executorService.execute(() -> System.out.println(Thread.currentThread().getName()+"ok"));
}
//线程池用完,程序结束,关闭线程池
try {
executorService.shutdown();
} catch (Exception e) {
e.printStackTrace();
} finally {
}
}
}
2、方法二:newSingleThreadExecutor()
- 只有一个线程
package com.cnl.juc.test8;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class NewSingleThreadExecutorDemo {
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
executorService.execute(() -> System.out.println(Thread.currentThread().getName()+"ok"));
}
//线程池用完,程序结束,关闭线程池
try {
executorService.shutdown();
} catch (Exception e) {
e.printStackTrace();
} finally {
}
}
}
3、方法三:newCachedThreadPool
- 最大线程 21亿
package com.cnl.juc.test8;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class NewCachedThreadPoolDemo {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
executorService.execute(() -> System.out.println(Thread.currentThread().getName()+"ok"));
}
//线程池用完,程序结束,关闭线程池
try {
executorService.shutdown();
} catch (Exception e) {
e.printStackTrace();
} finally {
}
}
}
总结:
1)newFixedThreadPool和newSingleThreadExecutor:
主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。
2)newCachedThreadPool和newScheduledThreadPool:
主要问题是线程数最大数是Integer.MAX_VALUE(约为21亿),可能会创建数量非常多的线程,甚至OOM。
9.3.2、七大参数
1、源码分析(三大方法的源码)
// 永远只有一个线程
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue()));
}
//核心线程0,最大线程21亿
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue());
}
//固定线程个数,核心与最大一样,无法扩展
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
}
2、参数说明
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;
}
解释:
银行业务排队理解
银行总共5个窗口,今天星期六,只有3个人值班,等待区的座位只有10个位置
核心线程 =》值班的3个窗口
线程总数 =》 5个窗口
队列 =》 10个座位
假设1
今天并发办理业务的人数不超过3人,刚好持续办理
假设2
今天并发办理业务的人数不超过5人,把放假的2人,叫回来加班。
若是超时时间 30m,加班回来的人忙完一阵,30m后没有人来办理,就让他们回去继续休假
假设3
今天来的人很多,等待区已经坐满了,但是还有人来,就采取一些措施,比如:不让办理了,比如把办理很久还没完成的,通知下次办理等等。
3、最大线程池应该如何定义
- cpu密集行,12条线程同时执行,几核心就是几,可以保证cpu的效率最高
- io密集型>判断你的程序中十分耗io的线程
9.3.3、四大拒绝策略
- AbortPolicy:直接抛出异常,默认策略;
- CallerRunsPolicy:用调用者所在的线程来执行任务;
- DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
- DiscardPolicy:直接丢弃任务;
10.四大函数
- 只有一个方法的接口
10.1、函数型接口Function
- 传入T,返回R
package com.cnl.juc;import java.util.function.Function;/** * @author cnl */public class FunctionDemo { public static void main(String[] args) { Function<String, Boolean> function = (str-> { System.out.println("str = " + str); return str.isEmpty(); }); System.out.println(function.apply("abc")); }}
10.2、断定型接口Predicate
- 传T,返回布尔值
package com.cnl.juc.test9;import java.util.function.Predicate;public class PredicateDemo { public static void main(String[] args) { Predicate<String> predicate1 = new Predicate<String>() { @Override public boolean test(String str) { return str.isEmpty(); } }; System.out.println(predicate1.test("ass")); Predicate<String> predicate2 = str -> str.isEmpty(); System.out.println(predicate2.test("")); }}
10.3、消费型接口Consumer
- 只传参,无返回值
package com.cnl.juc.test9;import java.util.function.Consumer;public class ConsumerDemo { public static void main(String[] args) { Consumer<String> consumer1 = new Consumer<String>() { @Override public void accept(String s) { System.out.println("s = " + s); } }; consumer1.accept("123"); Consumer<String> consumer2 = s -> System.out.println("s = " + s); consumer2.accept("456"); }}
10.4、供给型接口Supplier
- 无参数,只返回
package com.cnl.juc.test9;import java.util.function.Supplier;public class SupplierDemo { public static void main(String[] args) { Supplier<String> supplier1 = new Supplier<String>() { @Override public String get() { return "123"; } }; System.out.println(supplier1.get()); Supplier<String> supplier2 = () -> "123"; System.out.println(supplier2.get()); }}
11、流式计算
public class Test { public static void main(String[] args) { User user1 = new User(1,21,"张三"); User user2 = new User(2,23,"李四"); User user3 = new User(3,29,"王五"); User user4 = new User(4,18,"赵六"); //集合存储 List<User> userList = Arrays.asList(user1, user2, user3, user4); //计算交给流 userList.stream().filter(user -> {return user.getId()%2==0;}) .filter(user -> {return user.getAge()>20;}) .map(user -> {return user.getName().toUpperCase(Locale.ROOT);}) .sorted((u1,u2)->{return u2.compareTo(u1); }) // .limit(1)//分页 .forEach(System.out::println); }}
12、ForkJoin
1、什么是ForkJoin
- jdk1.7提出来,并发执行,提高效率,数据量大
- 大数据Map Reduce,把一个大任务拆分成几个小任务
2、特点
- 工作窃取。就是A线程未执行完,B线程执行完,B线程可以去帮A线程执行后面的工作的,由于双端队列的存在。
3、案例
- 在数据量少的情况下 for > forkJoin、stream流
- 在大数据情况下 forkJoin、stream流 > for
package com.cnl.juc.test10;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.LongStream;
/**
* @author cnl
*/
public class Demo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
test3();
}
public static void test1() {
long start = System.currentTimeMillis();
long sum = 0L;
for (long i = 0L; i <= 100_000_000; i++) {
sum += i;
}
System.out.println("sum = " + sum);
System.out.println("时间 = " + (System.currentTimeMillis() - start));
}
public static void test2() throws ExecutionException, InterruptedException {
long start = System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinHandle forkJoinHandle = new ForkJoinHandle(0L, 100_000_000L);
ForkJoinTask<Long> submit = forkJoinPool.submit(forkJoinHandle);
System.out.println("sum = " + submit.get());
System.out.println("时间 = " + (System.currentTimeMillis() - start));
}
public static void test3() {
long start = System.currentTimeMillis();
long sum = LongStream.rangeClosed(0L, 100_000_000L).parallel().sum();
System.out.println("sum = " + sum);
System.out.println("时间 = " + (System.currentTimeMillis() - start));
}
}
package com.cnl.juc.test10;
import lombok.Data;
import java.util.concurrent.RecursiveTask;
/**
* @author cczha
*/
public class ForkJoinHandle extends RecursiveTask<Long> {
private Long start;
private Long end;
//临界值
private Long temp = 10000L;
public ForkJoinHandle(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;
}
}else {
Long middle = (start + end) / 2;
ForkJoinHandle task1 = new ForkJoinHandle(start, middle);
ForkJoinHandle task2 = new ForkJoinHandle(middle + 1, end);
task1.fork();
task2.fork();
sum = task1.join() + task2.join();
}
return sum;
}
}
13、异步回调
有返回值:CompletableFuture.supplyAsync(Runnable, Executor)
无返回值:CompletableFuture.runAsync(Runnable, Executor)
package com.cnl.juc.test11;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
/**
* @author cnl
*/
public class Demo {
public static void main(String[] args) {
notReturn();
returnInteger();
}
private static void returnInteger() {
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + "run");
int i = 10/0;
return 1024;
});
try {
Integer integer = future.whenComplete((t, u) -> {
System.out.println("t = " + t);
System.out.println("u = " + u);
}).exceptionally(e -> {
System.out.println("e.getMessage() = " + e.getMessage());
return 111;
}).get();
System.out.println("integer = " + integer);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
private static void notReturn() {
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "run");
});
System.out.println("111111");
try {
future.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
14、JMM
14.1、什么是JMM
- java的内存模型,不存在的东西,概念,约定
14.2、JMM约定
- 线程解锁前,必须把共享变量立刻刷回主存
- 线程枷锁前,必须读取主存中的最新值到工作的内存中
- 加锁和解锁是同一把锁
14.3、8种操作
- lock(锁定):作用于主内存中的变量,把一个变量表示为一个线程独占的状态
- unlock(解锁):作用于主内存中的变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
- read(读取):作用于主内存的变量,把一个变量的值从主内存读取到线程的工作内存中,以便于后面的load操作
- load(载入):作用于工作内存中的变量,把read操作从主存中得到的变量值放入工作内存中的变量副本
- use(使用):作用于工作内存中的变量,把工作内存中的一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作
- assign(赋值):作用于工作内存中的变量,把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时就执行这个操作
- store(存储):作用于工作内存中的变量,把工作内存中一个变量的值传送给主存中以便于后面的write操作
- write(写入):作用于主内存中的变量,把store操作从工作内存中得到的变量的值放入主内存的变量中
14.4、问题
private static int num = 0; public static void main(String[] args) throws InterruptedException {//main线程 new Thread(()->{//线程1 while (num == 0) { } }).start(); TimeUnit.SECONDS.sleep(1); num = 1; System.out.println(num); //程序一直在执行,线程1不知道主存中的值发生了变化 }
会死循环,线程1从主存中获取num = 0,main线程把主存中的num = 1,但是线程1的工作主存并不会去监测主存中的num,所以导致无法跳出循环。为了解决可见性,引出 volatile
15、Volatile
15.1、什么是volatile
- java虚拟机提供的轻量级的同步机制
15.2、三大特征
- 保证可见性
- 不保证原子性
- 由于内存屏障,禁止指令重排
15.3、验证三大特征
15.3.1、可见性
//加了volatile可以保证可见性,不加进入死循环
private volatile static int num = 0;
public static void main(String[] args) throws InterruptedException {//main线程
new Thread(()->{//线程1
while (num == 0) {
}
}).start();
TimeUnit.SECONDS.sleep(1);
num = 1;
System.out.println(num);
//程序一直在执行,线程1不知道主存中的值发生了变化
}
15.3.2、不保证原子性
原子性:不可分割
线程a在执行任务的时候,不能被打扰,也不能被分割,要么同时成功,要么同时失败
private volatile static int num = 0;
public static void add() {
num++;
}
public static void main(String[] args) {
//理论上num结果应该为20000,加volatile还是不能加到2万,加Synchronized可以
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount()>2) {
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+num);
}
如果不加Synchronizd和Lock怎么保证原子性
num++;//不是原子性操作
使用原子类解决问题
private volatile static AtomicInteger num = new AtomicInteger();
public static void add() {
// num++;
num.getAndIncrement();//AtomicInteger+1方法CAS效率高
}
public void main(String[] args) {
//理论上num结果应该为20000,加volatile还是不能加到2万,加Synchronized可以
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount()>2) {
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+num);
}
这些类的底层都和操作系统挂钩,直接在内存中修改值,Unsafe类是一个很特殊的存在
15.3.3、指令重排
- 什么是指令重排
你写的程序,计算机并不是按照你写的那样去执行
源代码->编译器优化->指令并行可能重排->内存系统可能重排->执行
int x=1; //1
int y=1; //2
x=x+5; //3
y=x+x; //4
顺序:我们期望的是1234,但是可能是21344,1324
不可能是4123,==处理器在执行指定重排的时候,考虑数据之间的依赖性
多个线程时,由于指令重排,就会导致数据问题
- 可以采用volatile避免指令重排
内存屏障,CPU指令。作用:
- 保证特定的操作顺序执行
- 可以保证某些变量的可见性
16、深入理解CAS
16.1、什么是CAS
- cas:比较并交换,也就是说比较当前的内存的值与主内存的值,如果这个值是期望的,则执行操作,否则会一直循环
- 缺点
- 循环会耗时
- 一次只能确保一个共享变量的原子性
- ABA问题
/**
* CAS:比较且替换
*/
public class CASDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2022);
System.out.println(atomicInteger.compareAndSet(2022, 2023));
System.out.println("atomicInteger.get() = " + atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(2022, 2023));
System.out.println("atomicInteger.get() = " + atomicInteger.get());
}
}
compareAndSet(int expect, int update) //期望,更新
当期望的值相同的时候,才会更新
16.2、Unsafe
由于java无法操作内存,但是java可以调用C++,C++可以操作内存
所以Unsafe就是为java提供操作内存的后门
采用自旋锁来达到+1的操作,do{} while()
16.3、CAS的ABA问题(狸猫换太子)
线程A与线程B同时获取到主存的值,线程B执行cas(1, 3),cas(3,1),但是线程A还一直以为获取的值还是之前从主存中得到的1,并不知晓线程B已经操作了。
解决方案,原子引用,带版本号的原子操作
17、原子引用
/** * 原子引用 */public class YzyyDemo { public static void main(String[] args) { //Integer超过-128~127就会不会复用之前的对象,所以之前测试使用2022更新2023就会更新失败 AtomicStampedReference<Integer> atomicReference = new AtomicStampedReference<>(1, 1); new Thread(() -> { System.out.println("a1=>" + atomicReference.getReference()); System.out.println(atomicReference.compareAndSet(1, 2, atomicReference.getStamp(), atomicReference.getStamp() + 1)); System.out.println("a2=>" +atomicReference.getReference()); System.out.println(atomicReference.compareAndSet(2, 3, atomicReference.getStamp(), atomicReference.getStamp() + 1)); System.out.println("a3=>" +atomicReference.getReference()); }, "a").start(); new Thread(() -> { System.out.println("b1=>" +atomicReference.getReference()); System.out.println(atomicReference.compareAndSet(1, 5, atomicReference.getStamp(), atomicReference.getStamp() + 1)); System.out.println("b2=>" +atomicReference.getReference()); }, "b").start(); }}
18、各种锁的理解
18.1、公平锁与非公平锁
- 公平锁:非常公平,不能够插队,先来后到
- 非公平锁:非常不公平,可以插队(所有的锁都是默认非公平,为了提高效率)
18.2、可重入锁
- 所有的锁都是可重入锁
- synchronized案例
package com.cnl.juc.test14;import java.util.concurrent.TimeUnit;/** * */public class Demo01 { public static void main(String[] args) { Phone phone = new Phone(); new Thread(() -> { phone.sms(); }, "A").start(); new Thread(() -> { phone.call(); }, "B").start(); }}class Phone { //当访问sms拿到synchronized锁时,会顺便把call的锁也拿到,其他线程访问call时,必须等待sms释放,才能访问 public synchronized void sms() { System.out.println(Thread.currentThread().getName() + "sms"); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } call(); } public synchronized void call() { System.out.println(Thread.currentThread().getName() + "call"); }}
- lock案例
package com.cnl.juc.test14;import java.util.concurrent.TimeUnit;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;public class Demo02 { public static void main(String[] args) { Phone1 phone = new Phone1(); new Thread(() -> { phone.sms(); }, "A").start(); new Thread(() -> { phone.call(); }, "B").start(); }}class Phone1 { private Lock lock = new ReentrantLock(); //locl锁必须配对 public void sms() { try { lock.lock(); System.out.println(Thread.currentThread().getName() + "sms"); TimeUnit.SECONDS.sleep(2); 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(); } }}
18.3、自旋锁
- 自定义自旋锁
package com.cnl.juc.test15;import java.util.concurrent.atomic.AtomicReference;/** * 自定义自旋锁 */public class SpinLock { private AtomicReference<Thread> objectAtomicReference = new AtomicReference<>(); //加锁 public void myLock() { Thread thread = Thread.currentThread(); System.out.println("thread.getName() = " + thread.getName() + "==> 进来了"); while (!objectAtomicReference.compareAndSet(null, thread)) { } } //解锁 public void myUnlock() { Thread thread = Thread.currentThread(); System.out.println("thread.getName() = " + thread.getName() + "==> 出去了"); objectAtomicReference.compareAndSet(thread, null); }}
- 测试
package com.cnl.juc.test15;import java.util.concurrent.TimeUnit;/** * @author cnl * 测试 */public class SpinLockDemo { public static void main(String[] args) { SpinLock spinLock = new SpinLock(); new Thread(() -> { spinLock.myLock(); try { TimeUnit.SECONDS.sleep(10); } catch (Exception e) { e.printStackTrace(); }finally { spinLock.myUnlock(); } }, "T1").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(() -> { spinLock.myLock(); try { TimeUnit.SECONDS.sleep(1); } catch (Exception e) { e.printStackTrace(); }finally { spinLock.myUnlock(); } }, "T2").start(); }}
只要T1未释放锁,T2会一直等待释放锁
18.4、死锁
就是资源抢占导致
产生死锁的四个必要条件
- 互斥条件:一个资源每次只能被一个进程(线程)使用。
- 请求与保持条件:一个进程(线程)因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件 : 此进程(线程)已获得的资源,在末使用完之前,不能强行剥夺。
- 循环等待条件 : 多个进程(线程)之间形成一种头尾相接的循环等待资源关系。
- 死锁案例
package com.cnl.juc.test16;import java.util.concurrent.TimeUnit;/** * 死锁 */public class deadLock { public static void main(String[] args) { String lock1 = "lock1"; String lock2 = "lock2"; new Thread(new MyThread(lock1, lock2), "A").start(); new Thread(new MyThread(lock2, lock1), "B").start(); }}class MyThread implements Runnable { private String lock1; private String lock2; public MyThread(String lock1, String lock2) { this.lock1 = lock1; this.lock2 = lock2; } @Override public void run() { synchronized (lock1) { try { TimeUnit.SECONDS.sleep(3); System.out.println(Thread.currentThread().getName() + "=> lock2锁"); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lock2) { System.out.println(Thread.currentThread().getName() + "=> lock1锁"); } } }}
- 死锁排查
- 采用jpa + jstack
jstack 96521
- 日志(先确认是否正常)
- 堆栈
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://itzsg.com/17440.html