目录
1、什么是JUC
1、什么是juc(学习方法:官方文档+源码)
java.util:java的工具包,包,分类
什么是juc:java.util.concurrent,java工具类下的一个并发功能的包。
Runnable 没有返回值、效率相比入 Callable相对较低!
·两者都属于juc里面
·但callable可以返回结果,也可以抛出异常
所以比起runable来说,callable有以下特点:
1、有返回值
2、可以抛出异常
3、方法不同,run(),call()
案例:https://blog.csdn.net/qq_42388853/article/details/111769444
2、进程和线程回顾
·进程:一个程序,QQ.exe Music.exe程序的线程的集合;
·一个进程往往可以包含多个线程,至少包含一个!
·Java默认有几个线程?最少2个,mian、GC
·举例说明:开了一个进程Typora(开启进程),写字(一个线程),自动保存(又一个线程线程)
·对于Java而言调用多线程的方法:Thread、Runnable(静态代理)、Callable(静态代理)
#java真的可以开启线程嘛?(面试题)
不行,因为开启线程需要调用start0(),这是个本地方法,使用的是底层c++。
并发、并行:
并发(多线程操作同一个资源)
·CPU一核,模拟出来多条线程,天下武功,唯快不破,快速交替
并行(多个人一起行走)
·CPU多核,多个线程可以同时执行;线程池
public class Test1 {
public static void main(String[] args) {
//获取cpu核数
System.out.println(Runtime.getRuntime().availableProcessors());
}
}
#并发编程的本质:充分利用CPU的资源
线程有几个状态:java有6个状态,操作系统有5个
public enum State {
//新生
NEW,
//运行
RUNNABLE,
//阻塞
BLOCKED,
//无限期等待
WAITING,
//限时等待
TIMED_WAITING,
//结束
TERMINATED;
}
java的Thread.State源码中操作系统的五个状态:初始状态(NEW) ,可运行状态(READY),运行状态(RUNNING) ,等待状态(WAITING) ,终止状态(TERMINATED)。
区别:
当线程调用阻塞式 API时,进程(线程)进入等待状态,这里指的是操作系统层面的。从 JVM层面来说,Java线程仍然处于 RUNNABLE 状态。JVM 并不关心操作系统线程的实际状态,从 JVM 看来,等待CPU使用权(操作系统状态为可运行态)与等待 I/O(操作系统处于等待状态)没有区别,都是在等待某种资源,所以都归入RUNNABLE 状态
wait/sleep区别:
1、来自不同的类
wait=》object
sleep=》Thread
常用的休眠方法:使用java.util.concurrent里的TimeUnit
TimeUnit.SECONDS.sleep(1);
TimeUnit.DAYS.sleep(2);
2、关于锁的释放
wait 会释放锁,sleep睡觉了,抱着锁睡觉,不会释放!
3、使用的范围不同
wait:必须在同步代码块。
sleep:可在任何地方睡。
#即有synchronized修饰符修饰的语句块,被该关键词修饰的语句块,将加上内置锁。实现同步。
例:synchronized(Object o ){}
4、是否需要捕获异常,两者都需要捕获异常
wait:需要捕获异常(中断异常)
sleep:必须要捕获异常
3、Lock锁
1、传统的Synchronized
使用过程 :https://blog.csdn.net/qq_45464015/article/details/118702948
2、Lock锁
·属于JUC包下,是一个接口,实现类有读写锁和可重入锁(reentrantlock)
使用方法:https://blog.csdn.net/qq_45464015/article/details/118703864
·观看文档的使用方法
Synchronized和Lock区别:
1、Synchronized 内置的Java关键字,Lock是一个Java类
2、Synchronized无法判断获取锁的状态,Lock 可以判断是否获取到了锁
3、Synchronized 会自动释放锁,lock 必须要手动释放锁!如果不释放锁,死锁
4、Synchronized线程1(获得锁,阻塞)、线程2(等待,傻傻的等) ; Lock锁就不一定会等待下去;
5、Synchronized 可重入锁,不可以中断的,非公平;Lock,可重入锁,可判断锁,默认非公平(可以自己设置);
6、Synchronized适合锁少量的代码同步问题,Lock适合锁大量的同步代码!
什么是CAS、非公平锁和公平锁: https://blog.csdn.net/qq_45464015/article/details/118704070
4、生产者和消费者
锁是什么,如何判断锁的是谁?
public class Demo04 {
public static void main(String[] args) {
Ticket1 ticket =new Ticket1();
new Thread(()->{
for (int i = 1; i < 10; i++) {
try {
ticket.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 1; i < 10; i++) {
try {
ticket.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
class Ticket1 {
private int number = 0;
public synchronized void increment() throws InterruptedException {
if (number!=0) {
this.wait();//通知其他线程我+1完了
}
number++;
System.out.println(Thread.currentThread().getName() + "=>" + number);
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException {
if (number==0) {
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName() + "=>" + number);
this.notifyAll();//通知其他线程我-1完了
}
}
代码测试:
package com.example.jvm01;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Dome05 {
public static void main(String[] args) {
Demae05 data = new Demae05();
new Thread(() ->{
for (int i = 0; i < 10; i++) {
data.prinkA();
}
},"A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.prinkB();
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.prinkC();
}
}, "C").start();
}
}
class Demae05 {
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private int number = 1;
public void prinkA() {
lock.lock();
try {
//业务,判断->执行->通知
while (number != 1) {
condition1.await(); //等待
}
System.out.println(Thread.currentThread().getName()+"=>AAAAAAAAA");
number = 2;
condition2.signal(); //通知B可以执行了
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void prinkB() {
lock.lock();
try {
//业务,判断->执行->通知
while (number != 2) {
condition2.await(); //等待
}
System.out.println(Thread.currentThread().getName()+"=>BBBBBBBB");
number = 3;
condition3.signal(); //通知C可以执行了
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void prinkC() {
lock.lock();
try {
//业务,判断->执行->通知
while (number != 3) {
condition3.await(); //等待
}
System.out.println(Thread.currentThread().getName()+"=>CCCCCCCC");
number = 1;
condition1.signal(); //通知A可以执行了
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
5、8锁的现象
—如何判断锁的是谁!永远的知道什么锁,锁到底锁的是谁!深刻理解我们的锁。
package com.example.jvm01;
import java.util.concurrent.TimeUnit;
/**
* 8锁,就是关于锁的八个问题
* 1.标准情况下,两个线程去打印 先输出发短息还是打电话? 输出结果 1、发短信 2、打电话
* 1.sendSms延迟四秒,四个线先打印 先输出发短信还是打电话 输出结果 1、发短信 2、打电话
*/
public class Demo06 {
public static void main(String[] args) throws InterruptedException {
Deat deat = new Deat();
new Thread(()->{
deat.sendSms();
},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
deat.call();
}, "B").start();
}
}
class Deat {
//synchronized 锁的对象是方法的调用者!
public synchronized void sendSms() {
try {
/*TimeUnit.SECONDS.sleep(4);*/
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("发信息");
}
public synchronized void call() {
System.out.println("打电话");
}
}
8锁,就是关于锁的八个问题
1.标准情况下,两个线程去打印 先输出发短息还是打电话? 输出结果 1、发短信 2、打电话
2.sendSms延迟四秒,四个线先打印 先输出发短信还是打电话 输出结果 1、发短信 2、打电话
总结:synchronized 锁的对象是方法的调用者!因为这里用的都是一个Deat用的都是同意一把锁,所以谁先拿到就谁先执行
package com.example.jvm01;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 8锁,就是关于锁的八个问题
* 1.标准情况下,两个线程去打印 先输出发短息还是打电话? 输出结果 1、发短信 2、打电话
* 2.sendSms延迟四秒,四个线先打印 先输出发短信还是打电话 输出结果 1、发短信 2、打电话
* synchronized 锁的对象是方法的调用者!因为这里用的都是一个Deat用的都是同意一把锁,所以谁先拿到就谁先执行
* 3.增加了一个void普通方法并且把call换成普通方法(Hello) 先输出发短信还是hello 输出结果 1、hello 2、发短信
* 4.两个对象,两个同步方法, 先输出发短信还是打电话 输出结果 1、打电话 2、发短信
*/
public class Demo06 {
public static void main(String[] args) throws InterruptedException {
Deat deat1 = new Deat();
new Thread(()->{
deat1.sendSms();
},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
deat1.Hello();
}, "B").start();
}
}
class Deat {
//synchronized 锁的对象是方法的调用者!
public synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("发信息");
}
public synchronized void call() {
System.out.println("打电话");
}
public void Hello() {
System.out.println("hello");
}
}
总结:3、增加了一个void普通方法并且把call换成普通方法(Hello) 先输出发短信还是hello 输出结果 1、hello 2、发短信 原因:不受锁的影响
package com.example.jvm01;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 8锁,就是关于锁的八个问题
* 1.标准情况下,两个线程去打印 先输出发短息还是打电话? 输出结果 1、发短信 2、打电话
* 2.sendSms延迟四秒,四个线先打印 先输出发短信还是打电话 输出结果 1、发短信 2、打电话
* synchronized 锁的对象是方法的调用者!因为这里用的都是一个Deat用的都是同意一把锁,所以谁先拿到就谁先执行
* 3.增加了一个void普通方法并且把call换成普通方法(Hello) 先输出发短信还是hello 输出结果 1、hello 2、发短信
* 4.两个对象,两个同步方法, 先输出发短信还是打电话 输出结果 1、打电话 2、发短信
*/
public class Demo06 {
public static void main(String[] args) throws InterruptedException {
Deat deat1 = new Deat();
Deat deat2 = new Deat();
new Thread(()->{
deat1.sendSms();
},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
deat2.call();
}, "B").start();
}
}
class Deat {
//synchronized 锁的对象是方法的调用者!
public synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("发信息");
}
public synchronized void call() {
System.out.println("打电话");
}
}
总结:4.两个对象,两个同步方法, 先输出发短信还是打电话 输出结果 1、打电话 2、发短信
/**
* 8锁,就是关于锁的八个问题
* 1.标准情况下,两个线程去打印 先输出发短息还是打电话? 输出结果 1、发短信 2、打电话 原因:main从上到下运行
* 2.sendSms延迟四秒,四个线先打印 先输出发短信还是打电话 输出结果 1、发短信 2、打电话 原因:main从上到下运行
* synchronized 同步方法 锁的对象是方法的调用者!因为这里用的都是一个Deat用的都是同意一把锁,所以谁先拿到就谁先执行
* 3.增加了一个void普通方法并且把call换成普通方法(Hello) 先输出发短信还是hello 输出结果 1、hello 2、发短信 原因:普通方法没有锁
* 4.两个对象,两个同步方法, 先输出发短信还是打电话 输出结果 1、打电话 2、发短信 原因:有两个对象调用两个方法用的就不是一个锁了,不会被牵连
* 5.增加两个静态的同步方法,只有一个对象,输出结果 1.发信息 2.打电话 原因:static 锁的是整个Class
* 6.增加两个静态的同步方法,只有两个个对象,输出结果 1.发信息 2.打电话 原因:static 锁的是整个Class
*/
public class Demo06 {
public static void main(String[] args) throws InterruptedException {
Deat deat1 = new Deat();
new Thread(()->{
deat1.sendSms();
},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
deat1.call();
}, "B").start();
}
}
class Deat {
//加上static静态方法 : 类一加载就有了,所以锁得是整个Class
public static synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("发信息");
}
public static synchronized void call() {
System.out.println("打电话");
}
}
总结:5.增加两个静态的同步方法,只有一个对象,输出结果 1.发信息 2.打电话 原因:static 锁的是整个Class
package com.example.jvm01;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 8锁,就是关于锁的八个问题
* 1.标准情况下,两个线程去打印 先输出发短息还是打电话? 输出结果 1、发短信 2、打电话 原因:main从上到下运行
* 2.sendSms延迟四秒,四个线先打印 先输出发短信还是打电话 输出结果 1、发短信 2、打电话 原因:main从上到下运行
* synchronized 同步方法 锁的对象是方法的调用者!因为这里用的都是一个Deat用的都是同意一把锁,所以谁先拿到就谁先执行
* 3.增加了一个void普通方法并且把call换成普通方法(Hello) 先输出发短信还是hello 输出结果 1、hello 2、发短信 原因:普通方法没有锁
* 4.两个对象,两个同步方法, 先输出发短信还是打电话 输出结果 1、打电话 2、发短信 原因:有两个对象调用两个方法用的就不是一个锁了,不会被牵连
* 5.增加两个静态的同步方法,只有一个对象,输出结果 1.发信息 2.打电话 原因:static 锁的是整个Class
* 6.增加两个静态的同步方法,只有两个个对象,输出结果 1.发信息 2.打电话 原因:static 锁的是整个Class
*/
public class Demo06 {
public static void main(String[] args) throws InterruptedException {
Deat deat1 = new Deat();
Deat deat2 = new Deat();
new Thread(()->{
deat1.sendSms();
},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
deat1.call();
}, "B").start();
}
}
class Deat {
//加上static静态方法 : 类一加载就有了,所以锁得是整个Class
public static synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("发信息");
}
public static synchronized void call() {
System.out.println("打电话");
}
}
6.增加两个静态的同步方法,只有两个个对象,输出结果 1.发信息 2.打电话 原因:static 锁的是整个Class
package com.example.jvm01;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 8锁,就是关于锁的八个问题
* 1.标准情况下,两个线程去打印 先输出发短息还是打电话? 输出结果 1、发短信 2、打电话 原因:main从上到下运行
* 2.sendSms延迟四秒,四个线先打印 先输出发短信还是打电话 输出结果 1、发短信 2、打电话 原因:main从上到下运行
* synchronized 同步方法 锁的对象是方法的调用者!因为这里用的都是一个Deat用的都是同意一把锁,所以谁先拿到就谁先执行
* 3.增加了一个void普通方法并且把call换成普通方法(Hello) 先输出发短信还是hello 输出结果 1、hello 2、发短信 原因:普通方法没有锁
* 4.两个对象,两个同步方法, 先输出发短信还是打电话 输出结果 1、打电话 2、发短信 原因:有两个对象调用两个方法用的就不是一个锁了,不会被牵连
* 5.增加两个静态的同步方法,只有一个对象,输出结果 1.发信息 2.打电话 原因:static 锁的是整个Class
* 6.增加两个静态的同步方法,只有两个个对象,输出结果 1.发信息 2.打电话 原因:static 锁的是整个Class
* 7.1个静态的同步方法和一个普通的同步方法,一个对象 ,输出结果 1.打电话 ,发短信 原因:静态同步方法锁的是Class 普通同步方法所得是调用者
* 8.1个静态的同步方法和一个普通的同步方法,两个对象 ,输出结果 1.打电话 ,发短信
*/
public class Demo06 {
public static void main(String[] args) throws InterruptedException {
Deat deat1 = new Deat();
new Thread(()->{
deat1.sendSms();
},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
deat1.call();
}, "B").start();
}
}
class Deat {
public static synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("发信息");
}
public synchronized void call() {
System.out.println("打电话");
}
}
总结:7.1个静态的同步方法和一个普通的同步方法,一个对象 ,输出结果 1.打电话 ,发短信 原因:静态同步方法锁的是Class 普通同步方法所得是调用者
```java
package com.example.jvm01;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 8锁,就是关于锁的八个问题
* 1.标准情况下,两个线程去打印 先输出发短息还是打电话? 输出结果 1、发短信 2、打电话 原因:main从上到下运行
* 2.sendSms延迟四秒,四个线先打印 先输出发短信还是打电话 输出结果 1、发短信 2、打电话 原因:main从上到下运行
* synchronized 同步方法 锁的对象是方法的调用者!因为这里用的都是一个Deat用的都是同意一把锁,所以谁先拿到就谁先执行
* 3.增加了一个void普通方法并且把call换成普通方法(Hello) 先输出发短信还是hello 输出结果 1、hello 2、发短信 原因:普通方法没有锁
* 4.两个对象,两个同步方法, 先输出发短信还是打电话 输出结果 1、打电话 2、发短信 原因:有两个对象调用两个方法用的就不是一个锁了,不会被牵连
* 5.增加两个静态的同步方法,只有一个对象,输出结果 1.发信息 2.打电话 原因:static 锁的是整个Class
* 6.增加两个静态的同步方法,只有两个个对象,输出结果 1.发信息 2.打电话 原因:static 锁的是整个Class
* 7.1个静态的同步方法和一个普通的同步方法,一个对象 ,输出结果 1.打电话 ,发短信 原因:静态同步方法锁的是Class 普通同步犯法锁的是调用者
* 8.1个静态的同步方法和一个普通的同步方法,两个对象 ,输出结果 1.打电话 ,发短信
*/
public class Demo06 {
public static void main(String[] args) throws InterruptedException {
Deat deat1 = new Deat();
Deat deat2 = new Deat();
new Thread(()->{
deat1.sendSms();
},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
deat2.call();
}, "B").start();
}
}
class Deat {
public static synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("发信息");
}
public synchronized void call() {
System.out.println("打电话");
}
}
总结:8.1个静态的同步方法和一个普通的同步方法,两个对象 ,输出结果 1.打电话 ,发短信 。 原因:不在一个锁
全面总结:
普通带锁方法:锁对象,同一对象下的才按顺序执行,如果是同一个类下的不同对象则不受影响。
普通不带锁方法:不受任何影响。
静态带锁方法:锁类,同一个类下的所有对象的所有带锁方法都得按顺序执行。
小结:
new this 具体的一个对象
static Class 唯一的模板
6、集合类不安全
并发下ArrayList 不安全
public class Demo07 {
public static void main(String[] args) {
//并发下ArrayList 不安全,sychronized
/*解决方案
1.List<String> strings1 = new Vector<>();
2.List<String> strings2 = Collections.synchronizedList(new ArrayList<String>());
3.List<String> strings = new CopyOnWriteArrayList<>();
*/
//CopyOnWrite 写入时复制 COW 计算机程序设计领域的一种优化策略
//多个线程调用的时候,List,读取的时候是固定的,写入时可能会被覆盖,那么就要先CopyOnWrite写入时复制一份出来调用,调用写完再放回去
//再写入的时候避免覆盖,造成数据问题
//读写分离
//CopyOnWriteArrayList 比 Vector牛在 Vector用了sychronized同步方法(同步方法自动加锁解锁)效率会低而CopyOnWriteArrayList用的是写入时候复制之后再set回去效率而高出很多
List<String> strings = new CopyOnWriteArrayList<>();
for (int i = 0; i <= 20; i++) {
new Thread(()->{
strings.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(strings);
}, String.valueOf(i)).start();
}
}
}
总结:CopyOnWrite 写入时复制 COW 计算机程序设计领域的一种优化策略,多个线程调用的时候,List,读取的时候是固定的,写入时可能会被覆盖,那么就要先CopyOnWrite写入时复制一份出来调用,调用写完再放回去,再写入的时候避免覆盖,造成数据问题。
CopyOnWriteArrayList 比 Vector牛在哪里
Vector 的 add方法源码:
// Vector 的 add方法
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
Vector用了sychronized同步方法(同步方法自动加锁解锁)效率会很多
CopyOnWriteArrayList 的add方法源码:
//CopyOnWriteArrayList 的add方法
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;//取出长度
Object[] newElements = Arrays.copyOf(elements, len + 1);//拿出复制
newElements[len] = e;//并且修改
setArray(newElements);//让后将数据set回去
return true;
} finally {
lock.unlock();
}
}
总结:CopyOnWriteArrayList用的是写入时候复制之后再set回去效率而高出很多
并发下Set不安全
public class Demo08 {
public static void main(String[] args) {
//ConcurrentModificationException 并发修改异常
/*
Set<String> strings = new HashSet<>();
解决方案:Set<String> strings1 = Collections.synchronizedSet(new HashSet<String>());
最佳解决方案:Set<String> strings = new CopyOnWriteArraySet<>();
*/
Set<String> strings = new CopyOnWriteArraySet<>();
for (int i = 0; i < 30; i++) {
new Thread(()->{
strings.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(strings);
},String.valueOf(i)).start();
}
}
}
HashSet源码
//HashSet源码:
public HashSet() {
map = new HashMap<>();
}
//HashSet本质就是 map key是无法重复的
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
private static final Object PRESENT = new Object();//不变的值
*/
HashMap不安全
public class Demo09 {
public static void main(String[] args) {
/*Map<String,String> stringHashMap = new HashMap<>();*/
Map<String, Object> stringHashMap = new ConcurrentHashMap<>();
for (int i = 1; i <= 30; i++) {
new Thread(()->{
stringHashMap.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 5));
System.out.println(stringHashMap);
},String.valueOf(i)).start();
}
}
}
7、Callable
FutureTask是个适配类,这里可以看到可以通过FutureTask可以两边互相勾搭使用,Runnavle又和Thread有关系
public class Demo10 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
/*new Thread(new Runnable(){}).start();*/
/*new Thread(new FutureTask<V>()).start();*/
/*new Thread(new FutureTask<>(Callable));*/
MyTest myTest = new MyTest();
FutureTask<Integer> integerFutureTask = new FutureTask<>(myTest);
new Thread(integerFutureTask,"A").start();
new Thread(integerFutureTask,"B").start();//两个对象会被缓存,效率高
Integer integer = integerFutureTask.get();//可能会出现堵塞,如果有耗时操作就会堵塞,或者用异步通信来处理
System.out.println(integer);
}
}
class MyTest implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("call()");//两个对象还是输出一个结果
//耗时操作
return 1024;
}
}
细节
1、有缓存
2、结果有可能需要等待,会阻塞
8、CountDownLatch、CyclicBarrier、Semaphore
CountDownLatch:
public class Demo11 {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 0; i < 6; i++) {
System.out.println(Thread.currentThread().getName() + "go out");
countDownLatch.countDown();//-1
}
countDownLatch.await();//等待计数器归零,在向下执行
System.out.println("关门");
}
输出结果:
maingo out
maingo out
maingo out
maingo out
maingo out
maingo out
关门
}
总结: countDownLatch.countDown();会让其自动减一 ,而在CountDownLatch下用await等待会讲等待其计数器归零时才会再向下执行,防止门提前关了里面的人出不去
CyclicBarrier:
public class Demo11 {
public static void main(String[] args) throws InterruptedException {
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> System.out.println("您已及其七颗龙珠,召唤了神龙"));
for (int i = 0; i < 7; i++) {
int temp = i;
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"您已收集"+temp+"颗龙珠");
try {
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
输出结果:
Thread-3您已收集3颗龙珠
Thread-5您已收集5颗龙珠
Thread-2您已收集2颗龙珠
Thread-1您已收集1颗龙珠
Thread-6您已收集6颗龙珠
Thread-4您已收集4颗龙珠
您已及其七颗龙珠,召唤了神龙
}
总结:CyclicBarrier可以设置指定最终达到的数值(代表for里的最大数),还能创建线程,CyclicBarrier下的await会等待其数值达到最终数值才会执行最终数值旁边的Runnable线程
Semaphore:
public class Demo11 {
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(2);
System.out.println(Thread.currentThread().getName() + "离开了车位");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}finally {
semaphore.release();//释放
}
},String.valueOf(i)).start();
}
}
}
总结:Semaphore就是信号量可以填写线程总数,acquire()许可证同意进行操作,release()释放让其离开,然后会让后面的数值上来代替直到走完位置
9、读写锁
public class Demo12 {
public static void main(String[] args) {
Mycachr mycachr = new Mycachr();
for (int i = 1; i < 6; i++) {
int finalI = i;
new Thread(()->{
mycachr.put(finalI,finalI);
},String.valueOf(i)).start();
}
for (int i = 1; i < 6; i++) {
int finalI = i;
new Thread(()->{
mycachr.read(finalI);
},String.valueOf(i)).start();
}
}
}
class Mycachr {
private volatile Map<Integer, Object> map = new HashMap<>();
private ReadWriteLock lock = new ReentrantReadWriteLock();
public void put(int key , Object value) {
lock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "写入" + key);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "写入ok");
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.writeLock().unlock();
}
}
public void read(int key) {
lock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "读取" + key);
map.get(key);
System.out.println(Thread.currentThread().getName() + "读取ok");
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.readLock().unlock();
}
}
1写入1
1写入ok
2写入2
2写入ok
3写入3
3写入ok
4写入4
4写入ok
5写入5
5写入ok
3读取3
5读取5
5读取ok
4读取4
4读取ok
1读取1
1读取ok
2读取2
2读取ok
3读取ok
}
总结: 读-读 可以共存
写-写 不可共存
读-写 不可共存
独占锁(写锁):一次只能被一个线程占有
共享锁 (读锁): 多个线程可以同时运行
10、阻塞队列
阻塞队列:
BlockingQueue
什么情况下面我们会使用阻塞队列
学会使用队列
添加,删除
四组API
1.抛出异常
2.不抛出异常
3.阻塞等待
4.超时等待
抛出异常
public static void test1() {
//抛出异常
//队列大小
ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
System.out.println(blockingQueue.add("c"));
System.out.println(blockingQueue.add("d"));//超出队列大小就会爆出Queue full异常
System.out.println("========================");
System.out.println(blockingQueue.element());//队首:放谁前面谁就是队首
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
/*System.out.println(blockingQueue.remove());*/
System.out.println("========================");
/*System.out.println(blockingQueue.element());*/ //如果讲这个检查队首放在最后输出就会出现:java.util.NoSuchElementException异常
}
不抛出异常
public static void test2() {
//不抛出异常
ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);//队列大小
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
System.out.println(blockingQueue.offer("d"));
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());
System.out.println(blockingQueue.poll());
}
阻塞队列
public static void test3() throws InterruptedException {
ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);//队列大小
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());
}
超时等待
public static void test4() throws InterruptedException {
ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
System.out.println(blockingQueue.offer("d", 2, TimeUnit.SECONDS));//这里会等两秒,超时就不管了直接跳过
System.out.println("======================");
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
}
SynchronousQueue put,take
public class Demo14 {
public static void main(String[] args) {
SynchronousQueue<Object> synchronousQueue = new SynchronousQueue<>();
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + "put 1");
synchronousQueue.put("1");
System.out.println(Thread.currentThread().getName() + "put 1");
synchronousQueue.put("2");
System.out.println(Thread.currentThread().getName() + "put 1");
synchronousQueue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "T1").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();
}
}, "T2").start();
}
总结:
同步队列
和其他BlockingQueue 不一样,synchronousQueue 不存储元素
put了一个元素,必须冲里面取出来tack();否则进去值
11、线程池 Executors
1.节约资源,2.提升影响的速度,3.方便管理
也就是可以达到:线程复用、可以控制最大的线程数、管理线程
线程池:三大方法
//三大方法
public class Demo15 {
public static void main(String[] args) {
/*ExecutorService executorService = Executors.newSingleThreadExecutor();//单个线程池*/
/*ExecutorService executorService = Executors.newFixedThreadPool(5);//创建一个固定大小的线程池*/
ExecutorService executorService = Executors.newCachedThreadPool();//可伸缩的,遇强则强,遇弱则弱
try {
for (int i = 0; i < 100; i++) {
executorService.execute(()->{
System.out.println(Thread.currentThread().getName() + "ok");
});
}
} catch (Exception e) {
e.printStackTrace();
}finally {
executorService.shutdown();
}
}
}
三大方法源码
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
这里看出都新建了一个ThreadPoolExecutor的类,点击进去看他的源码
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;
}
七大参数
4中拒绝方案
public class Demo15 {
public static void main(String[] args) {
//自定义线程池
ThreadPoolExecutor executorService = new ThreadPoolExecutor(
2,//默认处理口数
5,//最大处理口数
3,//超出空闲时间除了默认线程数其他都释放
TimeUnit.SECONDS,//时间单位
new LinkedBlockingDeque<>(3),//提供等待区的队列
Executors.defaultThreadFactory(),//一般不会变,默认线程模式
/*new ThreadPoolExecutor.AbortPolicy());//拒绝策列的一种,服务口和等待队列都满了的时候,还有人进来,抛出异常*/
/*new ThreadPoolExecutor.CallerRunsPolicy());//哪来的回哪去(main处理)*/
/*new ThreadPoolExecutor.DiscardPolicy());//拒绝策列的一种,会丢掉多余的任务,不会抛出异常*/
new ThreadPoolExecutor.DiscardOldestPolicy());//拒绝策列的一种,服务口和等待队列都满了的时候,后面进来的人会尝试去和第一个竞争
//最大承载:LinkedBlockingDeque + 最大处理口数
// RejectedExecutionException 超出最大承载异常
try {
for (int i = 0; i < 9; i++) {
executorService.execute(()->{
System.out.println(Thread.currentThread().getName() + "ok");
});
}
} catch (Exception e) {
e.printStackTrace();
}finally {
executorService.shutdown();
}
}
}
``
最大线程数如何定义
1.cpu 密集型 ,几核,就是几,可以保持cpu的效率最高
2.IO 密集型 > 判断你的程序中十分消耗IO的线程,
程序 15哥大型任务,io十分占用资源
public class Demo15 {
public static void main(String[] args) {
//最大线程数如何定义
// 1.cpu 密集型 ,几核,就是几,可以保持cpu的效率最高
// 2.IO 密集型 > 判断你的程序中十分消耗IO的线程,
// 程序 15哥大型任务,io十分占用资源
//自定义线程池
System.out.println(Runtime.getRuntime().availableProcessors());//获取cpu的核数
ThreadPoolExecutor executorService = new ThreadPoolExecutor(
2,//默认处理口数
/*5,//最大处理口数*/
Runtime.getRuntime().availableProcessors(),//最大处理口数
3,//超出空闲时间除了默认线程数其他都释放
TimeUnit.SECONDS,//时间单位
new LinkedBlockingDeque<>(3),//提供等待区的队列
Executors.defaultThreadFactory(),//一般不会变,默认线程模式
/*new ThreadPoolExecutor.AbortPolicy());//拒绝策列的一种,服务口和等待队列都满了的时候,还有人进来,抛出异常*/
/*new ThreadPoolExecutor.CallerRunsPolicy());//哪来的回哪去(main处理)*/
/*new ThreadPoolExecutor.DiscardPolicy());//拒绝策列的一种,会丢掉多余的任务,不会抛出异常*/
new ThreadPoolExecutor.DiscardOldestPolicy());//拒绝策列的一种,服务口和等待队列都满了的时候,后面进来的人会尝试去和第一个竞争
//最大承载:LinkedBlockingDeque + 最大处理口数
// RejectedExecutionException 超出最大承载异常
try {
for (int i = 0; i < 9; i++) {
executorService.execute(()->{
System.out.println(Thread.currentThread().getName() + "ok");
});
}
} catch (Exception e) {
e.printStackTrace();
}finally {
executorService.shutdown();
}
}
}
12、四大函数式接口(重点)
1.函数式接口
public static void main(String[] args) {
//函数型接口
/*Function<String, String> stringStringFunction = new Function<>() {
@Override
public Object apply(Object o) {
return null;
}
};*/
Function<String,String> function = (str)->{return str;};
System.out.println(function.apply("acd"));
2.断定型接口
//断定型接口
/* Predicate<String> objectPredicate = new Predicate<String>() {
@Override
public boolean test(String sre) {
return sre.isEmpty();
}
};*/
Predicate<String> predicate = (sre)->{return sre.isEmpty();};//这边检查是否为空
System.out.println(predicate.test(""));//输入参数,因为上面检查是否为空,输入不是为空的话就会返回false
3.消费型接口
//消费型接口 只有参数没有返回值
Consumer<String> stringConsumer = new Consumer<String>() {
@Override
public void accept(String src) {
System.out.println(src);
}
};
Consumer<String> consumer = (src)->{ System.out.println(src); };
consumer.accept("dads");
4.供给型接口
//供给型接口 没有参数只有返回值
/* Supplier<Integer> stringSupplier = new Supplier<Integer>() {
@Override
public Integer get() {
return 1024;
}
};*/
Supplier<Integer> supplier = ()->{return 1024;};
System.out.println(supplier.get());
13、Stream流式计算(重点)
大数据:存储+计算
集合、MySQL本质就是存储东西的;计算都应该交给流!
常见使用方法:map(对对象进行操作)filter(对对象进行过滤)sorted(排序)limit(限制输出数量)
题目要求:一分钟内完成此题,只能用一行代码实现!
现在有5个用户!筛选:
1、ID 必须是偶数
2、龄必须大于23岁
3、用户名转为大写字母
4、用户名字母倒着排序
5、只输出一个用户!
public class Demo17 {
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);
User u6 = new User(6, "f", 25);
List<User> objects = Arrays.asList(u1, u2, u3, u4, u5,u6);
objects.stream()
//要求
.filter(user->{return user.getId()%2==0;})
.filter(user ->{return user.getAge()>23;})
//大写
.map(user -> {return user.getName().toUpperCase();})
//反序
.sorted((uu1,uu2)->{return uu2.compareTo(uu1);})
//输出两个
.limit(2)
.forEach(System.out::println);
}
}
Forkjoin
Forkjoin 特点 工作窃取
这里面维护的是双端队列
public class Demo18 extends RecursiveTask<Long> {
private Long start;//1
private Long end;//1990900000
//临界值
private Long temp = 10000L;
public Demo18(Long start, Long end) {
this.start = start;
this.end = end;
}
//计算
@Override
protected Long compute() {
if ((end - start) > temp) {
Long sum = 0L;
for (int i = 1; i < 1_0000_0000; i++) {
sum += i;
}
System.out.println(sum);
return sum;
} else {
long middle = (start + end) / 2;//中间值
Demo18 test1 = new Demo18(start, middle);
test1.fork();//拆分值,把任务压到线程队列
Demo18 test2 = new Demo18(middle+1, end);
test2.fork();
return test1.join() + test2.join();
}
}
}
不同方法的执行速度
public class Demo19 {
//普通算法 结果 sum=4999999950000000时间676
public static void test1() {
Long sum = 0L;
long start = System.currentTimeMillis();
for (Long i = 1L; i < 1_0000_0000; i++) {
sum += i;
}
long end = System.currentTimeMillis();
System.out.println("sum=" + sum + "时间" + (end - start));
}
//使用forkjoin 结果 sum=4999999950000000时间326
public static void test2() throws ExecutionException, InterruptedException {
long start = System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTask<Long> demo18 = new Demo18(1L, 1_0000_0000L);
/*forkJoinPool.execute(demo18);//执行任务(无返回值)*/
ForkJoinTask<Long> submit = forkJoinPool.submit(demo18);//提交任务(有返回值)
Long sum = submit.get();
long end = System.currentTimeMillis();
System.out.println("sum=" + sum + "时间" + (end - start));
}
//用Strream并行流 结果 sum=5000000050000000时间94
public static void test3() {
long start = System.currentTimeMillis();
long sum = LongStream.rangeClosed(0L, 1_0000_0000L).parallel().reduce(0, Long::sum);
long end = System.currentTimeMillis();
System.out.println("sum=" + sum + "时间" + (end - start));
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
test3();
}
}
14、分支合并
15、异步回调
public class Demo20 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
/* //无返回值runAsync 异步回调
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "runAsync=>void");
});*/
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + "runAsync=>void");
return 1024;
});
System.out.println(completableFuture.whenComplete((t, u) -> { //whenComplete成功回调
System.out.println("t=>" + t);//正常的返回结果
System.out.println("u=>" + u);//输出错误信息
}).exceptionally((e) -> {//exceptionally 失败回调
System.out.println(e.getMessage());
int i = 233;
return i;
}).get());
}
}
runAsync:源码
总结runAsync是没有返回参数的,并且输入一个多线程
supplyAsync:源码
总结:supplyAsync拥有返回值,并且返回函数式接口Supplier,Supplier源码就是一个get获取任何值
16、JMM
什么是JMM:
JMM : Java内存模型,不存在的东西,概念!约定!
关于JMM的一些同步的约定︰
1、线程解锁前,必须把工作内存立刻刷回主存。
2、线程加锁前,必须读取主存中的最新值到工作内存中!
3、加锁和解锁是同一把锁!
#每个线程都有自己的工作内存,主存只有一个。
8种操作:
过程解析:1、线程会去主存里读取信息然后加载到工作内存,然后加锁
(read和load,带上lock)
2、线程的执行引擎会使用工作内存里的信息,更新完毕后赋值给工作内
(use和assign)
3、当线程执行完毕后会将工作内存的信息写入并且存储在主存,然后解锁
(write和store,带上unlock)
内存交互操作
内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类型的变量来说,load、store、read和write操作在某些平台上允许例外)
lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态
unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用
load (载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中
use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令
assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中
store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用
write (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中
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操作之前,必须把此变量同步回主内存
但这样存在一定的问题:当线程AB同时读取了一个数据,线程A还在处理的时候,线程B就先写入改变了主内存中的数据的时候,此时A是看不到数据发生改变的,会有覆盖的风险,所以此时要引入volatile。
17、volatile:保证可见性,不保证原子性,禁止指令重排
1、可见性:
此时B线程陷入了死循环并且运行不会停止
解决办法:在变量中加入volatile
2、不保证原子性:
package JMM;
public class VDemo2 {
private volatile static int num=0;
public static void add(){
num++;
}
public static void main(String[] args) {
for (int i = 0; i <20 ; i++) {
new Thread(()->{
for (int j = 0; j <1000 ; j++) {
add();
}
}).start();
}
while(Thread.activeCount()>2)
{
Thread.yield();
//让主线程变成就绪状态,直到线程只有gc线程和主线程才继续执行下面操作
}
System.out.println(num);
}
}
输出结果是不确定的,因为不保证原子性,大致在19679左右。
解决办法:
1、加锁,不论是synchronized还是lock都可以(可行)
2、使用JUC下的原子类
反编译查看一下:使用javap -c VDemo2.class
从虚拟机上来看,add方法执行了获取值,+1,写入这个值三步操作。
juc原子类:
使用原子类进行操作:
public class Demo21 {
private volatile static AtomicInteger sum = new AtomicInteger();
public static void add() {
sum.getAndIncrement();//加一
}
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
while (Thread.activeCount() > 2) { //查看存活线程数 ,这里必须留两个 因为是 main 和gc
Thread.yield();//别离
}
}
System.out.println(Thread.currentThread().getName() + " " + sum);
}
}
#发现答案正确,说明加锁和原子类操作都可行。
这些类的底层都直接和操作系统挂钩!在内存中修改值 ! Unsafe类是一个很特殊的存在!
3、禁止指令重排:
什么是指令重排:你写的程序,计算机并不是按照你写的那样去执行的。
源代码–>编译器优化的重排–>指令并行也可能会重排–>内存系统也会重排—>执行
处理器在进行指令重排的时候,考虑:数据之间的依赖性!
指令重排可能会出现的问题:
volatile可以避免指令重排:操作系统内存在内存屏障。
作用:
1、保证特定的操作的执行顺序!
2、可以保证某些变量的内存可见性(利用这些特性volatile实现了可见性)
示意图:
Volatile是可以保持可见性。不能保证原子性,由于内存屏障,可以保证避免指令重排的现象产生!
内存屏障使用最多的地方单列模式(DCL懒汉式)
18、深入单例模式
饿汉式
//饿汉式
public class Demo22 {
private byte[] data1 = new byte[1024 * 1024]; //房费资源
private byte[] data2 = new byte[1024 * 1024];
private byte[] data3 = new byte[1024 * 1024];
private byte[] data4 = new byte[1024 * 1024];
private Demo22() {
}
private final static Demo22 dem = new Demo22();
private static Demo22 getInstance() {
return dem;
}
}
总结:一开始就把对象创建好,然后返回对象给调用方法的人,缺点浪费空间,优点,一开始用final写死了对象,对象怎么样都不会出现新的。
懒汉式
//懒汉式
public class LazyMan {
private static LazyMan lazyMan;
private LazyMan() {
System.out.println(Thread.currentThread().getName() + "ok");
}
public static LazyMan getInstance() {
if (lazyMan == null) {
lazyMan = new LazyMan();
System.out.println(lazyMan);
}
return lazyMan;
}
public static void main(String[] args) {
new Thread(()->{
LazyMan.getInstance();
}).start();
}
}
多线程模式下出现问题
public class LazyMan {
private static LazyMan lazyMan;
private LazyMan() {
System.out.println(Thread.currentThread().getName() + "ok");
}
public static LazyMan getInstance() {
if (lazyMan == null) {
lazyMan = new LazyMan();
System.out.println(lazyMan);
}
return lazyMan;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
LazyMan.getInstance();
}).start();
}
}
}
总结:有需求再再创建,调用getInstance的时候,判断定义里的对象是否为空,不为空就返回新对象,优点有需求才创建,节省空间,缺点多线程下会出现问题,说明这并不符合单例这个条件
DCL懒汉式
public class LazyMan {
private volatile static LazyMan lazyMan;
/*
* 由于创建对象是分成了三步骤所以必须加上volatile防止指令重排
* 1.分配内存空间
* 2.执行构造方法
* 3.返回对象
* 如果随机执行123就没错,但是会有几率132就出错
* */
private LazyMan() {
System.out.println(Thread.currentThread().getName() + "ok");
}
public static LazyMan getInstance() {
if (lazyMan == null) {
synchronized (LazyMan.class) {
if (lazyMan == null) {
lazyMan = new LazyMan();
System.out.println(lazyMan);
}
}
}
return lazyMan;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
LazyMan.getInstance();
}).start();
}
}
}
总结:再获取对象时候,加上判断,如果LazyMan为空则加上类锁,这也就能防止多个线程同时去创建LazyMan。但是还是有缺陷,如果我们此时用反射获取对象,则会出现问题
使用反射获取对象
public class LazyMan {
private volatile static LazyMan lazyMan;
/*
* 由于创建对象是分成了三步骤所以必须加上volatile防止指令重排
* 1.分配内存空间
* 2.执行构造方法
* 3.返回对象
* 如果随机执行123就没错,但是会有几率132就出错
* */
//空参构造器
private LazyMan() {
System.out.println(Thread.currentThread().getName()+" " + "ok");
}
public static LazyMan getInstance() {
if (lazyMan == null) {
synchronized (LazyMan.class) {
if (lazyMan == null) {
lazyMan = new LazyMan();
System.out.println(lazyMan);
}
}
}
return lazyMan;
}
public static void main(String[] args) throws Exception {
LazyMan instance = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);//获取LazyMan的空参构造器
declaredConstructor.setAccessible(true);//取消私有化
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance2);
}
}
小结:发现这里的两个值已经不是同一个了,按照单列模式应该一样才对,说明反射能够破坏这种单列
为了保持单列模式获取的值需要一样,在空参的地方添加异常,以便接下来的判断(重)
public class LazyMan {
private volatile static LazyMan lazyMan;
/*
* 由于创建对象是分成了三步骤所以必须加上volatile防止指令重排
* 1.分配内存空间
* 2.执行构造方法
* 3.返回对象
* 如果随机执行123就没错,但是会有几率132就出错
* */
//空参构造器
private LazyMan() {
synchronized (LazyMan.class) {
if (lazyMan != null) {
throw new RuntimeException("不要试图使用反射破坏异常");
}
}
}
public static LazyMan getInstance() {
if (lazyMan == null) {
synchronized (LazyMan.class) {
if (lazyMan == null) {
lazyMan = new LazyMan();
System.out.println(lazyMan);
}
}
}
return lazyMan;
}
public static void main(String[] args) throws Exception {
LazyMan instance = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);//获取LazyMan的空参构造器
declaredConstructor.setAccessible(true);//取消私有化
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance2);
}
}
本来是有一个instance去调用了LazyMan的空参方法,是可以启动异常的,这样看来确实解决了上面的问题,但是还有办法再防止这种反射,就是设置标志位,但是防止不了反编译
但是两个都是反射创建的呢?
public static void main(String[] args) throws Exception {
/* LazyMan instance = LazyMan.getInstance();*/
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);//获取LazyMan的空参构造器
declaredConstructor.setAccessible(true);//取消私有化
LazyMan instance2 = declaredConstructor.newInstance();
LazyMan instance = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance2);
}
结果:说明反射的确能解决这个问题,但是无法防止对方反编译去获取我们的标志位从而使用反射改变标志位和反射结果:
所以我们打算去枚举看看说是反射不能破坏枚举的单列
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance() {
return INSTANCE;
}
}
class Test{
public static void main(String[] args) {
EnumSingle instance = EnumSingle.INSTANCE;
EnumSingle instance2 = EnumSingle.INSTANCE;
输出结果确实两个相同符合单列模式
接下来试试反射:
这里idea说我们这是空参所以我们试试空参
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance() {
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws Exception {
EnumSingle instance = EnumSingle.INSTANCE;
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
EnumSingle instance2 = declaredConstructor.newInstance();
//报错:java.lang.NoSuchMethodException: com.example.jvm01.EnumSingle.<init>() ,没有空参idea骗了我们
System.out.println(instance);
System.out.println(instance2);
}
}
发现这报的错误是java.lang.NoSuchMethodException: com.example.jvm01.EnumSingle.() ,没有空参idea骗了我们,如果正确的报错应该是 ”Cannot reflectively create enum objects“
所以我们去反编译看看
这边显示也是空参说明反编译也在骗我们
所以为我们需要使用更专业的工具
这也会生成一个idea文件,打开发现
这边枚举就是一个类只是继承了枚举,这个工具表达了这个参数并不是空参而是String,int的所以我们在Class类加上参数
代码变为:
class Test{
public static void main(String[] args) throws Exception {
EnumSingle instance = EnumSingle.INSTANCE;
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
declaredConstructor.setAccessible(true);
EnumSingle instance2 = declaredConstructor.newInstance();
//报错:java.lang.NoSuchMethodException: com.example.jvm01.EnumSingle.<init>() ,没有空参idea骗了我们
System.out.println(instance);
System.out.println(instance2);
}
}
这个
这个输出来的异常才是正确的,最后成功解决了这个问题
19、深入理解CAS
什么是GAS
public class Demo23 {
public static void main(String[] args) {
//GAS compareAndSet :比较并交换
AtomicInteger atomicInteger = new AtomicInteger(2020);
//expect 期待 , update 更新
// public final boolean compareAndSet(int expect, int update)
//如果期望达到了,那么就更新,否则不更新,CAS是CPU的并发原语
System.out.println(atomicInteger.compareAndSet(2020,2021));
System.out.println(atomicInteger.get());
/*atomicInteger.getAndIncrement();*/
System.out.println(atomicInteger.compareAndSet(2020,2023));
System.out.println(atomicInteger.get());
}
}
AtomicInteger的getAndIncrement方法源码
精髓!!!!:var1对象等于var2内存地址偏移值,对应如果这个值还是我们希望的var5,那么我们就让这个值加一
总结:CAS: 比较当前工作内存中的值,若果这个值是期望中的,那么执行该操作 ,如果不是会一直循环因为底层是do while会一直循环
缺点: 1.循环耗时 2.一次性只能保证一个共享变量的共享性 3.ABA问题
20、原子引用(解决CAS的ABA问题)
ABA问题(狸猫换太子)
public class Demo23 {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2020);
//捣乱的
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(2021, 2020));
System.out.println(atomicInteger.get());
//最终期望
System.out.println(atomicInteger.compareAndSet(2020, 666));
System.out.println(atomicInteger.get());
/*
* 虽然最终期望确实完成可但是我们不希望数据被人动过了还不知道
* */
}
}
![在这里插入图片描述](https://img-blog.csdnimg.cn/img_convert/e2e9ff7552ce752358d85aeac91efa9b.png)
虽然最终期望确实完成可但是我们不希望数据被人动过了还不知道下面来解决这个问题
我们使用另外一个 AtomicStampedReference 原子类 带有版本号时间戳,可以每次记录加一类似于乐观锁
public class Demo24 {
//initialRef 期望值 , initialStamp 版本号时间戳
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<Integer>(1, 1);
public static void main(String[] args) {
new Thread(()->{
int stamp = atomicStampedReference.getStamp();//获取当前最新版本号
System.out.println("a1="+stamp);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicStampedReference.compareAndSet(1, 2,
atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
System.out.println("a2=" + atomicStampedReference.getStamp());
System.out.println(atomicStampedReference.compareAndSet(2, 1,
atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
System.out.println("a3=" + atomicStampedReference.getStamp());
},"a").start();
//乐观锁原理相同
new Thread(()->{
int stamp = atomicStampedReference.getStamp();
System.out.println("b1="+stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicStampedReference.compareAndSet(1,6, stamp, stamp + 1));
System.out.println("b2=" + atomicStampedReference.getStamp());
},"b").start();
}
}
这里成功避免了ABA错误,如果超出Integer类型的范围-128~127,就会在堆里卖弄新建一个对象并不会服用对象,然后我们看一下原子类AtomicStampedReference的compareAndSet方法的源码源码
阿里巴巴手册
总结:发现都是用==来判断的而并不是equals来判断,但是 ==是比较对象地址的而Integer类型的范围-128~127,就会在堆里卖弄新建一个对象并不会服用对象(这里是个大坑)
21、可重入锁、公平锁非公平锁、自旋锁
公平锁和非公平锁:
公平锁:非常公平,不能够插队,必须先来后到!FIFO
非公平锁:非常不公平,可以插队(默认都是非公平)
我们看看ReentrantLock的源码
可重入锁
可重入锁synchronized 版本
public class Demo25 {
public static void main(String[] args) {
Deap deap = new Deap();
new Thread(()->{
deap.sms();
},"A").start();
new Thread(()->{
deap.sms();
},"B").start();
}
}
class Deap {
public synchronized void sms() {
System.out.println(Thread.currentThread().getName() + "sms");
call();
}
public synchronized void call() {
System.out.println(Thread.currentThread().getName() + "call");
}
}
可重入锁Lock版本(递归锁 )
public class Demo25 {
public static void main(String[] args) {
Deap deap = new Deap();
new Thread(()->{
deap.sms();
},"A").start();
new Thread(()->{
deap.sms();
},"B").start();
}
}
class Deap {
Lock lock = new ReentrantLock();
public void sms() {
lock.lock();
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "sms");
call();
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
lock.unlock();
}
}
public void call() {
lock.lock();
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "call");
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
lock.unlock();
}
}
}
总结:两个锁只要获得外面的锁之后,就能够拿到里面的锁,Lock和synchronized 的区别在需要手动释放锁,并且枷锁的次数和释放的次数要一样
自旋锁
自己创建一个自己的锁用CAS完成
public class Demo26 {
//Thread null
AtomicReference<Thread> 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() + "==>myunlock");
atomicReference.compareAndSet(thread, null);
}
}
然后去用线程调用
public class Demo27 {
public static void main(String[] args) throws InterruptedException {
Demo26 demo26 = new Demo26();
new Thread(()->{
demo26.myLock();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
demo26.myUnLock();
}
},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
demo26.myLock();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
demo26.myUnLock();
}
},"B").start();
}
}
总结:B线程这里一定会等待T1完成比较交换这个时候自旋锁里已经被A占着了(自旋锁条件:线程不为空及自转)所以B线程会一直自旋到A线程完成CAS的比较交换完成才不在进行自旋操作再进入到myunlock里完成解锁操作
优点:
自旋锁不会使线程状态发生切换,一直处于用户态,即线程一直都是active的;不会使线程进入阻塞状态,减少了不必要的上下文切换,执行速度快。
缺点:
如果某个线程持有锁的时间过长,就会导致其它等待获取锁的线程进入循环等待,消耗CPU。使用不当会造成CPU使用率极高。
上面Java实现的自旋锁不是公平的,即无法满足等待时间最长的线程优先获取锁。不公平的锁就会存在“线程饥饿”问题。
死锁排查
死锁排查:
1、使用jps -l定位进程号
例如:jps -l
2、使用jstack 进程号 找到死锁问题
例如:jstack 110