狂神视频地址:https://www.bilibili.com/video/BV1B7411L7tE
准备工作
- 新建一个Maven项目,引入一个lombok依赖.
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
</dependencies>
- 项目配置
JDK8
- 下载JDK帮助文档
1. 什么是JUC
Java.util.concurrent
2. 线程和进程
进程:一个程序,QQ.exe Music.exe 程序的集合;
一个进程往往可以包含多个线程,至少包含一个!
Java默认有几个线程? 2 个 :mian、GC。
线程:开了一个进程 Typora,写字,自动保存(线程负责的)。
2.1 Java 真的可以开启线程吗?
java是开不了线程的
new Thread().start();
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
//调用本地方法,底层的C++,JAVA无法直接操作硬件
private native void start0();
首先。start()是一个synchronized方法,同步方法,安全,这个方法会把当前线程加入一个线程组,调用了start0()方法,这个start0()是用native修饰的,也就是本地方法。所以最后是调用了本地方法,JAVA是没有权限开启线程的。start()调用了本地的C++方法,因为java是运行在虚拟机之上的,无法直接操作硬件。
2.2 并发、并行
并发编程:并发,并行
并发:多个线程操作同一个资源
- 并发:cpu只有一个核:多线程操作同一个资源(cpu通过线程间的快速交替,模拟出来多条线程,看似并行,实际串行)
- 并行:cpu有多个核,多个线程可以同时执行,可以通过线程池完成
public class Demo01 {
public static void main(String[] args) {
//获取CPU核数
//CPU 密集型,IO密集型
System.out.println(Runtime.getRuntime().availableProcessors());
}
}
并发编程的本质:充分利用CPU的资源
2.3 线程状态
public enum State {
//新生
NEW,
//运行
RUNNABLE,
//阻塞
BLOCKED,
//等待(死死的等)
WAITING,
//超时等待(到期就不等了)
TIMED_WAITING,
//终止
TERMINATED;
}
2.4 wait/sleep区别
- 来自不同的类
wait()来自Object类,sleep()来自Thread类 - 关于锁的释放
wait()会释放锁,sleep()不会释放锁,可以理解为抱着锁睡觉 - 使用范围不同
wait()只能在同步代码块中使用,sleep()可以在任何地方使用 - 是否需要捕获异常
wait()不需要捕获异常,sleep()必须要捕获异常
3. LOCL锁(重点)
3.1 传统的synchronized锁
在公司真正的多线程开发中,线程就是一个单独的资源类,没有任何附属的操作(类中只有属性和方法),为了降低耦合性,不会用类去实现接口,因为实现类接口就不是OOP编程了,而且实现了接口的话耦合性变高,如果让类实现了Runnable,这个类就只是一个线程类了
如下代码,只是把Ticket作为了资源类,并没有让它实现Runnable接口
/**
* 真正的多线程开发,公司中的开发,降低耦合性
* 线程就是一个单独的资源类,没有认合附属操作
* 1. 属性,方法
*/
//基本的卖票例子
public class SaleTicket01 {
public static void main(String[] args) {
//多线程操作
//并发:多个线程操作同一个资源类,把资源类丢入线程
Ticket ticket = new Ticket();
// @FunctionalInterface 函数式接口,JDK1.8 Lambda表达式
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();
}
}
//资源类 OOP编程
class Ticket {
//属性,方法
//票数
private int num = 30;
//卖票方式
// synchronized 本质:队列,锁
public synchronized void sale() {
if (num > 0) {
System.out.println(Thread.currentThread().getName()+"卖出了第"+(num--)+"张票,剩余:"+num);
}
}
}
3.2 Lock锁
公平锁:十分公平,先来后到,排队
非公平锁:不公平,可以插队
(默认是非公平锁,是为了公平,比如一个线程要3s,另一个线程要3h,难道一定要让3h的锁先来就先执行吗)
public class SaleTicket02 {
public static void main(String[] args) {
//并发:多个线程操作同一个资源类,把资源类丢入线程
Ticket2 ticket = new Ticket2();
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();
}
}
//lock三部曲
// 1. new ReentrantLock()
// 2. lock.lock(); //加锁
// 3. finally => lock.unlock(); //解锁
class Ticket2 {
//属性,方法
private int num = 30;
Lock lock = new ReentrantLock();
public void sale() {
lock.lock(); //加锁
try {
// 业务代码
if (num > 0) {
System.out.println(Thread.currentThread().getName()+"卖出了第"+(num--)+"张票,剩余:"+num);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock(); //解锁
}
}
}
3.3 Synchronized和Lock锁的区别
- Synchronized:内置的Java关键字,Lock:是一个java类
- Synchronized:无法判断获取锁的状态,Lock:可以判断是否获取了锁
- Synchronized:会自动释放锁,Lock:必须要手动释放锁,如果不释放锁,会死锁
- Synchronized:线程1(获得锁,阻塞),线程2(等待,傻傻的等),Lock:Lock锁不一定会等待,Lock有一个方法Lock.tryLock();会去尝试获取锁
- Synchronized:可重入锁,不可以在中断,非公平, Lock:可重入,可以判断锁,可以设置公平或者非公平
- Synchronized:适合锁少量的代码同步问题,Lock:适合锁大量的同步代码
4. 生产者消费者问题
4.1 synchronized版
/**
* 线程之间的通信问题:生产者消费者问题
* 线程交替执行 A B 操作同一个变量 num=0
* A num+1
* B num-1
*/
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 num = 0;
// +1
public synchronized void increment() throws InterruptedException {
if (num != 0) {
//等待
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName()+"=>"+num);
//通知其他线程,我+1完了
this.notifyAll();
}
// -1
public synchronized void decrement() throws InterruptedException {
if (num == 0) {
//等待
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName()+"=>"+num);
//通知其他线程,我-1完了
this.notifyAll();
}
}
synchronized版本存在的问题
如果增加两个线程,即两个线程加,两个线程减,得到如下结果,并不能1,0交替,而且出现了2,3
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
问题原因分析
看jdk1.8的官方文档,找到Object类的wait()方法
导致当前线程等待,直到另一个线程调用此对象的notify()方法或notifyAll()方法,或指定的时间已过。
线程也可以唤醒,而不会被通知,中断或超时,即所谓的虚假唤醒 。 虽然这在实践中很少会发生,但应用程序必须通过测试应该使线程被唤醒的条件来防范,并且如果条件不满足则继续等待。 换句话说,等待应该总是出现在循环中,就像这样:
while (<condition does not hold>)
obj.wait(timeout);
... // Perform action appropriate to condition
} ```
结论
就是用if判断的话,唤醒后线程会从wait之后的代码开始运行,不会重新判断if条件,直接继续运行if代码块之后的代码,而如果使用while的话,也会从wait之后的代码运行,但是唤醒后会重新判断循环条件,如果不成立再执行while代码块之后的代码块,成立的话继续wait。
解决方法
把if判断改成while判断等待,因为if判断进if之后不会停,用while判断的话,变量一旦被修改,另外一个线程拿到锁之后,就会等待,防止虚假唤醒。
public synchronized void increment() throws InterruptedException {
while (num != 0) {
//等待
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName()+"=>"+num);
//通知其他线程,我+1完了
this.notifyAll();
}
4.2 JUC版(lock锁)
public class B {
public static void main(String[] args) {
Data2 data = new Data2();
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();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
class Data2{
private int num = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
// +1
public void increment() throws InterruptedException {
lock.lock();
try {
while (num != 0) {
//等待
condition.await();
}
num++;
System.out.println(Thread.currentThread().getName()+"=>"+num);
//通知其他线程,我+1完了
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
// -1
public void decrement() throws InterruptedException {
lock.lock();
try {
while (num == 0) {
//等待
condition.await();
}
num--;
System.out.println(Thread.currentThread().getName()+"=>"+num);
//通知其他线程,我-1完了
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
任何一个新的技术,绝对不是仅仅只是覆盖了原来的技术,优势和补充!
Condition 精准的通知和唤醒线程
//A执行完调用B,B执行完调用C,C执行完调用A
public class C {
public static void main(String[] args) {
Data3 data = new Data3();
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 Data3{
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private int num = 1; //1A 2B 3C
public void printA() {
lock.lock();
try {
//业务:判断-执行-通知
while (num != 1){
//等待
condition1.await();
}
System.out.println(Thread.currentThread().getName()+"=>AAAAA");
//唤醒,唤醒指定的人,B
num = 2;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB() {
lock.lock();
try {
//业务:判断-执行-通知
while (num != 2){
//等待
condition2.await();
}
System.out.println(Thread.currentThread().getName()+"=>BBBBB");
//唤醒,唤醒指定的人,B
num = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC() {
lock.lock();
try {
//业务:判断-执行-通知
while (num != 3){
//等待
condition3.await();
}
System.out.println(Thread.currentThread().getName()+"=>CCCCC");
//唤醒,唤醒指定的人,B
num = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
5. 八锁
锁是什么,如何判断锁的是谁?
8锁,就是关于锁的8个问题
- 问题1:标准情况下,先发短信,还是先打电话?
/*
问题1:标准情况下,先打印发短信,还是先打印打电话?结果是:先打印发短信,后打印打电话
原因:不能回答先调用A线程,这是错误的,不是先调用先执行,这是锁的问题,因为被Synchronized修饰的
方法,锁的对象是方法的调用者,所以调用两个方法的对象都是phone,但是现在phone只有一个,也就是说着两个方
法现在用的是同一把锁,谁先拿到,谁就先执行
*/
public class Test1 {
public static void main(String[] args) {
phone phone = new phone();
new Thread(()->{ phone.sendSms(); },"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
new Thread(()->{ phone.call(); },"B").start();
}
}
class phone{
//synchronized 锁的对象是方法的调用者
public synchronized void sendSms() {
System.out.println("发短信");
}
public synchronized void call() {
System.out.println("打电话");
}
}
结果:先打印发短信,后打印打电话
- 问题2:给发短信方法加延时4s,程序的执行情况
public synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
结果:经过4s之后打印发短信,然后立马打印打电话。
如果把主函数中延时改为5s,那么程序运行情况是经过4s打印发送短信,然后经过1s打印打电话(主程序的延时是同时进行的)
public class Test1 {
public static void main(String[] args) {
phone phone = new phone();
new Thread(()->{ phone.sendSms(); },"A").start();
try {
TimeUnit.SECONDS.sleep(5);
} catch (Exception e) {
e.printStackTrace();
}
new Thread(()->{ phone.call(); },"B").start();
}
}
- 问题3:当调用普通方法,而不是synchronized方法时,程序的执行情况
public class Test1 {
public static void main(String[] args) {
phone phone = new phone();
new Thread(()->{ phone.sendSms(); },"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
new Thread(()->{ phone.sayHello(); },"B").start();
}
}
class phone{
//synchronized 锁的对象是方法的调用者
public synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call() {
System.out.println("打电话");
}
public void sayHello()
{
System.out.println("hello");
}
}
结果:1秒先打印Hello,4秒后打印发短信
先执行普通方法,因为普通方法没有锁,不受锁的影响
- 问题4:两个对象分别调用synchronized方法时,程序的执行情况
public static void main(String[] args) {
//两个对象,两个调用者,两把锁
phone phone1 = new phone();
phone phone2 = new phone();
new Thread(()->{ phone1.sendSms(); },"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
new Thread(()->{ phone2.call(); },"B").start();
}
结果:先打电话,后发短信。
两个对象,两个调用者,两把锁,因为锁不一样,所以耗时短的先输出。
- 问题5:增加两个静态的同步方法,只有一个对象,程序的执行情况
public class Test1 {
public static void main(String[] args) {
phone phone = new phone();
new Thread(()->{ phone.sendSms(); },"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
new Thread(()->{ phone.call(); },"B").start();
}
}
class phone{
//static 静态方法,类一加载就有了,锁的是Class
public static synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static synchronized void call() {
System.out.println("打电话");
}
}
结果:先发短信,后打电话。
- 问题6:两个静态的同步方法,两个对象,程序的执行情况
public class Test1 {
public static void main(String[] args) {
phone phone1 = new phone();
phone phone2 = new phone();
new Thread(()->{ phone1.sendSms(); },"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
new Thread(()->{ phone2.call(); },"B").start();
}
}
结果:先发短信,后打电话。
两个对象的Class模板只有一个,static锁的是Class
- 问题7:一个静态同步方法,一个普通同步方法,只有一个对象,程序的执行情况
public class Test1 {
public static void main(String[] args) {
phone phone = new phone();
new Thread(()->{ phone.sendSms(); },"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
new Thread(()->{ phone.call(); },"B").start();
}
}
class phone{
//静态同步方法,锁的是Class模板
public static synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
//普通同步方法,锁的是调用者,不需要去等待锁
public synchronized void call() {
System.out.println("打电话");
}
}
结果:先打电话,后发短信。
不是同一个锁,锁调用者的不需要等待,锁Class的要等待
- 问题8:一个静态同步方法,一个普通同步方法,两个对象,程序的执行情况
public static void main(String[] args) {
phone phone1 = new phone();
phone phone2 = new phone();
new Thread(()->{ phone1.sendSms(); },"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
new Thread(()->{ phone2.call(); },"B").start();
}
结果:先打电话,后发短信。
两个锁,还是锁的对象不一样
小结
普通同步方法:(new this) 锁的是调用者,是一个具体的对象
静态同步方法:(static Class) 锁的是Class模板,是唯一的
6. 集合类不安全
6.1 List不安全
CopyOnWriteArrayList
- Arrarlist测试(单线程)
public class ListTest01 {
public static void main(String[] args) {
List<String> list = Arrays.asList("1", "2", "3");
list.forEach(System.out::println);
}
}
- 多线程下ArrayList还安全吗?
现在我们创建10个线程来向List添加元素
public class ListTest01 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
for (int i = 1; i <= 10; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
//UUID(Universally Unique Identifier):通用唯一识别码,是一种软件建构的标准.UUID是指在一台机器上生成的数字,它保证对在同一时空中的所有机器都是唯一的
//String.valueOf(i),给10个线程分别取名字
}
}
}
-
并发下 ArrayList 不安全的解决方案
- List list = new Vector<>(); //vector默认是安全的(是同步方法)
- List list = Collections.synchronizedList(new ArrayList<>); //工具类
- List list = new CopyOnWriteArrayList<>();//CopyOnWrite,写入时复制,COW,计算机程序设计领域的一种优化策略
多个线程调用的时候,list,读取的时候 固定的,写入(覆盖)
在写入的时候避免覆盖,造成数据问题!
读写分离(写入的时候复制一个数组出来,写入完之后再插入进去,保证线程安全)
-
CopyOnWriteArrayList 比 Vector 好在哪里?
Vector的add方法有Synchronized修饰(看源码),有Synchronized修饰的方法,效率都比较低
CopyOnWriteArrayList—— add源码
6.2 Set不安全
public class SetTest {
public static void main(String[] args) {
// Set<String> set = new HashSet<>(); //ConcurrentModificationException
// Set<String> set = Collections.synchronizedSet(new HashSet<>());
Set<String> set = new CopyOnWriteArraySet<>();
for (int i = 1; i <= 30; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
HashSet的底层是什么?
public HashSet() {
map = new HashMap<>();
}
//add set本质就是map,key是无法重复的
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
private static final Object PRESENT = new Object(); //PRESENT 不变的值
6.2 Map不安全
HashMap<Object, Object> map = new HashMap<>();
问题1:Map是这样用的吗?
答:工作中不这样用
问题2:map默认等价于什么
其中16是初始容量,0.75是加载因子
HashMap<Object, Object> map1 = new HashMap<>(16,0.75);
public class MapTest {
public static void main(String[] args) {
// HashMap<Object, Object> map = new HashMap<>(); //ConcurrentModificationException
// HashMap<Object, Object> map = (HashMap<Object, Object>) Collections.synchronizedMap(new HashMap<>());
Map<Object, Object> map = new ConcurrentHashMap<>();
for (int i = 0; i < 30; i++) {
new Thread(()->{
map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
System.out.println(map);
},String.valueOf(i)).start();
}
}
}
7. Callable
官方文档:Callable接口类似于Runnable ,因为它们都是为其实例可能由另一个线程执行的类设计的。 然而, Runnable不返回结果,也不能抛出被检查的异常。
- 可以有返回值
- 可以抛出异常
- 方法不同,run() / call()
根据底层源码
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
Thread 只能接受Runnable类型的参数,不能接受Callable类型的参数
但是,Runnable有一个实现类:Class FutureTask
FutureTask可以接受Callable类型的参数
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//new Thread(new Runnable()).start();
//new Thread(new FutureTask<V>()).start();
//new Thread(new FutureTask<V>(Callable)).start();
new Thread().start(); //怎么启动callable
MyThread myThread = new MyThread();
FutureTask futureTask = new FutureTask<>(myThread); //适配类
new Thread(futureTask,"A").start();
Object o = futureTask.get(); //获取Callable的返回结果,get方法可能会产生阻塞,一般放到最后,或者使用异步通信
System.out.println(o);
}
}
class MyThread implements Callable<String>{
@Override
public String call() throws Exception {
System.out.println("call()");
return "aaa";
}
}
问题:如果new两个线程会打印几个call()?
答案:一个
分析:结果会被缓存,效率高
8. 常用的辅助类(必会)
8.1 CountDownLatch(减法计数器)
//计数器
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
//总数是6,必须要在执行任务的时候,再使用
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i <= 6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+" go out");
countDownLatch.countDown(); //数量-1
},String.valueOf(i)).start();
}
countDownLatch.await(); //等待计数器归零,再向下执行
System.out.println("Close door");
}
}
原理:
countDownLatch.countDown(); // 数量-1
countDownLatch.await(); // 等待计数器归零,然后再向下执行
每次有线程调用 countDown() 数量-1,假设计数器变为0,countDownLatch.await() 就会被唤醒,继续执行!
如果 countDown()没有减到0,后面的程序是不会执行的
8.2 CylicBarrier(加法计数器)
public class CylicBarrierDemo {
public static void main(String[] args) throws InterruptedException {
//集齐7颗龙珠召唤神龙
//召唤龙珠的线程
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
System.out.println("召唤神龙成功");
});
for (int i = 1; i <= 7; i++) {
final int temp = i;
new Thread(()->{
//lambda表达式获取不到i
System.out.println(Thread.currentThread().getName()+"收集了"+temp+"颗龙珠");
try {
cyclicBarrier.await(); //等待
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
8.3 Semaphore(信号量)
//模拟停车,假设现在有6辆车,但是只有3个停车位
//在有限的情况下使其有秩序,限流的时候可以使用
public class SemaphoreDemo {
public static void main(String[] args) throws InterruptedException {
//线程数量:停车位
Semaphore semaphore = new Semaphore(3);
for (int i = 1; i <= 6; i++) {
new Thread(()->{
// acquire() 得到
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"抢到车位");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+"离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release(); // release() 释放
}
},String.valueOf(i)).start();
}
}
}
原理:
semaphore.acquire():获得,假设如果已经满了,等待,等待被释放为止!
semaphore.release():释放,会将当前的信号量释放 + 1,然后唤醒等待的线程!
作用: 多个共享资源互斥的使用!并发限流,控制最大的线程数!
9. 读写锁ReadWriteLock
读的时候可以被多线程同时读,写的时候只能有一个线程去写。
独占锁(写锁):一次只能被一个线程占有
共享锁(读锁):可以被多个线程同时占有
读-读:可以共存
读-写:不能共存
写-写:不能共存
public class ReadWriteLockTest {
public static void main(String[] args) {
//MyCache myCache = new MyCache();
MyCacheLock myCache = new MyCacheLock();
//写入
for (int i = 1; i <= 5; i++) {
final int temp = i;
new Thread(()->{
myCache.put(temp+"",temp+"");
},String.valueOf(i)).start();
}
//读取
for (int i = 1; i <= 5; i++) {
final int temp = i;
new Thread(()->{
myCache.get(temp+"");
},String.valueOf(i)).start();
}
}
}
//加锁的
class MyCacheLock{
private volatile Map<String,Object> map = new HashMap<>();
//读写锁(更加细粒度的控制)
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
//存 写入的时候,只希望同时只有一个线程在写
public void put(String key, Object value){
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"写入"+key);
map.put(key,value);
System.out.println(Thread.currentThread().getName()+"写入成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock();
}
}
//读 取,所有人可以读
public void get(String key){
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"读取"+key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName()+"读取成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
}
}
//自定义缓存
class MyCache{
private volatile Map<String,Object> map = new HashMap<>();
//存 写
public void put(String key, Object value){
System.out.println(Thread.currentThread().getName()+"写入"+key);
map.put(key,value);
System.out.println(Thread.currentThread().getName()+"写入成功");
}
//读 取
public void get(String key){
System.out.println(Thread.currentThread().getName()+"读取"+key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName()+"读取成功");
}
}
10. 阻塞队列
写入:如果队列满了,就必须阻塞等待
读取:如果队列是空的,必须阻塞等待生产
BlockingQueue
什么情况会使用阻塞队列?
多线程(A调用B,必须等B先执行,B没有执行完,A就会挂起或者等待)
线程池(出了弹性大小之外,一般会用一个队列去维护里面的大小)
学会使用队列
添加,移除
四组API
- 抛出异常
public class BqTest {
public static void main(String[] args) {
test1();
}
// 1. 抛出异常
public static void test1(){
//队列大小
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(arrayBlockingQueue.add("a"));
System.out.println(arrayBlockingQueue.add("b"));
System.out.println(arrayBlockingQueue.add("c"));
//java.lang.IllegalStateException: Queue full 抛出异常
//System.out.println(arrayBlockingQueue.add("d"));
System.out.println(arrayBlockingQueue.remove());
System.out.println(arrayBlockingQueue.remove());
System.out.println(arrayBlockingQueue.remove());
//java.util.NoSuchElementException
//System.out.println(arrayBlockingQueue.remove());
}
}
- 不会抛出异常
// 2. 有返回值,不抛出异常
public static void test2(){
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(arrayBlockingQueue.offer("a"));
System.out.println(arrayBlockingQueue.offer("b"));
System.out.println(arrayBlockingQueue.offer("c"));
System.out.println(arrayBlockingQueue.offer("d")); //false 不抛出异常
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll()); //null 不抛出异常
}
- 阻塞等待
// 3. 等待,阻塞(一直阻塞)
public static void test3() throws InterruptedException {
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
arrayBlockingQueue.put("a");
arrayBlockingQueue.put("b");
arrayBlockingQueue.put("c");
//arrayBlockingQueue.put("d"); //队列没有位置,一直阻塞
System.out.println(arrayBlockingQueue.take());
System.out.println(arrayBlockingQueue.take());
System.out.println(arrayBlockingQueue.take());
//System.out.println(arrayBlockingQueue.take()); //没有这个元素,一直阻塞
}
- 超时等待
// 4. 等待,阻塞(等待超时)
public static void test4() throws InterruptedException {
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(arrayBlockingQueue.offer("a"));
System.out.println(arrayBlockingQueue.offer("b"));
System.out.println(arrayBlockingQueue.offer("c"));
System.out.println(arrayBlockingQueue.offer("d", 2, TimeUnit.SECONDS)); //等待超过2秒就退出
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll(2, TimeUnit.SECONDS)); //等待超过2秒就退出
}
- 同步队列
- 没有容量,进去一个元素,必须等待取出来之后,才能再往里面放一个元素。
- 和其他的BlockingQueue 不一样, SynchronousQueue 不存储元素
- put了一个元素,必须从里面先take取出来,否则不能在put进去值!
public class SynchronousQueueDemo {
public static void main(String[] args) {
BlockingQueue<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();
}
},"T1").start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+" take "+synchronousQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+" take "+synchronousQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+" take "+synchronousQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T2").start();
}
}
11. 线程池
- 池化技术
- 程序运行的本质:占用系统资源,为了优化资源的使用,引入了池化技术
- 比如线程池、连接池。内存池、对象池
- 池化技术:事先准备好一些资源,有人要用,就来拿,用完就归还。
线程池的好处:
- 降低资源消耗
- 提高响应速度
- 方便管理线程
线程可以复用,可以控制最大并发数,管理线程
线程池重点核心:三大方法, 7大参数, 4种拒绝策略
11. 1 三大方法
//Executors 工具类 3大方法
public class Demo01 {
public static void main(String[] args) {
//ExecutorService threadPool = Executors.newSingleThreadExecutor();// 单个线程
//ExecutorService threadPool = Executors.newFixedThreadPool(5);// 创建一个固定的线程池的大小
ExecutorService threadPool = Executors.newCachedThreadPool(); // 可伸缩的
try {
for (int i = 0; i < 100; i++) {
// 使用线程池创建线程
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" ok");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 线程池用完,程序结束,线程池要关闭
threadPool.shutdown();
}
}
}
11.2 7大参数
源码分析
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
本质:new 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;
}
11.3 4种拒绝策略
四种拒绝策略:
- new ThreadPoolExecutor.AbortPolicy() //银行满了,但是还有人进来,不处理这个人的,抛出异常
- new ThreadPoolExecutor.CallerRunsPolicy() //哪来的去哪里 (main线程执行)
- new ThreadPoolExecutor.DiscardPolicy() //队列满了,丢掉任务,不会抛出异常
- new ThreadPoolExecutor.DiscardOldestPolicy() //队列满了,尝试去和最早的竞争,不会抛出异常
public class Demo01 {
public static void main(String[] args) {
//自定义线程池
ExecutorService threadPool = new ThreadPoolExecutor(
2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardOldestPolicy() //队列满了,尝试去和最早的竞争,不会抛出异常
);
try {
// 最大承载 8个:Deque + max
// 超过:RejectedExecutionException
for (int i = 0; i < 10; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" ok");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 线程池用完,程序结束,线程池要关闭
threadPool.shutdown();
}
}
}
- 最大线程到底该如何定义
- CPU密集型:几核,就是几,可以保持CPU的效率最高!
你的电脑是几个核心的(用程序获取),你的最大线程数就设置成几,可以保持CPU的效率最高)
- IO密集型:最大线程数 > 判断你程序中十分耗IO的线程
例如:你的程序里面有15个任务很占用IO资源,就用15个线程去执行,所以最大
线程数量大于这个15就好了,一般是大型IO任务数量的2倍
12. 四大函数式接口
新时代的程序员:lambda表达式、链式编程、函数式接口、Stream流式计算
函数式接口:只有一个方法的接口
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
超级多的FunctionalInterface
简化编程模型,在新版本的框架底层大量应用
foreach(消费者类型的函数式接口)
12.1 Function 函数型接口
有一个输入参数,有一个输出
只要是函数式接口,就可以用lambda表达式简化
public class FunctionDemo {
public static void main(String[] args) {
/*Function function = new Function<String,String>() {
//工具类:输出输入的值
@Override
public String apply(String str) {
return str;
};
};*/
Function function = (str)->{return str;};
System.out.println(function.apply("aaa"));
}
}
12.2 Predicate 断定型接口
有一个输入参数,返回值只能是布尔值
public class PredicateDemo {
public static void main(String[] args) {
//判断字符串是否为空
/*Predicate<String> predicate = new Predicate<String>() {
@Override
public boolean test(String str) {
return str.isEmpty();
}
};*/
Predicate<String> predicate = (str)->{return str.isEmpty();};
System.out.println(predicate.test(""));
}
}
12.3 Supplier 供给型接口
public class SuppierDemo {
public static void main(String[] args) {
//打印字符串
/*Supplier<String> supplier = new Supplier<String>() {
@Override
public String get() {
return "aaa";
}
};*/
Supplier<String> supplier = ()->{return "aaa";};
System.out.println(supplier.get());
}
}
12.4 Consumer 消费型接口
public class ConsummerDemo {
public static void main(String[] args) {
//打印字符串
/* Consumer<String> consumer = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};*/
Consumer<String> consumer = (s)->{System.out.println(s);};
consumer.accept("sss");
}
}
13. Stream 流式计算
- 什么是Stream流式计算
- 大数据:存储+计算
- 集合、Mysql本来就是存储数据的,计算应该交给流来操作。
/**
* 题目要求: 用一行代码实现
* 1. Id 必须是偶数
* 2.年龄必须大于23
* 3. 用户名转为大写
* 4. 用户名倒序
* 5. 只能输出一个用户
**/
public class StreamDemo {
public static void main(String[] args) {
User u1 = new User(1, "a", 23);
User u2 = new User(2, "b", 23);
User u3 = new User(3, "c", 23);
User u4 = new User(6, "d", 24);
User u5 = new User(4, "e", 25);
List<User> list = Arrays.asList(u1, u2, u3, u4, u5);
// lambda、链式编程、函数式接口、流式计算
list.stream()
.filter(user -> {return user.getId()%2 == 0;})
.filter(user -> {return user.getAge() > 23;})
.map(user -> {return user.getName().toUpperCase();})
.sorted((user1, user2) -> {return user2.compareTo(user1);})
.limit(1)
.forEach(System.out::println);
}
}
14. ForkJoin
ForkJoin 在JDK1.7,并行执行任务!提高效率~。在大数据量速率会更快!
大数据中:Map Reduce 核心思想->把大任务拆分为小任务!
- ForkJoin 特点: 工作窃取!
这里面维护的是双端队列
实现原理是:双端队列!从上面和下面都可以去拿到任务进行执行!
- 如何使用ForkJoin?
- 通过ForkJoinPool来执行
- 计算任务 execute(ForkJoinTask<?> task)
- 计算类要去继承ForkJoinTask;
public class ForkJoinDemo extends RecursiveTask<Long> {
private long star;
private long end;
/** 临界值 */
private long temp = 1000000L;
public ForkJoinDemo(long star, long end) {
this.star = star;
this.end = end;
}
/**
* 计算方法
* @return
*/
@Override
protected Long compute() {
if ((end - star) < temp) {
Long sum = 0L;
for (Long i = star; i < end; i++) {
sum += i;
}
return sum;
}else {
// 使用ForkJoin 分而治之 计算
//1 . 计算平均值
long middle = (star + end) / 2;
ForkJoinDemo forkJoinDemo1 = new ForkJoinDemo(star, middle);
// 拆分任务,把线程压入线程队列
forkJoinDemo1.fork();
ForkJoinDemo forkJoinDemo2 = new ForkJoinDemo(middle, end);
forkJoinDemo2.fork();
long taskSum = forkJoinDemo1.join() + forkJoinDemo2.join();
return taskSum;
}
}
}
测试
public class ForkJoinTest {
private static final long SUM = 20_0000_0000;
public static void main(String[] args) throws ExecutionException, InterruptedException {
test1();
test2();
test3();
}
/**
* 使用普通方法
*/
public static void test1() {
long star = System.currentTimeMillis();
long sum = 0L;
for (long i = 1; i < SUM ; i++) {
sum += i;
}
long end = System.currentTimeMillis();
System.out.println(sum);
System.out.println("时间:" + (end - star));
System.out.println("----------------------");
}
/**
* 使用ForkJoin 方法
*/
public static void test2() throws ExecutionException, InterruptedException {
long star = System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTask<Long> task = new ForkJoinDemo(0L, SUM);
ForkJoinTask<Long> submit = forkJoinPool.submit(task); //提交任务
Long along = submit.get();
System.out.println(along);
long end = System.currentTimeMillis();
System.out.println("时间:" + (end - star));
System.out.println("-----------");
}
/**
* 使用 Stream并行流计算
*/
public static void test3() {
long star = System.currentTimeMillis();
long sum = LongStream.rangeClosed(0L, SUM).parallel().reduce(0, Long::sum);
System.out.println(sum);
long end = System.currentTimeMillis();
System.out.println("时间:" + (end - star));
System.out.println("-----------");
}
}
15. 异步回调
Future 设计的初衷:对将来的某个事件结果进行建模!
其实就是前端 --> 发送ajax异步请求给后端
但是我们平时都使用CompletableFuture
- 没有返回值的runAsync异步回调
public class FutureDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 发起一个请求
CompletableFuture completableFuture = CompletableFuture.runAsync(()->{
//发起一个异步任务
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"runAsync");
});
System.out.println("111");
completableFuture.get(); // 获取阻塞执行结果
}
}
- 有返回值的runAsync异步回调
//ajax 成功和失败的回调
//返回的是错误信息
public class FutureDemo2 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName()+"supplyAsync");
int i = 10/0;
return 1024;
});
System.out.println(completableFuture.whenComplete((t,u)->{
//success 回调
System.out.println("t=>"+t); //正常的返回结果
System.out.println("u=>"+u); //抛出异常的 错误信息
}).exceptionally((e)->{
//error回调
System.out.println(e.getMessage());
return 233;
}).get());
}
}
16. JMM
-
对Volatile 的理解
Volatile 是 Java 虚拟机提供 轻量级 的同步机制
1、保证可见性
2、不保证原子性
3、禁止指令重排 -
什么是JMM?
JMM:JAVA内存模型,不存在的东西,是一个概念,也是一个约定! -
关于JMM的一些同步的约定:
- 线程解锁前,必须把共享变量立刻刷回主存;
- 线程加锁前,必须读取主存中的最新值到工作内存中;
- 加锁和解锁是同一把锁;
线程中分为 工作内存、主内存
- 8种操作:
- 内存操作有8种,虚拟机实现必须保证每一个操作都是原子性的,不可再分的
- Read(读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用;
- load(载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中;
- Use(使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令;
- assign(赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中;
- store(存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用;
- write(写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中;
- lock(锁定):作用于主内存的变量,把一个变量标识为线程独占状态;
- unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定;
- JMM对这8种操作给了相应的规定:
- 必须成对出现,不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
- 不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
- 不允许一个线程将没有assign的数据从工作内存同步回主内存
- 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是对变量实施use、store操作之前,必须经过assign和load操作
- 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
- 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
- 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
- 对一个变量进行unlock操作之前,必须把此变量同步回主内存
17. volatile
17.1 保证可见性
public class JMMDemo {
private static volatile int num = 0;
public static void main(String[] args) {
new Thread(()->{
while (num == 0){
}
}).start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
num = 1;
System.out.println(num);
}
}
17.2 不保证原子性
原子性:不可分割;
线程A在执行任务的时候,不能被打扰的,也不能被分割的,要么同时成功,要么同时失败。
public class JMMDemo {
// volatile 不保证原子性
private static volatile int num = 0;
public static void add(){
num++; //不是一个原子性操作
}
//num 理论上结果应该是20000
public static void main(String[] args) {
for (int i = 1; i <= 20; i++) {
new Thread(()->{
for (int j = 1; j <= 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount() > 2) { //main gc
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+ " " + num);
}
}
如果不加lock和synchronized ,怎么样保证原子性?
使用原子类 解决原子性问题
public class JMMDemo {
// 原子类的 Integer
private static volatile AtomicInteger num = new AtomicInteger();
public static void add(){
num.getAndIncrement(); //AtomicInteger的+1方法,底层CAS
}
//num 理论上结果应该是20000
public static void main(String[] args) {
for (int i = 1; i <= 20; i++) {
new Thread(()->{
for (int j = 1; j <= 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount() > 2) { //main gc
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+ " " + num);
}
}
原子类为什么这么高级?
这些类的底层都直接和操作系统挂钩!是在内存中修改值。
Unsafe类是一个很特殊的存在;
17.3 禁止指令重排
- 什么是指令重排?
- 我们写的程序,计算机并不是按照我们自己写的那样去执行的
- 源代码–>编译器优化重排–>指令并行也可能会重排–>内存系统也会重排–>执行
- 处理器在进行指令重排的时候,会考虑数据之间的依赖性!
处理器在进行指令重排的时候,会考虑数据之间的依赖性!
int x=1; //1
int y=2; //2
x=x+5; //3
y=x*x; //4
//我们期望的执行顺序是 1_2_3_4 可能执行的顺序会变成2134 1324
//可不可能是 4123? 不可能的
1234567
可能造成的影响结果:前提:a b x y这四个值 默认都是0
线程A | 线程B |
---|---|
x=a | y=b |
b=1 | a=2 |
正常的结果: x = 0; y =0;
线程A | 线程B |
---|---|
b=1 | a=2 |
x=a | y=b |
可能在线程A中会出现,先执行b=1,然后再执行x=a;
在B线程中可能会出现,先执行a=2,然后执行y=b;
那么就有可能结果如下:x=2; y=1.
- volatile可以避免指令重排:
- volatile中会加一道内存的屏障,这个内存屏障可以保证在这个屏障中的指令顺序。
- 内存屏障:CPU指令。作用:
1、保证特定的操作的执行顺序;
2、可以保证某些变量的内存可见性(利用这些特性,就可以保证volatile实现的可见性)
总结
- volatile可以保证可见性;
- 不能保证原子性
- 由于内存屏障,可以保证避免指令重排的现象产生
在哪里用这个内存屏障用得最多呢?单例模式
18. 单例模式
- 饿汉式
// 饿汉式
public class Hungry {
//可能会浪费空间
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 Hungry(){
}
private final static Hungry HUNGRY = new Hungry();
public static Hungry getInstance(){
return HUNGRY;
}
}
- 静态内部类
// 静态内部类
public class Holder {
private Holder(){
}
public static class InnerClass {
private static final Holder HOLDER = new Holder();
}
public static Holder getInstance() {
return InnerClass.HOLDER;
}
}
- 懒汉式
// 懒汉式
public class LazyMan {
private static boolean flag = false;
private LazyMan() {
//System.out.println(Thread.currentThread().getName() + "ok");
synchronized (LazyMan.class) {
if (flag == false) {
flag = true;
}else {
throw new RuntimeException("不要试图通过反射破坏异常");
}
/*if (lazyMan != null) {
throw new RuntimeException("不要试图通过反射破坏异常");
}*/
}
}
private volatile static LazyMan lazyMan;
/*public static LazyMan getInstance(){
if (lazyMan == null) {
lazyMan = new LazyMan();
}
return lazyMan;
} //单线程可以*/
// 解决:双重检测锁模式的 懒汉式单例 DCL懒汉式
public static LazyMan getInstance() {
if (lazyMan == null) {
synchronized (LazyMan.class) {
if (lazyMan == null) {
lazyMan = new LazyMan(); //不是一个原子性操作
/**
* 1. 分配内存空间
* 2. 执行构造方法,初始化对象
* 3. 把这个对象指向这个空间
* 可能发生指令重排 上面加volatile
*/
}
}
}
return lazyMan;
}
//多线程并发 有问题
/*public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
lazyMan.getInstance();
}).start();
}
}*/
// 反射破解
public static void main(String[] args) throws Exception {
//LazyMan instance = LazyMan.getInstance();
Field flag = LazyMan.class.getDeclaredField("flag");
flag.setAccessible(true); //破坏私有权限
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan instance = declaredConstructor.newInstance(); //还是被破解,用标志位加密
flag.set(instance, false);
LazyMan instance1 = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance1);
}
}
单例不安全, 因为反射
- 枚举
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws Exception {
EnumSingle instance1 = EnumSingle.INSTANCE;
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class, int.class);
declaredConstructor.setAccessible(true);
EnumSingle instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
枚举类型的最终反编译源码:
public final class EnumSingle extends Enum
{
public static EnumSingle[] values()
{
return (EnumSingle[])$VALUES.clone();
}
public static EnumSingle valueOf(String name)
{
return (EnumSingle)Enum.valueOf(com/ogj/single/EnumSingle, name);
}
private EnumSingle(String s, int i)
{
super(s, i);
}
public EnumSingle getInstance()
{
return INSTANCE;
}
public static final EnumSingle INSTANCE;
private static final EnumSingle $VALUES[];
static
{
INSTANCE = new EnumSingle("INSTANCE", 0);
$VALUES = (new EnumSingle[] {
INSTANCE
});
}
}
19. 深入理解CAS
public class CASDemo {
// CAS: compareAndSet 比较并交换
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2020);
// 期望, 更新
// 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, 2021));
System.out.println(atomicInteger.get());
}
}
Unsafe 类
-
总结
- CAS:比较当前工作内存中的值 和 主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环,使用的是自旋锁。
- 缺点:
- 循环会耗时;
- 一次性只能保证一个共享变量的原子性;
- 它会存在ABA问题
-
CAS:ABA问题?(狸猫换太子)
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, 6666));
System.out.println(atomicInteger.get());
}
20. 原子引用
解决ABA问题,对应的思想:就是使用了乐观锁~
带版本号的 原子操作!
public class CASDemo {
// 注意:AtomicStampedReference,如果泛型是一个包装类,注意对象的引用问题
static AtomicStampedReference<Integer> atomicInteger = new AtomicStampedReference<>(1, 1);
public static void main(String[] args) {
//AtomicInteger atomicInteger = new AtomicInteger(2020);
new Thread(()->{
int stamp = atomicInteger.getStamp();//获得版本号
System.out.println("a1=>"+stamp);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicInteger.compareAndSet(1, 2, atomicInteger.getStamp(), atomicInteger.getStamp() + 1);
System.out.println("a2=>"+atomicInteger.getStamp());
atomicInteger.compareAndSet(2, 1, atomicInteger.getStamp(), atomicInteger.getStamp() + 1);
System.out.println("a3=>"+atomicInteger.getStamp());
},"a").start();
new Thread(()->{
int stamp = atomicInteger.getStamp();//获得版本号
System.out.println("b1=>"+stamp);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicInteger.compareAndSet(1, 6, stamp, stamp + 1));
System.out.println("b2=>"+atomicInteger.getStamp());
},"b").start();
}
}
21. 各种锁的理解
21.1 公平锁,非公平锁
- 公平锁:非常公平,不能插队,必须先来后到
public ReentrantLock() {
sync = new NonfairSync();
}
- 非公平锁(默认):非常不公平,允许插队,可以改变顺序
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
21.2 可重入锁
- Synchonized 锁
public class Demo01 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sms();
},"A").start();
new Thread(()->{
phone.sms();
},"B").start();
}
}
class Phone{
public synchronized void sms(){
System.out.println(Thread.currentThread().getName()+"=> sms");
call();//这里也有一把锁
}
public synchronized void call(){
System.out.println(Thread.currentThread().getName()+"=> call");
}
}
- Lock 锁
//lock
public class Demo02 {
public static void main(String[] args) {
Phone2 phone = new Phone2();
new Thread(()->{
phone.sms();
},"A").start();
new Thread(()->{
phone.sms();
},"B").start();
}
}
class Phone2{
Lock lock=new ReentrantLock();
public void sms(){
lock.lock(); //细节:这个是两把锁,两个钥匙
//lock锁必须配对,否则就会死锁在里面
try {
System.out.println(Thread.currentThread().getName()+"=> sms");
call();//这里也有一把锁
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void call(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "=> call");
}catch (Exception e){
e.printStackTrace();
}
finally {
lock.unlock();
}
}
}
lock锁必须配对,相当于lock和 unlock 必须数量相同;
在外面加的锁,也可以在里面解锁;在里面加的锁,在外面也可以解锁;
12.3 自旋锁
- spinlock
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
- 自我设计自旋锁
//自旋锁
public class SpinLock {
// int 0
// 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 TestSpinLock {
public static void main(String[] args) throws InterruptedException {
/*ReentrantLock reentrantLock = new ReentrantLock();
reentrantLock.lock();
reentrantLock.unlock();*/
// 底层使用自旋锁CAS
SpinLock spinLock = new SpinLock();
new Thread(()->{
spinLock.myLock();
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
e.printStackTrace();
} finally {
spinLock.myUnlock();
}
},"t1").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
spinLock.myLock();
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
e.printStackTrace();
} finally {
spinLock.myUnlock();
}
},"t2").start();
}
}
t2进程必须等待t1进程Unlock后,才能Unlock,在这之前进行自旋等待。。。。
21.4 死锁
public class DeadLock {
public static void main(String[] args) {
String lockA = "lockA";
String lockB = "lockB";
new Thread(new MyThread(lockA,lockB),"t1").start();
new Thread(new MyThread(lockB,lockA),"t2").start();
}
}
class MyThread implements Runnable{
private String lockA;
private String lockB;
public MyThread(String lockA, String lockB) {
this.lockA = lockA;
this.lockB = lockB;
}
@Override
public void run() {
synchronized (lockA) {
System.out.println(Thread.currentThread().getName()+"lock: "+lockA+"=>get"+lockB);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB) {
System.out.println(Thread.currentThread().getName()+"lock: "+lockB+"=>get"+lockA);
}
}
}
}
死锁排查:
- 使用
jps
定位进程号
jdk的bin目录下: 有一个jps
命令:jps -l
使用jstack
进程进程号,找到死锁信息
一般情况信息在最后: