1、线程池

1.1)、什么是线程池?

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

1.2)、如何创建线程池?

先来了解几个常识

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

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

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

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

创建线程池

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

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

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

  • 创建定时任务线程池 —— 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锁时会做解耦演示 )
  • 三者关系如下:

2、重点来咯 —— JUC

2.1)、JUC是什么?

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

思考一些问题

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

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

  • 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中的那个 —— 可以理解为:是用了一个堆结构【 这是一种数据结构 】,来定义了一个空间 ),怎么理解,来个图

前面这些都是废话,学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 );
} }
}

效果图如下:

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

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

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


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、这就是解锁的意思
}
}
}

效果图如下:

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

总结:lock怎么玩儿?

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

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

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

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

什么是公平锁、不公平锁

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

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

    假如:一个线程需要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 操作 )、 多个消费者 ( -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();

效果图如下:

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

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

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

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

      怎么解决?

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

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

4、JUC中的线程通信

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

  • 用这两个一对比,发现两个的用法一样 , 只是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();
}
}
}

  • 这样是成功了,但是: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();
}
}
}

   

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的结果:无论执行多少次结果都是下面的

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

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

  • 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的结果:无论执行多少次,结果都是如下

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

结果为什么会这样?

  • 这个知识点要懂反射之后才可以看懂,我的反射相关知识内容在这里:我的反射知识链接
  • 刚刚得出的结论:锁不是锁住的是对象吗,虽然这里是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();
} }
}

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

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

ConcurrentModificationException 高并发修改异常

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

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

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

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

  • 3、利用JUC来做操作

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

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

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

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

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

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

7.3)、然后就是map家族

多线程高级篇1 — JUC — 只弄到处理高并发集合问题的更多相关文章

  1. java之高并发与多线程

    进程和线程的区别和联系 从资源占用,切换效率,通信方式等方面解答 线程具有许多传统进程所具有的特征,故又称为轻型进程(Light—Weight Process)或进程元:而把传统的进程称为重型进程(H ...

  2. javaSE高级篇1 — 异常与多线程基础

    1.异常的体系结构  注:Throwable是一个类,不是一个接口,这个类里面是描述的一些Error和Exception的共性,如图所示: 异常 / 错误是什么意思? 定义:指的是程序运行过程中,可能 ...

  3. Python3学习(3)-高级篇

    Python3学习(1)-基础篇 Python3学习(2)-中级篇 Python3学习(3)-高级篇 文件读写 源文件test.txt line1 line2 line3 读取文件内容 f = ope ...

  4. Kotlin——从无到有系列之高级篇(一):Lambda表达式

    如果您对Kotlin很有兴趣,或者很想学好这门语言,可以关注我的掘金,或者进入我的QQ群大家一起学习.进步. 欢迎各位大佬进群共同研究.探索 QQ群号:497071402 进入正题 经过前面一系列对K ...

  5. java高并发系列 - 第23天:JUC中原子类,一篇就够了

    这是java高并发系列第23篇文章,环境:jdk1.8. 本文主要内容 JUC中的原子类介绍 介绍基本类型原子类 介绍数组类型原子类 介绍引用类型原子类 介绍对象属性修改相关原子类 预备知识 JUC中 ...

  6. PHP代码审计基础-高级篇

    高级篇主要讲 1. 熟知各个开源框架历史版本漏洞. 2. 业务逻辑漏洞 3. 多线程引发的漏洞 4. 事务锁引发的漏洞 在高级篇审计中有很多漏洞正常情况下是不存在的只有在特殊情况下才有 PHP常用框架 ...

  7. 面试题_Spring高级篇

    Spring高级篇 1.什么是 Spring 框架? Spring 框架有哪些主要模块?  Spring 框架是一个为 Java 应用程序的开发提供了综合.广泛的基础性支持的 Java 平台. Spr ...

  8. 互联网大厂高频重点面试题 (第2季)JUC多线程及高并发

    本期内容包括 JUC多线程并发.JVM和GC等目前大厂笔试中会考.面试中会问.工作中会用的高频难点知识.斩offer.拿高薪.跳槽神器,对标阿里P6的<尚硅谷_互联网大厂高频重点面试题(第2季) ...

  9. 3 - 基于ELK的ElasticSearch 7.8.x技术整理 - 高级篇( 偏理论 )

    4.ES高级篇 4.1.集群部署 集群的意思:就是将多个节点归为一体罢了( 这个整体就有一个指定的名字了 ) 4.1.1.window中部署集群 - 了解即可 把下载好的window版的ES中的dat ...

随机推荐

  1. 构建乘积数组 牛客网 剑指Offer

    构建成绩数组 牛客网 剑指Offer 题目描述 给定一个数组A[0,1,...,n-1],请构建一个数组B[0,1,...,n-1],其中B中的元素B[i]=A[0]A[1]...*A[i-1]A[i ...

  2. 使用google zxing生成二维码图片

    生成二维码工具类: 1 import java.awt.geom.AffineTransform; 2 import java.awt.image.AffineTransformOp; 3 impor ...

  3. pip 安装使用 ImportError: No module named setuptools 解决方法

    安装过程详见这篇博客: http://www.ttlsa.com/python/how-to-install-and-use-pip-ttlsa/ 安装后运行到:python setup.py ins ...

  4. redis学习笔记(详细)——高级篇

    redis学习笔记(详细)--初级篇 redis学习笔记(详细)--高级篇 redis配置文件介绍 linux环境下配置大于编程 redis 的配置文件位于 Redis 安装目录下,文件名为 redi ...

  5. TLFS 内存分配算法详解

    文章目录 1. DSA 背景介绍 1.1 mmheap 1.2 mmblk 2. TLFS 原理 2.1 存储结构 2.2 内存池初始化 2.3 free 2.4 malloc 参考资料 1. DSA ...

  6. 设计模式学习-使用go实现适配器模式

    适配器模式 定义 代码实现 优点 缺点 适用范围 代理.桥接.装饰器.适配器4种设计模式的区别 参考 适配器模式 定义 适配器模式的英文翻译是Adapter Design Pattern.顾名思义,这 ...

  7. pytest-allure测试报告

    该类型的警告大多属于版本更新时,所使用的方法过时的原因,可以在该方法的说明出查找替换的方法 1.安装allure a)下载allure.zip https://github.com/allure-fr ...

  8. 【linux系统】jmeter安装

    安装步骤: 1.下载jmeter安装包  wget https://dlcdn.apache.org//jmeter/binaries/apache-jmeter-5.4.1.tgz 如报错以下,需使 ...

  9. ubuntu更換清華軟件源

    打开软件源的编辑sudo gedit /etc/apt/sources.list 软件源: Ubuntu--更改国内镜像源(阿里.网易.清华.中科大) 打開軟件源文件進行修改: 使用 sudo vim ...

  10. [luogu4331]数字序列

    令$a'_{i}=a_{i}+n-i$.$b'_{i}=b_{i}+n-i$,代价仍然是$\sum_{i=1}^{n}|a'_{i}-b'_{i}|$,但条件变为了$b'_{i}\le b'_{i+1 ...