多线程高级篇1 — JUC — 只弄到处理高并发集合问题

时间:2023-03-09 16:36:04
多线程高级篇1 — JUC — 只弄到处理高并发集合问题

1、线程池

1.1)、什么是线程池?

池( pool ),就是一个容器,所以线程池就是把多个线程对象放到一个容器中

1.2)、如何创建线程池?

先来了解几个常识

  • Executor —— 这是一个接口( 即:线程池的*接口 ),在java.util.Concurrent包下
  • ExecutorService —— Executor的子类,这里面是一些管理线程任务的方法
  • scheduledExecutorService —— ExecutorService的子类,这是用来给线程任务设置任务时间的( 即:延时任务执行时间 )

回到正题:线程池如何创建

这里需要知道一个线程池的工具类 —— Executors( 这里面提供了一些创建线程池的方法 )

这个类的构造方法是private修饰的,所以无法直接创建对象,因此通过 类名. 的方式获取这个类中的方法

多线程高级篇1 — JUC — 只弄到处理高并发集合问题

创建线程池

  • 创建单个线程池 —— Executors.newSingleThreadExecutor()

  • 创建固定大小的线程池 —— Executors.newFixedThreadPool( int ThreadNumber )

  • 创建可变数量的线程池 —— Executors.newCachedThreadPool() ——— 这个线程池的大小会随着放进来的线程任务个数而变化( 同时线程结束之后会自动回收 )—— 但是:在公司开发的时候不推荐使用( 推荐使用第2种 和 第4种方式创建 ——可控 ),原因在API中有,如下:

    多线程高级篇1 — JUC — 只弄到处理高并发集合问题

  • 创建定时任务线程池 —— Excutors.newScheduledThreadPool ( int corePoolSize )

如何提交线程任务到线程池中?

  • 利用submit()方法 —— 即:提交任务

package cn.xieGongZi.threadPool; import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; public class Demo { public static void main(String[] args) { // 创建线程池
ExecutorService threadPool = Executors.newFixedThreadPool(10); // 1、提交Runnable类型的线程任务
threadPool.submit( new testSubmitRunnableThreadTask() ); // new一个Runnable类型的线程任务 传到submit里面就行 // 2、提交Thread类型的线程任务 到 线程池中
threadPool.submit( new testSubmitThreadTask() );
System.out.println(); // 3、提交Callable类型的线程任务 到 线程池中
threadPool.submit( new testSubmitCallaleThreadTask() );
System.out.println(); // 关闭线城池的方法 ———— shutdown()
threadPool.shutdown(); // 这个方法其实是拒绝接收线程任务
// 然后等现有的线程全部执行完之后,关闭所有线程 }
} // 1、提交Runnable类型的线程任务
class testSubmitRunnableThreadTask implements Runnable{ @Override
public void run() {
System.out.println("就为了玩儿Runnable类型的线程任务 到 线程池中");
// 这种线程任务是没有返回值的,而且不可以抛出异常( 需要在这里面就捕获异常处理 )
}
} // 2、提交Thread类型的线程任务
class testSubmitThreadTask extends Thread{ @Override
public void run() { System.out.println("为了玩儿一下提交Thread类型的线程任务 到 线程池中");
// 这种线程任务也是没有返回值的,也不可以抛出异常的( 也是需要在这里面就把异常处理了 ) // 如:下面这种
// try {
// new BufferedReader(new FileReader("d;/test/play.txt"));
// } catch (FileNotFoundException e) {
// e.printStackTrace();
// } }
} // 3、提交Callable类型的线程任务 到 线程池中
class testSubmitCallaleThreadTask implements Callable { @Override
public Object call() throws Exception { // 这里注意 这个线程类型的方法名是 call() 不再是run()了 System.out.println("为了玩儿提交Callable类型的线程任务 到 线程池中");
// 这种线程任务有返回值( 这里是Object,也可以自定义 ),也可以抛出异常( 这里的throws Exception ) return "这是Callable类型线程的返回值";
}
}

对于Callable类型线程的补充 —— 这个类型的线程不是有返回值、可以抛出异常吗。

那么怎么接收Callable这个异步线程的结果?

  • 利用Future —— 将来嘛,所以就是线程结束之后,把结果保存起来

  • 这个Future提供了一个方法 ——— get()方法,这个方法会造成线程阻塞,因为:它会一直等到异步线程把结果返回回来,再继续执行接下来的代码,不然就会一直等着结果,然后后面的代码就运行不了了

  • 分析Future

    • 假如有两个运行着的线程( 让这两个线程在线程池中运行,它们都要做相应的运行任务 ),那么来个问题:把这两个线程提交到线程池之后,能不能拿到线程结果?——— 答案肯定是不能啊,要是能拿到的话,那线程就白学了,所以有了这个Future涩

实例


package cn.xieGongZi.supplyCallable; import java.util.concurrent.*; public class Test { public static void main(String[] args) throws ExecutionException, InterruptedException { // 把Callable类型的线程任务提交到线程池中
ExecutorService threadPool = Executors.newFixedThreadPool(10); Future<Integer> threadResult = threadPool.submit( new CallableTisk() ); // 这个类型的线程可以得到结果哦 // 这样这 threadReasult 就是 这个Callable类型的线程任务做完之后 保存起来的结果
// 这只是把这个Callable类型的线程结果放在这个容器中了,那需要取出来啊————get()方法来了
Integer CallableThreadResult = threadResult.get(); // 这样就取到这个Callable类型的线程任务结果了
// 但是注意:get()方法需要处理异常,因为:万一Callable任务中写错了,所以导致最后没结果呢
// 这里为了方便就选择抛出异常
// 另外:这个方法会造成线程阻塞————即:这后面的代码 需要 等到这个get()方法拿到结果了,才继续执行
System.out.println("Callable类型的线程任务结果为:" + CallableThreadResult ); // 最后记得关闭线程池
threadPool.shutdown();
}
} // 创建一个Callable类型的线程任务
// 让这个线程任务算1 ———— 100的偶数之和嘛
class CallableTisk implements Callable<Integer>{ @Override
public Integer call() throws Exception { int EvenSum = 0; for (int i = 0; i < 100; i += 2) { EvenSum += i;
} return EvenSum;
}
}

因此:玩完了这个callable类型的线程之后,小小总结一下创建线程的方式有哪些?

  • 1、继承Thread
  • 2、实现runnable
  • 3、实现callable

    注:以前的callable不止这么简单,如:thread、runnable、callable的关系是什么?上述的代码解耦怎么解开( 在本篇博客的lock锁时会做解耦演示 )
  • 三者关系如下:
  • 多线程高级篇1 — JUC — 只弄到处理高并发集合问题

2、重点来咯 —— JUC

2.1)、JUC是什么?

没有多么高深,就下面这个图 —— 三个包( 严格来讲:是第一个包,后面两个是这里面的一些技术东西 —— 第二个是原子、第三个是lock锁

多线程高级篇1 — JUC — 只弄到处理高并发集合问题

思考一些问题

  • 1、java到底能不能开启线程?—— 答案是:不能,原因:看下面图中的源码

    在前面玩儿线程的时候,让线程进入就绪状态不是调用了一个方法吗 —— start(),那么就去看一下这个方法的源码

    多线程高级篇1 — JUC — 只弄到处理高并发集合问题

多线程高级篇1 — JUC — 只弄到处理高并发集合问题

  • 2、java默认是有几个线程?

    就两个———— main() 和 GC回收

  • 3、到底什么是并发和并行?

    • 并发 ——— 好比:多个线程去争夺资源

      • 举个例子 —— 假如一个CPU只有一核,然后多个线程去竞争里面的资源,最后谁可以得到这个资源 —— 天下武功,唯快不破嘛,所以并发争夺咯,快的那个线程就拿到资源了
    • 并行 ——— 好比:多个人一起行走

      • 举个例子—— 假如一个CPU是多核的,多个线程就可以同时执行了涩
  • 4、并发编程的实质是什么?

    充分利用CPU的资源嘛 ———— 题外话:要是能够把这个东西研究透了,然后自己研发出东西来更充分利用CPU资源的话,那就牛逼大了啊

wait() 和 sleep()的区别是什么?

  • 1、来自不同的类

    wait()是Object中的、sleep是Thread中的

  • 2、二者的使用范围不一样

    wait只能在同步块 / 同步方法中使用 、 而sleep在什么地方都可以调用

  • 4、wait会释放锁 、 而sleep不会释放锁( 理解就是:sleep是睡觉了啊,所以是抱着锁就睡了嘛,因此不会释放涩 )

    多说一嘴:跟问题无关 —— 这二者都需要添加处理手段

  • 5、线程是存放在哪里的?

    是在一个堆空间里面,每来一个线程,就来排着队,然后依次进入堆空间( 这个堆空间不是JVM中的那个 —— 可以理解为:是用了一个堆结构【 这是一种数据结构 】,来定义了一个空间 ),怎么理解,来个图

    多线程高级篇1 — JUC — 只弄到处理高并发集合问题

前面这些都是废话,学java基础的时候就应该看源码、面向百度编程了解的东西,所以还是来搞主题

3、lock锁

在搞这个之前,来深化使用一下synchronized( 注意哦:思想开始转变咯 ——— 开始解耦咯,不再是写一个类,然后继承 / 实现,然后丢到线程里面去 )

举个例子:

首先知道一句话:线程就是一个资源类,它不应该有任何的附属操作,里面就只有属性 和 方法( 别忘了在面向对象编程中讲的继承这些东西会增加耦合度 )


package cn.xieGongZi.JUC.depthStudySynchronized; // 深入玩一下synchronized
// 首先知道一点:多线程就是资源而已,它里面只有属性和方法
// 继承、实现那些就只会增加耦合度,因此实际开发中能少用就少用 // 顺便来看看:在实际开发中是怎么玩儿多线程的
public class Play { public static void main(String[] args) { saleTicket saleTicket = new saleTicket(); // 线程1
new Thread( ()-> { // lambda表达式————别忘了在集合体系的treeset排序解决办法的这个玩意儿
// 在这里以前不是需要一个子类对象吗
// 如:new Thread( new saleSock() )
// 但是:这个saleSocket我没有实现runnable... for (int i = 1; i < 10; i++) {
saleTicket.sale();
}
},"邪公子" ).start(); // 线程2
new Thread( ()-> { for (int i = 1; i < 10; i++) {
saleTicket.sale();
}
},"紫邪情" ).start(); // 线程3
new Thread( ()-> { for (int i = 1; i < 10; i++) {
saleTicket.sale();
}
},"小紫" ).start(); } } // 定义一个类 ———— 用来卖票的
class saleTicket { // 注意咯:这里不再是搞什么继承Thread 或 实现Runnable了 // 定义一个票的总数 ———— 这就是一个属性而已
private int ticketNumber = 20; // 售票的方法
public synchronized void sale() { // 然后进行卖票
if ( ticketNumber > 0 ){
System.out.println( Thread.currentThread().getName() + "卖了" + ( ticketNumber -- ) + "票,剩余" + ticketNumber );
} }
}

效果图如下:

多线程高级篇1 — JUC — 只弄到处理高并发集合问题

刚刚小扯了一下,现在来开始玩儿Lock锁 —— 先来看一下API的内容( 这样就可以了解一些信息了 )

多线程高级篇1 — JUC — 只弄到处理高并发集合问题

  • 但是知道这些好像没什么卵用啊,因为连用都不会呢( 来,接着看API )

    多线程高级篇1 — JUC — 只弄到处理高并发集合问题

  • 那就照着这个鬼玩意儿来整一下嘛


package cn.xieGongZi.JUC.studyLock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; public class Demo { public static void main(String[] args) { // 测试一下看对不对涩————那把刚刚的测试代码拿过来试一下嘛
saleTicket saleTicket = new saleTicket(); new Thread( ()-> {// for (int i = 1; i < 10; i++) {
// saleTicket.sale();
// } // 这里就执行了这一句代码,这么写看起来烦不烦?———— 当然烦啊( 所以简化嘛 ) for (int i = 1; i < 10; i++) saleTicket.sale();
},"邪公子" ).start(); new Thread( ()-> { for (int i = 1; i < 10; i++) saleTicket.sale(); },"紫邪情" ).start(); // 这样看起来不就舒服多了 new Thread( ()-> { for (int i = 1; i < 10; i++) saleTicket.sale();},"小紫" ).start(); }
} // 定义一个类
class saleTicket { // 定义一个票的总数————属性
private int ticketNumber = 20; // 1、文档中不是需要new lock吗,那就创一个嘛
// 问题又来了:lock是接口啊,new了对象也没屁用啊
// 但是lock不是有三个实现类吗 ———— 那就new实现类啊
// 这里就new ReentrantLock() 可重入锁嘛----因为那些另外的锁后面玩儿 ^ _ ^
Lock lock = new ReentrantLock(); // 售票的方法
public void sale() { // 这里不再用synchronized
// 官网不是讲了new lock之后,然后使用lock()方法吗,那就继续整嘛
lock.lock(); // 2、这就是使用了涩 ———— 这就是上锁的意思 // 文档不是说了try....finally,这里面写业务代码吗 ———— 那又接着来嘛
try { // 业务代码不就是卖票这个东西吗
if (ticketNumber > 0) {
System.out.println(Thread.currentThread().getName() + "卖了" + (ticketNumber--) + "票,剩余" + ticketNumber);
}
}finally { // 这里面不是需要调一个方法unlock()吗,那就来嘛
lock.unlock(); // 3、这就是解锁的意思
}
}
}

效果图如下:

多线程高级篇1 — JUC — 只弄到处理高并发集合问题

诶嘿 ~ 结果对了嘛,那这样是不是得来个总结了

总结:lock怎么玩儿?

  • 1、new 一个lock的子类,得到一把锁lock( new三个子类哪一个都行 —— 不过读写锁还没说呢,学了就可以任意玩儿了 )
  • 2、利用得到的lock去调用lock()方法进行上锁
  • 3、使用try......finally,在try中进行编写业务代码

    利用获取到的lock对象,去调用unLock()方法,进行解锁 —— 为了能够保证这把锁最后一定能够解开,那么就需要放到finally结构中

    但是啊,老衲这个人有点不满足,所以我还想要多一点内容,咋个办?看源码啊

    在例子中不是new了一个ReetrantLock吗,那贫僧再去看一下这个东西有什么鬼玩意儿

    多线程高级篇1 — JUC — 只弄到处理高并发集合问题

什么是公平锁、不公平锁

  • 公平锁:前面不是说了线程是存在一个堆空间中的,需要排队吗 —— 所以公平锁就是先来先得

  • 不公平锁:反过来不就是可以插队吗 ^ _ ^ —— 系统默认的就是这个,为什么?

    假如:一个线程需要1小时执行完,而它后面的那个线程需要5秒执行完,这插队的好处不就来了吗

2、问题又来了:lock锁和synchronized锁的区别是什么?

  • 1、synchronized是一个关键字,lock是一个类( 接口 —— 接口就是类的特殊结构嘛 )
  • 2、synchronized不可以判断锁的状态,而lock可以判断是否获得了锁
  • 3、synchronized会自动释放锁,而lock需要我们手动释放锁 ( 不释放会怎么样? —— 就会产生大名鼎鼎的死锁【 要是开发搞成这样的话,可以考虑赶紧走人了 】 )
  • 4、synchronized会造成线程堵塞,而lock就不会( 因为:这个有一个方法 trylock() —— 尝试获得一把锁 )
    • 举个例子:如果A线程堵塞了,那么使用了synchronize的话,就会造成A线程后面的B线程也会堵塞( 等待嘛 ),这个B线程就会傻傻的等着A执行完,而lock就不会
  • 5、synchronized 是可重入锁、不可以中断、也是不公平锁。而lock也是可重入锁、但是可以判断从而中断、是不公平锁( 可是可以手动控制 —— 刚刚看源码的时候,不是有一个不公平锁吗,它可以传一个boolean类型的参数,这个就可以达到公平锁和不公平锁之间的转换 )
  • 6、synchronized适合锁少量的同步代码,而lock可以锁大量的同步代码

3、又来一个问题:锁是个啥玩意儿?锁的是谁?

在搞清楚这个问题之前,再来玩儿一个模型 —— 生产消费者模型( 这个模型可是大有东西的一个玩意儿 )

以前是用synchronized锁来玩的生产消费者模型( 这他喵的是老版本的 ),所以在这里用lock锁来玩一下

不过在用lock锁玩生产消费者模型之前,先来了解一个知识:线程的虚假唤醒


package cn.xieGongZi.JUC.falseAwaken; public class Play { public static void main(String[] args) { // 搞几个线程来玩儿一下
TestFalseAwaken instance = new TestFalseAwaken(); new Thread( ()->{ for (int i = 1; i < 10; i++) { try {
instance.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} ,"邪公子").start(); new Thread( ()->{ for (int i = 1; i < 10; i++) { try {
instance.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} ,"紫邪情").start();
}
} // 写一个线程 ———— 做的事情就是+1 和 -1
class TestFalseAwaken{ // 搞个初始值
private int number = 0; // 做一件事情 +1
public synchronized void increment() throws InterruptedException { if ( number == 0 ){ this.wait();
} number ++; System.out.println( Thread.currentThread().getName() + "————>" + number ); this.notifyAll();
} // 搞第二件事情 -1
public synchronized void decrement() throws InterruptedException { if ( number != 0 ){ this.wait();
} number -- ; System.out.println( Thread.currentThread().getName() + "————>" + number ); this.notifyAll();
} }

效果图如下:

多线程高级篇1 — JUC — 只弄到处理高并发集合问题

  • 从上面的演示可以得到一个生产者消费者模型的公式:等待、业务代码、唤醒其他线程 ———— 又扯远了,重点不是这个啊

从图中可以看出:这样是没有问题的。但是嘛,要搞事情涩,这咋可能是我要想的结果

用多个生产者( +1 操作 )、 多个消费者 ( -1 操作 )试一下


// 搞几个线程来玩儿一下
TestFalseAwaken instance = new TestFalseAwaken(); new Thread( ()->{ for (int i = 1; i < 10; i++) { try {
instance.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} ,"邪公子").start(); new Thread( ()->{ for (int i = 1; i < 10; i++) { try {
instance.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} ,"紫邪情").start(); new Thread( ()->{ for (int i = 1; i < 10; i++) { try {
instance.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} ,"韩非").start(); new Thread( ()->{ for (int i = 1; i < 10; i++) { try {
instance.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} ,"紫女").start();

效果图如下:

多线程高级篇1 — JUC — 只弄到处理高并发集合问题

  • 这产生了一种:虚假唤醒

什么是虚假唤醒 —— 来看API( java.lang.Object中的wait()方法 )

多线程高级篇1 — JUC — 只弄到处理高并发集合问题

  • 因此:从这里知道了什么是虚假唤醒、同时也知道了怎么解决( 原因嘛,就是我例子中用的是if语句 )

    • 什么意思?if判断嘛,初值是0,第一次+1 线程在进来的时候判断条件成立了( 需要进行+1 ),但是第二个+1线程进来也发现条件成立( 也做+1 ),为什么可以出现,是因为使用了if判断涩,所以两个线程判断都成立了,也就会执行相应的操作,-1的原理也是一样的,所以导致出现这个虚假唤醒了

      怎么解决?

      这个API文档已经说了 ——— 采用while循环嘛,把if改成while,效果如下:

      多线程高级篇1 — JUC — 只弄到处理高并发集合问题

多线程高级篇1 — JUC — 只弄到处理高并发集合问题

  • 发现就正常了,所以这也是一个注意点:在线程中,线程等待( 即:使用wati() 方法 )最好放在while循环语句中,不然很容易产生线程的虚假唤醒

4、JUC中的线程通信

  • 用synchronized玩生产消费者模型,涉及到了线程通信,即:线程的等待( wait() ) 和 线程唤醒涩( notify() 和 notifyAll() ),那么用lock锁需不需要呢?
  • 当然需要了,所以:这里还要弄一个知识点 —— lock锁的线程通信:Condition,不知道怎么玩 —— 看API咯,会教我们怎么玩( 顺便对照一下synchronized锁 )

    多线程高级篇1 — JUC — 只弄到处理高并发集合问题

多线程高级篇1 — JUC — 只弄到处理高并发集合问题

  • 用这两个一对比,发现两个的用法一样 , 只是synchronized锁是老版的,而Lock锁是新版的而已

因此:真正利用Lock锁来玩一下生产消费者模型( 改造前面的 +1 和 -1 )


package cn.xieGongZi.lock.quickStart; import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; public class PlayLock { public static void main(String[] args) { Do aDo = new Do(); new Thread( ()->{
for (int i = 0; i < 5; i++) { try {
aDo.doIncrease();
} catch (Exception e) {
e.printStackTrace();
}
}
} , "紫邪情" ).start(); new Thread( ()->{
for (int i = 0; i < 5; i++) { try {
aDo.doDecrease();
} catch (Exception e) {
e.printStackTrace();
}
}
} , "小紫" ).start(); new Thread( ()->{
for (int i = 0; i < 5; i++) { try {
aDo.doDecrease();
} catch (Exception e) {
e.printStackTrace();
}
}
} , "韩非" ).start(); new Thread( ()->{
for (int i = 0; i < 5; i++) { try {
aDo.doDecrease();
} catch (Exception e) {
e.printStackTrace();
}
}
} , "紫女" ).start(); }
} class Do{ private Integer num = 0; private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition(); // 这个方法做+1操作
public void doIncrease() throws InterruptedException { lock.lock(); try { while ( num == 0 ){
condition.await();
} num ++;
System.out.println( Thread.currentThread().getName() + "————>" + num );
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
} // 这个方法做-1操作
public void doDecrease() throws InterruptedException { lock.lock(); try { while ( num > 0 ){ condition.await();
} num --; System.out.println(Thread.currentThread().getName() + "————>" + num);
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally { lock.unlock();
}
}
}

多线程高级篇1 — JUC — 只弄到处理高并发集合问题

  • 这样是成功了,但是:condition这个东西要是只是为了把旧技术替代的话,那就没什么意思了( 新技术出来不止还拥有原技术的特点,还有自己独特的魅力 )

    • 现在先思考一个问题:前面玩的这些所谓的多线程,它们在执行的时候,顺序我们可控吗?也就是说:我想要一个线程执行完了之后,接着执行的是我指定的某个线程,前面的知识可以做到吗?

5、Condition实现精准唤醒线程


package cn.xieGongZi.lock.b_condition_implPreciseAwaken; import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; // 深度玩condition ———— 精准唤醒
public class DepthPlayCondition { public static void main(String[] args) { // 有这么一个需求:我想要:紫邪情线程执行完了之后,小紫线程开始执行,小紫线程执行完了之后,韩非线程执行
// 韩非线程完了之后,紫邪情线程又继续执行 Do aDo = new Do(); new Thread( ()->{
for (int i = 0; i < 3; i++) {
aDo.oneThread();
}
} , "紫邪情").start(); new Thread( ()->{
for (int i = 0; i < 3; i++) {
aDo.twoThread();
}
} , "小紫").start(); new Thread( ()->{
for (int i = 0; i < 3; i++) {
aDo.threeThread();
}
} , "韩非").start();
} } class Do{ private Lock lock = new ReentrantLock(); // 创建3个监听者,监听不同的对象
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition(); private Integer num = 1; public void oneThread(){ lock.lock(); try { while ( num != 1 ){
condition1.await();
} System.out.println( "当前执行者为:" + Thread.currentThread().getName() ); num = 2; condition2.signal(); // 就是这里做了文章:这里只去唤醒某一个指定的线程 } catch (Exception e) {
e.printStackTrace();
} finally { lock.unlock();
}
} public void twoThread(){ lock.lock(); try { while ( num != 2 ){
condition2.await();
} System.out.println( "当前执行者为:" + Thread.currentThread().getName() );
num = 3;
condition3.signal(); } catch (Exception e) {
e.printStackTrace();
} finally { lock.unlock();
}
} public void threeThread(){ lock.lock(); try { while ( num != 3 ){
condition3.await();
} System.out.println( "当前执行者为:" + Thread.currentThread().getName() );
num = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}

多线程高级篇1 — JUC — 只弄到处理高并发集合问题

   

6、回到前面留的问题:锁到底锁的是谁?

  • 第一组问题:
package cn.xieGongZi.lock.c_depthUnderstandLock;

import java.util.concurrent.TimeUnit;

// 深刻理解锁
public class UnderstandLock { /*
1、一个对象,两个同步方法,是先输出足疗,还是先输出嫖娼?
2、一个对象,一个等待3秒的同步方法,一个标准的同步方法,是先输出足疗,还是先输出嫖娼?
*/ public static void main(String[] args) throws InterruptedException { Person person = new Person(); new Thread( ()->{ person.do1(); } , "紫邪情").start(); // 等待1秒
TimeUnit.SECONDS.sleep(1); // 休息1秒 这个TimeUnit就是JUC中的等待,这是一个枚举类 Thread中等待是Thread.sleep() new Thread( ()->{ person.do2(); } , "小紫").start();
}
} class Person{ public synchronized void do1() {
System.out.println("足疗");
} public synchronized void do2() {
System.out.println("嫖娼");
}
}

问题1的结果:无论执行多少次结果都是下面的

多线程高级篇1 — JUC — 只弄到处理高并发集合问题

问题2的结果:无论执行多少次结果也是如下如下的结果

多线程高级篇1 — JUC — 只弄到处理高并发集合问题

那么问题来了:为什么会这样?

  • synchronized锁的是什么?锁的是调用当前类的方法 的那个对象嘛( 实例都在前面已经演示过了 ),因此:得出第一个结论,锁要锁住的是什么:对象
  • 而上述的那一组问题结果为什么都是:足疗和嫖娼,就是因为都是用的一个对象person,而那两个方法都是synchronized锁住的,所以调用这两个方法的对象都是同一个person,因此:执行顺序就是足疗和嫖娼
  • 既然得出的只是一个结果,那说明还不能高兴得太早_

第二组问题:

package cn.xieGongZi.lock.c_depthUnderstandLock.b;

import java.util.concurrent.TimeUnit;

public class Two {

    /*
* 3、一个同步方法,一个静态同步方法,执行的结果是怎么样的?
* 4、两个静态同步方法,两个对象,执行的结果是怎么样的?
* */
public static void main(String[] args) {
Person person = new Person(); new Thread( ()->{ person.do1(); } , "紫邪情").start(); new Thread( ()->{ person.do2(); } , "小紫").start();
}
} class Person{ public static synchronized void do1() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("足疗");
} public synchronized void do2() {
System.out.println("嫖娼");
}
}

问题3的结果:无论执行多少次,结果都是如下

多线程高级篇1 — JUC — 只弄到处理高并发集合问题

问题4的结果:无论执行多少次,结果还是如下

多线程高级篇1 — JUC — 只弄到处理高并发集合问题

多线程高级篇1 — JUC — 只弄到处理高并发集合问题

结果为什么会这样?

  • 这个知识点要懂反射之后才可以看懂,我的反射相关知识内容在这里:我的反射知识链接
  • 刚刚得出的结论:锁不是锁住的是对象吗,虽然这里是new了两个对象,但是:那两个方法是用了static修饰了的,这个关键字的特性是什么?类加载的时候就加载出类中对应的东西了,而且是只有一份,因此:虽然new的两个Person对象,但是这两个对象已经发生了质的变化,因为:Person现在已经是一个静态类了,所以这个Person类的对象只有一个,即:类对象,这最后的结果也就出来了,虽然new了两个Person对象,可是这两个对象都是同一个类对象,故:锁现在锁住的类对象,即:Class

总结:锁到底锁的是谁?

1、锁的是对象 即:new出来的那个

2、锁的是类对象 即:Class对象

后续的内容:看我博客的人建议把自己的基础回顾一下,玩明白

  • 初学者:直接跳过,把我博客的javaSE部分中级和高级篇全部看明白 / 自己看视频 / 自己跟着别人学

  • 总之:要有javaSE的知识,涉及点说多不多,说少不少,不然看的话会把自己整懵的

  • 有项目经验的看起来更容易明白

  • 当然:说难其实也不难,老衲牛批吹得有点大

7、集合为啥子不安全的问题

  • 前面玩过一个跟集合相关的安全问题:Collections这个工具类,因此:来玩一下

7.1)、首先就是list家族

  • 一号头牌:ArrayList集合

package cn.xieGongZi.unsafeCollection; import java.util.ArrayList;
import java.util.List; public class ListGroup { public static void main(String[] args) { List<Double> list = new ArrayList<>();
// 这里正常的list..add()绝逼没有问题 ———— 放到线程中呢?即:高并发
// 多个线程同时往list这个资源地中放东西 for (int i = 1; i <= 20; i++) {
new Thread( ()->{ list.add( Math.random()*10 ); System.out.println(list);
} , String.valueOf(i) ).start();
} }
}

多线程高级篇1 — JUC — 只弄到处理高并发集合问题

面试装逼的点来了:面试官问你开发遇到的异常有哪些? —— 小孩才会说:什么空指针、数组越界、数字化、输入不匹配这些

老衲装逼就要装得高大上一点:Stack Memory Overflow Excepion /ConcurrentModificationException.....

ConcurrentModificationException 高并发修改异常

这个点是弄出来了:即 list中的ArrayList集合的不安全性,那怎么解决呢?老衲这里有三本秘籍

  • 1、前面才说到的一种嘛:Collections工具类,专他喵的把不安全的集合转成安全的集合涩

    多线程高级篇1 — JUC — 只弄到处理高并发集合问题

这个为什么可以啊,前面已经见过它的源码了,用synchronized修饰了,变线程安全了

  • 2、Vector集合不是安全的吗,拿它做文章

    多线程高级篇1 — JUC — 只弄到处理高并发集合问题

  • 3、利用JUC来做操作

    多线程高级篇1 — JUC — 只弄到处理高并发集合问题

  • CopyOnWriteArrayList 叫做:写入时复制 即:COW思想

    • 这是一种计算机程序设计的优化策略,指的是:在进行写入数据之前把原来的数据拷贝一份。
    • 多个线程之间,在进行数据的读取操作时,这没什么问题;但是:在进行数据写入时会产生一种情况:一个线程修改了另一个线程已经修改的数据,即:值覆盖了,怎么解决这种问题呢?就搞出了这个COW思想:写入时复制,在写入之前先把原来的数据拷贝一份,这样最后就可以比对了嘛
  • 那这个CopyOnWriteArrayList 比 Vector厉害在哪里诶?

    • 都知道Vector是ArrayList 的早期版本( 即:线程安全的 ),那CopyOnWriteArrayList 肯定也是比Vector晚咯,官方想不到有了这个Vector吗,那还搞出个相对Vector来说的新技术CopyOnWriteArrayList 干嘛?

多线程高级篇1 — JUC — 只弄到处理高并发集合问题

它就牛掰在这里,lock锁相比synchronized锁的优点是什么?

  • 两个都是可重入锁,不公平锁,但是不同点就是:synchronized锁不可以中断,而lock锁可以判断从而中断,这就是牛批之处。

7.2)、二号头牌Set集合 —— 和list差不多,只是没有Vector的处理方式,set当然也有synchronizedSet 和 CopyOnWriteArraySet咯

7.3)、然后就是map家族

多线程高级篇1 — JUC — 只弄到处理高并发集合问题