java内力内力篇总纲
JDK1.8 官网在线文档,学习跟着看
目录
1、何为JUC
2、Lock锁(接口)
3、生产者和消费者
4、线程八锁(八个问题,四组)
5、集合类(安全与不安全)
6、Callable
7、CountDownLatch、CyclicBarrier、Semaphore
8、读写锁(ReadWriteLock)
9、阻塞队列(FIFO 先进先出)
10、线程池()
11、四大函数式接口
12、Stream流式计算
13、分之合并(ForkJoin)
14、异步回调
15、JMM
16、volatile
17、深入单例模式
18、CAS(比较并交换)
19、原子引用(解决ABA问题)
20、可重入锁、自旋锁、死锁、公平锁、非公平锁....
一、何为JUC
JDK并发工具类是JDK1.5引入的一大重要的功能,集中在Java.util.concurrent包下。java.util.concurrent包主要包含了并发集合类以及线程池和信号量三组重要工具类。
二、Lock 接口
1、实现类:
2、官网提供的使用方式:
3、ReentrantLock
- 公平锁:十分公平,可以先来后到
- 非公平锁:十分不公平,可以插队(根据CPU调度优化算法)
4、使用 demo
// lock 三部曲
// 1 new ReentantLock
// 2 lock.lock() 加锁
// 3 finally -> lock.unlock() 解锁
class TicketTest {
// 属性、方法
private int number = 30;
private Lock lock = new ReentrantLock ( );
public void sale() {
// 加锁
lock.lock ( );
try {
// 业务代码------------
if (number > 0) {
System.out.println (Thread.currentThread ( ).getName ( ) + ":" + (++number));
}
} catch (Exception e) {
e.printStackTrace ( );
} finally {
lock.unlock ( );
}
}
// 测试
public static void main(String[] args) {
TicketTest ticket = new TicketTest ( );
new Thread (() -> {
for (int i = 0; i < 40; i++) {
ticket.sale ( );
}
}, "A").start ();
new Thread (() -> {
for (int i = 0; i < 40; i++) {
ticket.sale ( );
}
}, "B").start ();
new Thread (() -> {
for (int i = 0; i < 40; i++) {
ticket.sale ( );
}
}, "C").start ();
}
}
5、Synchronized 与 Lock 区别
1、Synchronized内置的Java关键字,Lock是一个Java类
2、Synchronized无法判断获取锁的状态,Lock 可以判断是否获取到了锁
3、Synchronized会自动释放锁,lock必须要手动释放锁!
如果不释放锁,死锁
4、Synchronized 线程1(获得锁,阻塞)、线程2(等待,傻傻的等) ; Lock锁就不一定会等待下去;
5、Synchronized可重入锁,不可以中断的,非公平;
Lock,可重入锁,可以判断锁,非公平(可以自己设置);
6、Synchronized适合锁少量的代码同步问题,Lock 适合锁大量的同步代码!
三、生产者和消费者
1、Synchronized 版本(1.0,导致虚假唤醒)
package com.kk.pc;
public class A {
public static void main(String[] args) {
Data data = new Data ( );
new Thread (()->{
for (int i = 0; i < 10; i++) {
try {
data.increment ();
} catch (InterruptedException e) {
e.printStackTrace ( );
}
}
},"A").start ();
new Thread (()->{
for (int i = 0; i < 10; i++) {
try {
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){
// 等待
this.wait ();
}
number++;
System.out.println (Thread.currentThread ().getName ()+"=>"+number );
// 通知其他线程,我 +1 完毕
this.notifyAll ();
}
// -1
public synchronized void decrement() throws InterruptedException{
if (number==0){
// 等待
this.wait ();
}
number--;
System.out.println (Thread.currentThread ().getName ()+"=>"+number );
// 通知其他线程,我 -1 完毕
this.notifyAll ();
}
}
2、虚假唤醒(跑两个线程,一个生产一个消费没问题;但是两个以上,IF 就会出现问题,必须使用 white 循环防止中断)
只需要把 以上代码的 IF 改为 while
3、JUC版本
通过 lock 找到 Condition
package com.kk.pc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class A {
public static void main(String[] args) {
Data data = new Data ( );
new Thread (() -> {
for (int i = 0; i < 10; i++) {
data.increment ( );
}
}, "A").start ( );
new Thread (() -> {
for (int i = 0; i < 10; i++) {
data.decrement ( );
}
}, "B").start ( );
new Thread (() -> {
for (int i = 0; i < 10; i++) {
data.increment ( );
}
}, "C").start ( );
new Thread (() -> {
for (int i = 0; i < 10; i++) {
data.decrement ( );
}
}, "D").start ( );
}
}
// 判断等待、业务、通知
class Data {// 数字 资源类
private int number = 0;
private Lock lock = new ReentrantLock ( );
Condition condition = lock.newCondition ( );// 监视器
//condition.await (); 等待
//condition.signalAll (); 唤醒全部
// +1
public void increment() {
lock.lock ( );// 上锁
try {
while (number != 0) {
// 等待
condition.await ( );
}
number++;
System.out.println (Thread.currentThread ( ).getName ( ) + "=>" + number);
// 通知其他线程,我 +1 完毕
condition.signalAll ( );
} catch (InterruptedException e) {
e.printStackTrace ( );
} finally {
lock.unlock ( );// 解锁
}
}
// -1
public void decrement() {
lock.lock ( );// 上锁
try {
while (number == 0) {
// 等待
condition.await ( );
}
number--;
System.out.println (Thread.currentThread ( ).getName ( ) + "=>" + number);
// 通知其他线程,我 -1 完毕
condition.signalAll ( );
} catch (InterruptedException e) {
e.printStackTrace ( );
} finally {
lock.unlock ( );// 解锁
}
}
}
4、两个版本的使用区别
5、 Condition 精准的通知和唤醒线程(既 有序执行线程)
有序执行: A->B->C
package com.kk.pc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class A {
public static void main(String[] args) {
Data data = new Data ( );
new Thread (() -> {
for (int i = 0; i < 10; i++) {
data.printA ( );
}
}, "A").start ( );
new Thread (() -> {
for (int i = 0; i < 10; i++) {
data.printB ( );
}
}, "B").start ( );
new Thread (() -> {
for (int i = 0; i < 10; i++) {
data.printC ( );
}
}, "C").start ( );
}
}
// 判断等待、业务、通知
class Data {// 数字 资源类
private int number = 1;// 1A 2B 3C
private Lock lock = new ReentrantLock ( );
Condition condition1 = lock.newCondition ( );// 监视器1
Condition condition2 = lock.newCondition ( );// 监视器2
Condition condition3 = lock.newCondition ( );// 监视器3
public void printA () {
lock.lock ( );// 上锁
try {
// 业务,判断 -> 执行 -> 通知
while (number != 1) {
// 等待
condition1.await ( );
}
System.out.println (Thread.currentThread ( ).getName ( ) + "=>" + "AAAAAA");
// 唤醒指定线程 B
number=2;
condition2.signal ();
} catch (InterruptedException e) {
e.printStackTrace ( );
} finally {
lock.unlock ( );// 解锁
}
}
public void printB () {
lock.lock ( );// 上锁
try {
// 业务,判断 -> 执行 -> 通知
while (number != 2) {
// 等待
condition2.await ( );
}
System.out.println (Thread.currentThread ( ).getName ( ) + "=>" + "BBBBBB");
// 唤醒指定线程 B
number=3;
condition3.signal ();
} catch (InterruptedException e) {
e.printStackTrace ( );
} finally {
lock.unlock ( );// 解锁
}
}
public void printC () {
lock.lock ( );// 上锁
try {
// 业务,判断 -> 执行 -> 通知
while (number != 3) {
// 等待
condition3.await ( );
}
System.out.println (Thread.currentThread ( ).getName ( ) + "=>" + "CCCCCC");
// 唤醒指定线程 B
number=1;
condition1.signal ();
} catch (InterruptedException e) {
e.printStackTrace ( );
} finally {
lock.unlock ( );// 解锁
}
}
}
四、八锁的现象( 八个问题,四组)
问题一:标准情况下:两个线程(先发短信/先打电话)? 先发短信,后打电话
package com.kk.Lock8;
import java.util.concurrent.TimeUnit;
// 问题一:标准情况下:两个线程(先发短信/先打电话)? 先发短信,后打电话
public class Test {
public static void main(String[] args) {
Phone phone = new Phone ( );
new Thread(()->{phone.sendSms ();},"A").start ();
// 睡眠一秒
try {
TimeUnit.SECONDS.sleep (1);
} catch (InterruptedException e) {
e.printStackTrace ( );
}
new Thread(()->{phone.call ();},"B").start ();
}
}
class Phone{
public synchronized void sendSms(){
System.out.println ("发短信" );
}
public synchronized void call(){
System.out.println ("打电话" );
}
}
问题二 sendSms延迟3秒 :标准情况下:两个线程(先发短信/先打电话)?
package com.kk.Lock8;
import java.util.concurrent.TimeUnit;
// 问题一:标准情况下:两个线程(先发短信/先打电话)? 先发短信,后打电话
// 问题二 sendSms延迟3秒 :标准情况下:两个线程(先发短信/先打电话)? 先发短信,后打电话
// 两个问题的分析:首先要明确,不是因为 sendSms先调用而先运行,是 sendSms先拿到锁才线先运行
//而两线程持有的是通一把锁,所以 call 等待,两个是异步,如果加 main是三个
public class Test {
public static void main(String[] args) {
Phone phone = new Phone ( );
new Thread(()->{phone.sendSms ();},"A").start ();
// 睡眠一秒
try {
TimeUnit.SECONDS.sleep (1);
} catch (InterruptedException e) {
e.printStackTrace ( );
}
new Thread(()->{phone.call ();},"B").start ();
}
}
class Phone{
// synchronized 锁的对象是方法的调用者 phone 实例对象
// 两方法用的是同一个锁,谁先拿到谁先运行
public synchronized void sendSms(){
// 睡眠三秒
try {
TimeUnit.SECONDS.sleep (3);
} catch (InterruptedException e) {
e.printStackTrace ( );
}
System.out.println ("发短信" );
}
public synchronized void call(){
System.out.println ("打电话" );
}
}
// 问题三:标准情况下:两个线程(先发短信/先打电话)?
package com.kk.Lock8;
import java.util.concurrent.TimeUnit;
// 问题三:标准情况下:两个线程(先发短信/先打电话)? 先打印 hello,后打电话
public class Test {
public static void main(String[] args) {
Phone phone = new Phone ( );
new Thread(()->{phone.sendSms ();},"A").start ();
// 睡眠 1 秒(main 调用顺序睡眠 1 秒)
try {
TimeUnit.SECONDS.sleep (1);
} catch (InterruptedException e) {
e.printStackTrace ( );
}
new Thread(()->{phone.hello ();},"B").start ();
}
}
class Phone{
// synchronized 锁的对象是方法的调用者 phone 实例对象
public synchronized void sendSms(){
// 睡眠 2 秒(sendSms 前睡眠 2 秒)
try {
TimeUnit.SECONDS.sleep (2);
} catch (InterruptedException e) {
e.printStackTrace ( );
}
System.out.println ("发短信" );
}
public synchronized void call(){
System.out.println ("打电话" );
}
// 没有锁,不受锁的影响
public void hello(){
System.out.println ("hello" );
}
}
问题四:两个对象锁,两个同步方法(先发短信/先打电话)?
package com.kk.Lock8;
import java.util.concurrent.TimeUnit;
// 问题三:标准情况下:两个线程(先发短信/先打电话)? 先打印 hello,后打电话
// 问题四:两个对象锁,两个同步方法(先发短信/先打电话)? 打电话(看设的延迟,但两个线程不相互影响)
public class Test {
public static void main(String[] args) {
Phone phone = new Phone ( );
Phone phone2 = new Phone ( );
new Thread(()->{phone.sendSms ();},"A").start ();
// 睡眠 1 秒(main 调用顺序睡眠 1 秒)
try {
TimeUnit.SECONDS.sleep (1);
} catch (InterruptedException e) {
e.printStackTrace ( );
}
new Thread(()->{phone2.call ();},"B").start ();
}
}
class Phone{
// synchronized 锁的对象是方法的调用者 phone 实例对象
public synchronized void sendSms(){
// 睡眠 2 秒(sendSms 前睡眠 2 秒)
try {
TimeUnit.SECONDS.sleep (2);
} catch (InterruptedException e) {
e.printStackTrace ( );
}
System.out.println ("发短信" );
}
// synchronized 锁的对象是方法的调用者 phone2 实例对象
public synchronized void call(){
System.out.println ("打电话" );
}
// 没有锁,不受锁的影响
public void hello(){
System.out.println ("hello" );
}
}
问题五:增加两个静态方法,只有一个对象(先发短信/先打电话)?
package com.kk.Lock8;
import java.util.concurrent.TimeUnit;
// 问题五:增加两个静态方法,只有一个对象(先发短信/先打电话)?
//问题五分析:先发短信,后打电话,两个线程持有的是同一把锁
public class Test {
public static void main(String[] args) {
Phone phone = new Phone ( );
new Thread(()->{phone.sendSms ();},"A").start ();
// 睡眠 1 秒(main 调用顺序睡眠 1 秒)
try {
TimeUnit.SECONDS.sleep (1);
} catch (InterruptedException e) {
e.printStackTrace ( );
}
new Thread(()->{phone.call ();},"B").start ();
}
}
// 类是唯一的,对象是N个
class Phone{
// synchronized 锁的对象是方法的调用者
// stati 类一加载,既有 Class 模板(类)
// 锁的是 Class 类
public static synchronized void sendSms(){
// 睡眠 2 秒(sendSms 前睡眠 2 秒)
try {
TimeUnit.SECONDS.sleep (2);
} catch (InterruptedException e) {
e.printStackTrace ( );
}
System.out.println ("发短信" );
}
public static synchronized void call(){
System.out.println ("打电话" );
}
}
问题六:两个对象,两个静态的同步方法(先发短信/先打电话)?
package com.kk.Lock8;
import java.util.concurrent.TimeUnit;
// 问题五:增加两个静态方法,只有一个对象(先发短信/先打电话)?
//问题五分析:先发短信,后打电话,两个线程持有的是同一把锁
// 问题六:两个对象,两个静态的同步方法(先发短信/先打电话)?
//问题五分析:其实就是同问题五的结果
public class Test {
public static void main(String[] args) {
// 两个对象的 Class 都是同一个,static 锁也是同一个 Class
Phone phone = new Phone ( );
Phone phone2 = new Phone ( );
new Thread(()->{phone.sendSms ();},"A").start ();
// 睡眠 1 秒(main 调用顺序睡眠 1 秒)
try {
TimeUnit.SECONDS.sleep (1);
} catch (InterruptedException e) {
e.printStackTrace ( );
}
new Thread(()->{phone2.call ();},"B").start ();
}
}
// 类是唯一的,对象是N个
class Phone{
// synchronized 锁的对象是方法的调用者
// stati 类一加载,既有 Class 模板(类)
// 锁的是 Class 类
public static synchronized void sendSms(){
// 睡眠 2 秒(sendSms 前睡眠 2 秒)
try {
TimeUnit.SECONDS.sleep (2);
} catch (InterruptedException e) {
e.printStackTrace ( );
}
System.out.println ("发短信" );
}
public static synchronized void call(){
System.out.println ("打电话" );
}
}
问题七:1 个静态同步方法, 1 个普通同步方法(先发短信/先打电话)?
package com.kk.Lock8;
import java.util.concurrent.TimeUnit;
// 问题七:1 个静态同步方法, 1 个普通同步方法(先发短信/先打电话)?
//问题七分析:先打电话,后发短信,因为两个持有的是两个锁,一个Class,一个 phone实例
public class Test {
public static void main(String[] args) {
// 两个对象的 Class 都是同一个,static 锁也是同一个 Class
Phone phone = new Phone ( );
new Thread(()->{phone.sendSms ();},"A").start ();
new Thread(()->{phone.call ();},"B").start ();
}
}
// 类是唯一的,对象是N个
class Phone{
// synchronized 锁的对象是方法的调用者
// stati 类一加载,既有 Class 模板(类)
// 锁的是 Class 类
public static synchronized void sendSms(){
// 睡眠 2 秒(sendSms 前睡眠 2 秒)
try {
TimeUnit.SECONDS.sleep (2);
} catch (InterruptedException e) {
e.printStackTrace ( );
}
System.out.println ("发短信" );
}
public synchronized void call(){
System.out.println ("打电话" );
}
}
问题八:1 个静态同步方法, 1 个普通同步方法,两个对象(先发短信/先打电话)?
package com.kk.Lock8;
import java.util.concurrent.TimeUnit;
// 问题七:1 个静态同步方法, 1 个普通同步方法,一个对象(先发短信/先打电话)?
//问题七分析:先打电话,后发短信,因为两个持有的是两个锁,一个Class,一个 phone实例
//问题八:1 个静态同步方法, 1 个普通同步方法,两个对象(先发短信/先打电话)?
//同问题七
public class Test {
public static void main(String[] args) {
// 两个对象的 Class 都是同一个,static 锁也是同一个 Class
Phone phone = new Phone ( );
Phone phone2 = new Phone ( );
new Thread(()->{phone.sendSms ();},"A").start ();
new Thread(()->{phone2.call ();},"B").start ();
}
}
// 类是唯一的,对象是N个
class Phone{
// synchronized 锁的对象是方法的调用者
// stati 类一加载,既有 Class 模板(类)
// 锁的是 Class 类
public static synchronized void sendSms(){
// 睡眠 2 秒(sendSms 前睡眠 2 秒)
try {
TimeUnit.SECONDS.sleep (2);
} catch (InterruptedException e) {
e.printStackTrace ( );
}
System.out.println ("发短信" );
}
public synchronized void call(){
System.out.println ("打电话" );
}
}
五、集合类(安全与不安全)
1、ArrayList 解决线程安全问题
package com.kk.unsafe;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
public class ListTest {
// java.util.ConcurrentModificationException 并发修改异常
public static void main(String[] args) {
// 解决 ArrayList 并发修改问题
// 1、List list = new Vector<> ( );
// 2、List list = Collections.synchronizedList (new ArrayList<> ());
// 3、List list = new CopyOnWriteArrayList();
// CopyOnWrite 顾名思义,写入时复制;采用 COW,计算机领域的一种优化策略
// CopyOnWrite 在多线程调用时的处理方式:
// (1)读:原来固定的 list
// (2)写:将新数据放入 copy数组中(避免覆盖),此时有人读取的还是原来的数组
// 读写分离
List list = new CopyOnWriteArrayList();
//
for (int i = 1; i <= 10; i++) {
new Thread (() ->
{
list.add (
UUID.randomUUID ( ).toString ( ).substring (0,6));
System.out.println ("线程名:"+Thread.currentThread ().getName ()+"\t"+list );
}, "" + i).start ( );
}
}
}
2、HashSet 同上
package com.kk.unsafe;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;
public class SetList {
public static void main(String[] args) {
// 解决 ArrayList 并发修改问题
// 1、Set set = Collections.synchronizedSet (new HashSet<> ());
// 2、Set set = new CopyOnWriteArraySet();
// CopyOnWrite 顾名思义,写入时复制;采用 COW,计算机领域的一种优化策略
// CopyOnWrite 在多线程调用时的处理方式:
// (1)读:原来固定的 list
// (2)写:将新数据放入 copy数组中,此时有人读取的还是原来的数组
// 读写分离
Set set = new CopyOnWriteArraySet();
//
for (int i = 1; i <= 10; i++) {
new Thread (() ->
{
set.add (
UUID.randomUUID ( ).toString ( ).substring (0,6));
System.out.println ("线程名:"+Thread.currentThread ().getName ()+"\t"+set );
}, "" + i).start ( );
}
}
}
3、hashMap 并发问题
public class MapTest {
public static void main(String[] args) {
// 解决 ArrayList 并发修改问题
Map map = new ConcurrentHashMap<> ( );
//
for (int i = 1; i <= 10; i++) {
new Thread (() ->
{
map.put (
UUID.randomUUID ( ).toString ( ).substring (0, 6),UUID.randomUUID ( ).toString ( ).substring (0, 6));
System.out.println ("线程名:" + Thread.currentThread ( ).getName ( ) + "\t" + map);
}, "" + i).start ( );
}
}
}
六、Callable
1、和 Runnable 的区别
(1)有返回值
(2)可抛异常
(3)方法不同,run() / call()
2、关系图
3、demo
package com.kk.call;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class CallableTest {
public static void main(String[] args)throws Exception {
MyThread thread = new MyThread ( );
// 适配类
FutureTask futureTask = new FutureTask (thread);
// 启动
new Thread (futureTask,"A").start ();
new Thread (futureTask,"B").start ();// 结果只会打印一个 call.因为第二个被缓存了
// 获取返回值, get() 可能会产生阻塞
// 所以将这句代码放最后,或者使用异步处理
Integer result = (Integer) futureTask.get ( );
System.out.println (result );
}
}
class MyThread implements Callable{
@Override
public Integer call() throws Exception {
System.out.println ("call..." );
// ---耗时操作---
return 1024;
}
}
七、CountDownLatch、CyclicBarrier、Semaphore
1、CountDownLatch(减法计数器)
package com.kk.count;
import java.util.concurrent.CountDownLatch;
// 计数器
public class CountDownLatchDemo{
public static void main(String[] args) throws Exception{
// 初始化设置总数
CountDownLatch countDownLatch = new CountDownLatch (6);
for (int i = 1; i <= 6; i++) {
new Thread (()->
{
System.out.println (Thread.currentThread ().getName () );
countDownLatch.countDown ();// 计数 -1
},""+i).start ();
}
// 等待计时器归零,再往下执行
countDownLatch.await ();
System.out.println ( "结束,关闭资源...");
}
}
须知:
countDownLatch.countDown ();// 计数 -1
countDownLatch.await ();// 等待计时器归零,再往下执行
每次有线程执行完后,countDown ()数量 -1,countDownLatch.await ()就会被唤醒,继续执行。
2、CyclicBarrier(加法计数器)
package com.kk.count;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
public static void main(String[] args) {
// 赚 10 亿财富自由
CyclicBarrier cyclicBarrier = new CyclicBarrier (10, () -> {
System.out.println ("实现财富自由!");
});
for (int i = 1; i <= 10; i++) {
// 1.8 以后线程外的变量,要在线程内使用,若是每加 final 默认隐式加 final
final 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 ( );
}
},temp+"").start ();
}
}
}
3、Semaphore(信号量:限流可以用到)
package com.kk.count;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class SemaphoreDemo {
public static void main(String[] args) {
// 信号量:初始化限定线程数量,可用于限流
Semaphore semaphore = new Semaphore (3);
for (int i = 1; i <= 6; i++) {
new Thread (()->{
try {
// 获取:厕所拉屎的权利
semaphore.acquire ();
System.out.println (Thread.currentThread ().getName ()+":开始拉屎!" );
TimeUnit.SECONDS.sleep (1);
System.out.println (Thread.currentThread ().getName ()+":拉屎完毕!" );
} catch (InterruptedException e) {
e.printStackTrace ( );
}finally {
// 释放:厕所关闭
semaphore.release ();
}
}).start ();
}
}
}
原理:
(1)semaphore.acquire () 获得,假设如果已经满了,等待释放为止。
(2)semaphore.release () 释放,会将当前的信号量释放 +1,然后唤醒等待的线程!
(3)作用:多个共享资源互斥的使用,并发限流,控制最大并发数
八、读写锁(ReadWriteLock)
1、没有加锁的缓存demo
package com.kk.rw;
import java.util.HashMap;
import java.util.Map;
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache myCache = new MyCache ( );
// 并发写
for (int i = 1; i <= 100; i++) {
final int temp = i;
new Thread (()->{
myCache.put (temp+"",temp+"");
},""+i).start ();
}
// 并发读
for (int i = 1; i <= 100; i++) {
final int temp = i;
new Thread (()->{
myCache.get (temp+"");
},""+i).start ();
}
}
}
// 自定义缓存
class MyCache{
private Map map = new HashMap<> ();
// 写
public void put(String key,Object val){
System.out.println (Thread.currentThread ().getName ()+" 写入:"+key );
map.put (key,val);
System.out.println (Thread.currentThread ().getName ()+" 写入成功!");
}
// 读
public void get(String key){
System.out.println (Thread.currentThread ().getName ()+" 读取:"+key );
Object o = map.get (key);
System.out.println (Thread.currentThread ().getName ()+" 读取成功,读到:"+o);
}
}
插队,无序写,会出问题,读无所谓
2、加入读写锁
package com.kk.rw;
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) {
MyCacheLock myCache = new MyCacheLock ( );
// 并发写
for (int i = 1; i <= 100; i++) {
final int temp = i;
new Thread (()->{
myCache.put (temp+"",temp+"");
},""+i).start ();
}
// 并发读
for (int i = 1; i <= 100; i++) {
final int temp = i;
new Thread (()->{
myCache.get (temp+"");
},""+i).start ();
}
}
}
// 自定义缓存(读写锁)
class MyCacheLock{
private Map map = new HashMap<> ();
// 读写锁:更细粒度控制
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
// 写:写入的时候,同一时间只允许一个线程写
public void put(String key,Object val){
// 加锁(写锁)
readWriteLock.writeLock ().lock ();
try {
System.out.println (Thread.currentThread ().getName ()+" 写入:"+key );
map.put (key,val);
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 );
Object o = map.get (key);
System.out.println (Thread.currentThread ().getName ()+" 读取成功,读到:"+o);
} catch (Exception e) {
e.printStackTrace ( );
}finally {
readWriteLock.readLock ().unlock ();// 释放
}
}
}
3、读写锁小结:
(1)读写锁分为:独占锁(写锁)又称排它锁,共享锁(读锁)
(2)读——读 可以共存
(3)读——写 不可共存
(4)写——写 不可共存
九、阻塞队列(FIFO-先进先出)
1、概念
(1)写入:如果队列满,就必须阻塞等待
(2)取出:如果队列空,就必须阻塞生产
(3)文档
(4)树结构关系
2、四组 API
抛出异常,不会抛出异常,阻塞等待,阻塞超时
/**
* 抛出异常
*/
@Test
public void test1(){
// 队列的大小
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<> (3);
System.out.println (blockingQueue.add ("A") );
System.out.println (blockingQueue.add ("B") );
System.out.println (blockingQueue.add ("C") );
// 抛出异常:java.lang.IllegalStateException: Queue full
// System.out.println (blockingQueue.add ("D") );
System.out.println (blockingQueue.remove () );
System.out.println (blockingQueue.remove () );
System.out.println (blockingQueue.remove () );
// 抛出异常:java.util.NoSuchElementException
// System.out.println (blockingQueue.remove () );
}
/**
* 有返回值,无异常
*/
@Test
public void test2(){
// 队列的大小
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<> (3);
System.out.println (blockingQueue.offer ("A") );
System.out.println (blockingQueue.offer ("B") );
//System.out.println (blockingQueue.element () );
System.out.println (blockingQueue.peek () );
System.out.println (blockingQueue.offer ("C") );
//System.out.println (blockingQueue.offer ("D") );// false 不抛出异常
System.out.println (blockingQueue.poll () );
System.out.println (blockingQueue.poll () );
System.out.println (blockingQueue.poll () );
// System.out.println (blockingQueue.poll () );// null 不抛出异常
}
/**
* 等待,阻塞(一直阻塞)
*/
@Test
public void test3() throws Exception{
// 队列的大小
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<> (3);
// 一直阻塞
blockingQueue.put ("A");
blockingQueue.put ("B");
blockingQueue.put ("C");
// 队列没有位置了,一直阻塞
//blockingQueue.put ("D");
blockingQueue.take ();
blockingQueue.take ();
blockingQueue.take ();
//blockingQueue.take ();// 队列内没有元素,一直阻塞
}
/**
* 等待,阻塞(超时等待)
*/
@Test
public void test4() throws Exception{
// 队列的大小
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<> (3);
blockingQueue.offer ("A");
blockingQueue.offer ("B");
blockingQueue.offer ("C");
// 设置超时超过 2 s,则不等待
blockingQueue.offer ("D",2, TimeUnit.SECONDS);
blockingQueue.poll ();
blockingQueue.poll ();
blockingQueue.poll ();
// 设置超时超过 2 s,则不等待
blockingQueue.poll (2,TimeUnit.SECONDS);
}
3、SynchronousQueue (无容量)
(1)进去一个元素,必须等待取出来之后,才能再往里面放一个元素。
(2)put 进去一个元素,必须从 take 中取出,才能再 put 第二个元素
(3)这里可以先put; 然后再打印语句,然后把T2线程注释掉。就会发现put 之后的输出语佝是没办法执行的,也就是说put 之后的输出宇语句是没办法执行的;也就是说 put 操作时就进入到阻塞了。只有调用了take 方法后, 原有的put才会继续执行下一个 put 操作
package com.kk.queue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
public class SynchronousQueueDemo {
public static void main(String[] args) {
// 同步队列
BlockingQueue blockingQueue = new SynchronousQueue<> ( );
new Thread (() -> {
try {
System.out.println (Thread.currentThread ( ).getName ( ) + "添加:a");
blockingQueue.put ("a");
System.out.println (Thread.currentThread ( ).getName ( ) + "添加:b");
blockingQueue.put ("b");
System.out.println (Thread.currentThread ( ).getName ( ) + "添加:c");
blockingQueue.put ("c");
} catch (InterruptedException e) {
e.printStackTrace ( );
}
}, "T1").start ( );
new Thread (() -> {
try {
System.out.println (Thread.currentThread ( ).getName ( ) + "\t取出:" + blockingQueue.take ( ));
System.out.println (Thread.currentThread ( ).getName ( ) + "\t取出:" + blockingQueue.take ( ));
System.out.println (Thread.currentThread ( ).getName ( ) + "\t取出:" + blockingQueue.take ( ));
} catch (InterruptedException e) {
e.printStackTrace ( );
}
}, "T2").start ( );
}
}
(4)解释打印的连续 put现象,看 3 的解释
十、线程池()
核心内容
(1)三大构造方法
(2)七大构造参数
(3)四种拒绝策略
1、池化技术
(1)为何使用池化技术:程序的运行,本质:占用系统的资源,池化技术则可以优化资源的使用。
(2)池化技术概念:事先准备好一些资源,有人要用,就来池中取,用完了归还到池子中取。
(3)池化技术应用:线程池,连接池,内存池,对象池
2、线程池的好处:
(1)降低资源的消耗(线程复用)
(2)提高响应的速度
(3)方便管理
(4)可控最大并发数
3、三个创建线程的构造
package com.kk.executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExecutorsDemo {
public static void main(String[] args) {
// 单个线程,只有一个线程执行运行处理
//ExecutorService threadPool = Executors.newSingleThreadExecutor ( );
// 创建一个固定大小的线程池
//ExecutorService threadPool = Executors.newFixedThreadPool (5 );
// 可伸缩:遇强则强,遇弱则弱
ExecutorService threadPool = Executors.newCachedThreadPool ( );
// 使用线程池来创建函数
try {
for (int i = 1; i <= 10; i++) {
threadPool.execute (() -> {
System.out.println (Thread.currentThread ( ).getName ( ) + ":ok");
});
}
} catch (Exception e) {
e.printStackTrace ( );
}finally {
// 使用完毕,关闭线程池
threadPool.shutdown ();
}
}
}
4、七个构造参数
(1)先看三个构造的源码,发现底层都是调用同一个构造方法(ThreadPoolExecutor)
(2)然后追溯到 ThreadPoolExecutor 源码
(3)自定义线程池
public class ExecutorsDemo {
public static void main(String[] args) {
// 七大参数,自定义线程
ThreadPoolExecutor threadPool = new ThreadPoolExecutor (
2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingDeque<> (3),
Executors.defaultThreadFactory ( ),// 默认工厂
new ThreadPoolExecutor.AbortPolicy ( )
);
// 使用线程池来创建函数
try {
for (int i = 1; i <= 10; i++) {
threadPool.execute (() -> {
System.out.println (Thread.currentThread ( ).getName ( ) + ":ok");
});
}
} catch (Exception e) {
e.printStackTrace ( );
}finally {
// 使用完毕,关闭线程池
threadPool.shutdown ();
}
}
}
(4)结论:首选固定线程池,newFixedThreadPool,因可伸缩的 最大可达到 21亿,必然会有OOM的可能性
5、四个拒绝策略
RejectedExecutionHandler 接口底下的四个实现类
(1)默认:ThreadPoolExecutor.AbortPolicy ( ),阻塞队列满了,还有线程进来,不处理并且抛出异常。(当线程数达到 Max ,的线程进入阻塞队列)
public static void main(String[] args) {
// 七大参数,自定义线程
ThreadPoolExecutor threadPool = new ThreadPoolExecutor (
2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingDeque<> (3 ),
Executors.defaultThreadFactory ( ),// 默认工厂
new ThreadPoolExecutor.AbortPolicy ( )
);
// 使用线程池来创建函数
try {
for (int i = 1; i <= 9; i++) {
threadPool.execute (() -> {
System.out.println (Thread.currentThread ( ).getName ( ) + ":ok");
});
}
} catch (Exception e) {
e.printStackTrace ( );
}finally {
// 使用完毕,关闭线程池
threadPool.shutdown ();
}
}
(2)ThreadPoolExecutor.CallerRunsPolicy ( ),哪里来的去哪里(这个线程池由主线程 main创建,则拒绝策略让 main处理 超出来的部门)
public class ExecutorsDemo {
public static void main(String[] args) {
// 七大参数,自定义线程
ThreadPoolExecutor threadPool = new ThreadPoolExecutor (
2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingDeque<> (3 ),
Executors.defaultThreadFactory ( ),// 默认工厂
new ThreadPoolExecutor.CallerRunsPolicy ( )
);
// 使用线程池来创建函数
try {
for (int i = 1; i <= 11; i++) {
threadPool.execute (() -> {
System.out.println (Thread.currentThread ( ).getName ( ) + ":ok");
});
}
} catch (Exception e) {
e.printStackTrace ( );
}finally {
// 使用完毕,关闭线程池
threadPool.shutdown ();
}
}
}
(3)ThreadPoolExecutor.DiscardPolicy( ),队列满了,丢弃任务,不会抛出异常
(4)ThreadPoolExecutor.DiscardOldestPolicy ( ),队列满了,抢掉最早的那个线程来使用,不会抛出异常
6、CPU密集型 and IO密集型
线程池的最大线程数如何定义,取决于两者
CPU:几核就是几,可以保持CPU效率最高的使用
IO:判断程序中十分消耗IO的线程(写和读消耗大的多分配)
public class ExecutorsDemo {
public static void main(String[] args) {
System.out.println (Runtime.getRuntime ().availableProcessors () );
// 七大参数,自定义线程
ThreadPoolExecutor threadPool = new ThreadPoolExecutor (
2,
Runtime.getRuntime ().availableProcessors () ,// 最大线程数=CPU核数
3,
TimeUnit.SECONDS,
new LinkedBlockingDeque<> (3 ),
Executors.defaultThreadFactory ( ),// 默认工厂
new ThreadPoolExecutor.DiscardPolicy ( )
);
// 使用线程池来创建函数
try {
for (int i = 1; i <= 11; i++) {
final int temp = i;
threadPool.execute (() -> {
System.out.println (Thread.currentThread ( ).getName ( ) + ":ok"+temp);
});
}
} catch (Exception e) {
e.printStackTrace ( );
}finally {
// 使用完毕,关闭线程池
threadPool.shutdown ();
}
}
}
十一、四大函数式接口
何为函数式接口:只有一个方法的接口
(1)函数型接口:只有一个传入,只有一个输出
// 自定义方法
// Function:只有一个传入,只有一个输出
public class Demo {
public static void main(String[] args) {
// Function function = new Function ( ) {
// @Override
// public String apply(String str) {
// return str;
// }
// };
// 优化写法
Function function = str -> str;
// 使用
System.out.println (function.apply ("str"));
}
}
(2)断定型接口:只有一个输入参数,返回值只能是 布尔值
// 断定型接口:只有一个输入参数,返回值只能是 布尔值
public class Demo01 {
public static void main(String[] args) {
// 判断字符串是否为空
// Predicate predicate = new Predicate ( ) {
// @Override
// public boolean test(String str) {
// return str.isEmpty ();
// }
// };
// 优化写法 一
// Predicate predicate = (str) -> {
// return str.isEmpty ( );
// };
// 优化写法 二
Predicate predicate = str -> str.isEmpty ();
// 使用
System.out.println (predicate.test (""));
}
}
(3)消费型接口:只有输入,没有返回值
public class Demo2 {
public static void main(String[] args) {
Consumer consumer = str -> System.out.println ( str);
consumer.accept ("st");
}
}
(4)供给型接口:没有输入,只有返回
public class Demo3 {
public static void main(String[] args) {
Supplier supplier = () -> {
return "来了!";
};
System.out.println (supplier.get ( ));
}
}
十二、Stream流式计算
一道题,概括
(1)ID必须为偶数
(2)年龄必须大于23岁
(3)用户名转换为大写
(4)用户名字母倒着排序
(5)分页出数据范围
package com.kk.function;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.Arrays;
import java.util.List;
public class Demo4 {
public static void main(String[] args) {
/**
* (1)ID必须为偶数
* (2)年龄必须大于23岁
* (3)用户名转换为大写
* (4)用户名字母倒着排序
* (5)输出一个用户
*/
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);
// Stream 用于计算
list.stream ()
.filter (u->{return u.getId ()%2==0;})
.filter (u-> u.getAge ()>23)
.map (u->u.getName ().toUpperCase ())
.sorted ((c1,c2)->c1.compareTo (c2))
.limit (1)
.forEach (System.out::println);
}
}
@AllArgsConstructor
@Data
class User{
private int id;
private String name;
private int age;
}
十三、分之合并(ForkJoin)
1、原理
(1)将一个大任务,分解为小任务分别计算,最后再合并
(2)底层实现的是双端队列,可以从上面或者下面同时取
2、ForkJoin实现求和计算
// 计算一个从 start 每次累加到 end 的案例
public class ForkJoinDemo extends RecursiveTask {
private long start;// 从多少开始计算
private long end;// 计算到多少停止
// 临界值(到达这个数值 则进行分解
private long temp = 10000L;
public ForkJoinDemo(long start, long end) {
this.start = start;
this.end = end;
}
// 计算(返回值取决于继承类的泛型)
@Override
protected Long compute() {
if ((end - start) < temp) {// 小于临界值则常规计算
long sum = 0;
for (long i = start; i <= end; i++) {
sum += i;
}
return sum;
} else {// forkJoin(递归
// 中间值
long mid = (start + end) / 2;
ForkJoinDemo fork1 = new ForkJoinDemo (start, mid);
fork1.fork ( );// 拆分任务,将任务压入到线程队列
ForkJoinDemo fork2 = new ForkJoinDemo (mid + 1, end);
fork2.fork ( );// 拆分任务,将任务压入到线程队列
// 合并返回
return fork1.join ( ) + fork2.join ( );
}
}
}
3、性能比较(常规、forkJoin、Stream)
package com.kk.forkjoin;
import java.util.concurrent.RecursiveTask;
import java.util.stream.LongStream;
// 计算一个从 start 每次累加到 end 的案例
public class ForkJoinDemo extends RecursiveTask {
private long start;// 从多少开始计算
private long end;// 计算到多少停止
// 临界值(到达这个数值 则进行分解
private long temp = 10000L;
public ForkJoinDemo(long start, long end) {
this.start = start;
this.end = end;
}
// 计算(返回值取决于继承类的泛型)
@Override
protected Long compute() {
if ((end - start) < temp) {// 小于临界值则常规计算
long sum = 0;
for (long i = start; i <= end; i++) {
sum += i;
}
return sum;
} else {// forkJoin(递归
// 中间值
long mid = (start + end) / 2;
ForkJoinDemo fork1 = new ForkJoinDemo (start, mid);
fork1.fork ( );// 拆分任务,将任务压入到线程队列
ForkJoinDemo fork2 = new ForkJoinDemo (mid + 1, end);
fork2.fork ( );// 拆分任务,将任务压入到线程队列
// 合并返回
return fork1.join ( ) + fork2.join ( );
}
}
}
// 测试
class Test {
public static void main(String[] args) throws Exception {
long startTime = System.currentTimeMillis ( );
long start = 1;
long end = 100_0000_0000L;// 100亿
// 常规,4221 ms
long sum = 0;
// for (long i = start; i <= end; i++) {
// sum+=i;
// }
// forkJoin 3522 ms
// ForkJoinPool forkJoinPool = new ForkJoinPool ( );
// ForkJoinDemo task = new ForkJoinDemo (start, end);
// // 提交任务
// ForkJoinTask submit = forkJoinPool.submit (task);
// sum = submit.get ( );
// stream 并行流 2488 ms
sum = LongStream.rangeClosed (start,end).parallel ().reduce (0,Long::sum);
long endTime = System.currentTimeMillis ( );
System.out.println ("结果为:" + sum + ",花费时间为:" + (endTime - startTime) + "毫秒");
}
}
十四、异步回调
分为两类:有返回值 和 无返回值
1、无返回值
public class CompleableFutureDemo {
public static void main(String[] args)throws Exception {
// 一个异步任务(没有返回值)
CompletableFuture completableFuture = CompletableFuture.runAsync (() -> {
try {
TimeUnit.SECONDS.sleep (2);
System.out.println (Thread.currentThread ().getName ()+"=>async" );
} catch (InterruptedException e) {
e.printStackTrace ( );
}
});
System.out.println ("main" );
// 获取阻塞执行结果
completableFuture.get ();
}
}
2、有返回值
public class CompleableFutureDemo {
public static void main(String[] args) throws Exception {
// 一个异步任务(有返回值)
CompletableFuture completableFuture = CompletableFuture.supplyAsync (() -> {
try {
TimeUnit.SECONDS.sleep (2);
System.out.println (Thread.currentThread ( ).getName ( ) + "=>async");
} catch (InterruptedException e) {
e.printStackTrace ( );
}
// 异常情况
int i = 1/0;
return 1024;
});
System.out.println ("main");
// 获取阻塞执行结果
System.out.println (completableFuture.whenCompleteAsync ((t, u) -> {
// 正常返回结果
System.out.println ("t --> " + t);
// 错误信息
System.out.println ("u --> " + u);
}).exceptionally ((e) -> {
e.printStackTrace ( );
// 返回异常的结果信息
return 500;
}).get ());
}
}
十五、JMM
1、谈谈你对 Volatile 的理解
Volatile 是 java 虚拟机提供的 轻量级的同步机制
(1)保证可见性
(2)不保证原子性
(3)禁止指令重排
2、什么是 JMM
java 内存模型,不存在的东西,一个概念(约定 )。
关于 JMM 的一些同步约定
(1)线程解锁前,必须把共享变量 立刻 刷新回主存。
(2)线程加锁前,必须读取主存中的最新值到工作内存中。
(3)加锁和解锁用的是同一锁
线程、主存、工作内存关系图
(1)内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类型的变量来说,load、store、read和write操作在某些平台上允许例外)
lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态
unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用
load (载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中
use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令
assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中
store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用
write (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中
(2)JMM对这八种指令的使用,制定了如下规则:
不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
不允许一个线程将没有assign的数据从工作内存同步回主内存
一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实施use、store操作之前,必须经过assign和load操作
一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
对一个变量进行unlock操作之前,必须把此变量同步回主内存
public class JMMDemo {
private static int num = 0;
public static void main(String[] args) {
new Thread (()->{
while (num==0){
// 一直循环,若 num被改变不再是 0则停止
}
}).start ();
// 睡 2s 再改变值
try {
TimeUnit.SECONDS.sleep (2);
} catch (InterruptedException e) {
e.printStackTrace ( );
}
num =1;
System.out.println (num );
}
}
问题:运行后发现,程序没有停下来,因为线程A 无法知道,main 线程改变的num 为 1 。-- 此刻需要加入 volatile 关键字
十六、volatile
一、三个特性
(1)保证可见性
public class JMMDemo {
private volatile static int num = 0;
public static void main(String[] args) {
new Thread (()->{
while (num==0){
// 一直循环,若 num被改变不再是 0则停止
}
}).start ();
// 睡 2s 再改变值
try {
TimeUnit.SECONDS.sleep (2);
} catch (InterruptedException e) {
e.printStackTrace ( );
}
num =1;
System.out.println (num );
}
}
(2)不保证原子性(原子性:可分割)
// volatile 不保证原子性
public class JMMDemo {
private volatile static int num = 0;
private static void add(){
num++;// 不是一个原子性操作
}
public static void main(String[] args) {
// 理论上 num 应该为 2W
for (int i = 0; i < 10; i++) {
new Thread (()->{
for (int j = 0; j < 2000; j++) {
add ();
}
}).start ();
}
// JVM默认存在有 两个线程 main GC
// 当存活数为 2++ 说明以上操作执行完毕 则让步,输出以下代码
while (Thread.activeCount ()>2){
Thread.yield ();
}
System.out.println (num );
}
}
二、问:如果不加 lock 或 synchronize ,怎么保证原子性
查看字节码文件:javap -c JMMDemo.class
,进行分析
采用原子类
这些类的底层都直接和操作系统挂钩。。。(CAS...)
// volatile 不保证原子性
public class JMMDemo {
private static AtomicInteger num = new AtomicInteger ();
public static void add(){
num.addAndGet (1);
num.getAndDecrement ();
}
public static void main(String[] args) {
// 理论上 num 应该为 2W
for (int i = 0; i < 10; i++) {
new Thread (()->{
for (int j = 0; j < 2000; j++) {
add ();
}
}).start ();
}
// JVM默认存在有 两个线程 main GC
// 当存活数为 2++ 说明以上操作执行完毕 则让步,输出以下代码
while (Thread.activeCount ()>2){
Thread.yield ();
}
System.out.println (num );
}
}
三、禁止指令重排
(1)何为指令重排:你写的程序,计算机并不是按照你写的顺序去执行。
(2)代码的执行流程:源代码 =》 编译器优化的重排 =》 指令并行也可能会重排 =》 内存系统也会重排 =》 执行
(3)处理器在进行指令重排的时候,考虑:数据之间的依赖性
int x = 1 ; // 1
int y = 2 ; // 2
x = x+ 5 ; // 3
y = x + x; // 4
我们写的顺序是 1234,但系统执行的时候可能是, 2134,1324,但绝不会是 4123,因为 4 的时候 x 还没被定义。
(4)底层采用内存屏障,CPU级别的指令:
保证操作的执行顺序,保证变量内存可见性
总结:Volatile,可以保证内存可见性,禁止指令重排,但是无法保证原子性(可以用原子类),内存屏障在单例模式应用比较多。
十七、深入单例模式
https://www.jianshu.com/p/b120020e7ea8
十八、CAS(比较并交换)
1、使用demo
public class CASDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger (2021);
// 参数一:期望值,更新值
// public final boolean compareAndSet(int expect, int update)
// 如果我期望值是 2021(现在拿到的值),那么就更新成功(变成 2022),否则不更新
System.out.println (atomicInteger.compareAndSet (2021, 2022));
System.out.println (atomicInteger.get ( ));
//
// 期望值已经是 2022 ,则不更新
System.out.println (atomicInteger.compareAndSet (2021, 2022));
System.out.println (atomicInteger.get ( ));
}
}
2、Unsafe类
valueiffset 不是对象内存中的值,而是对象在内存中的偏移量
3、总结
比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行此操作,如果不是就一直循环(自旋)
4、缺点
(1)循环会耗时
(2)一次性只能保证一个共享变量的原子性
(3)ABA问题
十九、原子引用(解决ABA问题)
1、什么是ABA问题
你和王少分别是线程A,B,你女朋友是共享变量。今天你去见她,她还是她(还是一个人 1),后来你出差了。王少当你不在的时间去操作了你女朋友,于是她不在是她(变成了两个人 2),当你出差回来,你以为她还是她,其实她已经变了,这就是ABA问题,A搞完,B搞,B搞完 A搞。
2、解决ABA问题
package com.kk.cas.aba;
import com.kk.jingdongelactichserchdemo.pojo.Content;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;
public class ABADemo {
// AtomicStampedReference 如果是个包装类(需要注意原子引用问题),
// 因为 Integer (cache [-128~127])
// 若 Integer赋值超过了这个范围则会 new 出一个新的对象,就不是原来的,所以无法避免
// 正常业务下,泛型不能是包装类(原子引用可能会和底层缓存机制冲突)
// Content 自定义pojo
public static void main(String[] args) {
Content content1 = new Content ("1", "18", "aa");
Content content2 = new Content ("2", "18", "aa");
Content content3 = new Content ("3", "18", "aa");
AtomicStampedReference at = new AtomicStampedReference (content1, 1);
// 乐观锁原理
new Thread (() -> {
// 获得版本号
int stamp = at.getStamp ( );
System.out.println ("al =>" + stamp);
try {
TimeUnit.SECONDS.sleep (1);
} catch (InterruptedException e) {
e.printStackTrace ( );
}
at.compareAndSet (content1, content2, at.getStamp ( ), at.getStamp ( ) + 1);
System.out.println ("a2 =>" + at.getStamp ( ));
}, "A").start ( );
new Thread (() -> {
// 获得版本号
System.out.println ("bl =>" + at.getStamp ( ));
try {
TimeUnit.SECONDS.sleep (2);
} catch (InterruptedException e) {
e.printStackTrace ( );
}
System.out.println (at.compareAndSet (content1, content3, at.getStamp ( ), at.getStamp ( ) + 1));
System.out.println ("b2 =>" + at.getStamp ( ));
}, "B").start ( );
}
}
二十、可重入锁、自旋锁、死锁、公平锁、非公平锁....
六张面试题
https://www.jianshu.com/p/909a74926e55
个人理解
狂神说,有部分讲的不太准确,CAS,自旋锁....可以参考以上面试题。
文章参考B站:狂神说,B站中 弹屏们 ,尚硅谷以及一些博客
https://www.cnblogs.com/xiaoxi/p/9140541.html
https://www.cnblogs.com/KingIceMou/p/7239668.html
https://blog.csdn.net/weixin_41832850/article/details/100095677
https://blog.csdn.net/zxl1148377834/article/details/90079882