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


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 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{


    @Override
    public Integer call() throws Exception {

        int EvenSum = 0;

        for (int i = 0; i < 100; i += 2) {

            EvenSum += i;
        }

        return EvenSum;
    }
}

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 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家族

相关