1 Star 1 Fork 0

程序员Eric / java-concurrency-in-practice

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README

课程包括:核心基础、内存模型、死锁。

第1章 课程内容

  • 线程8大核心基础
  • Java内存模型
  • 死锁的前世今生

第2章 线程的八大核心基础知识

本章概览图

image-20210724155145204

1·、实现多线程的方法到底有1种还是2种还是4种?

2、怎样才是正确的线程启动方式?

3、上山容易下山难---如何正确停止线程?(难点)

4、线程的一生---6个状态(生命周期)

5、Thread类和Object类中的重要方法详解

6、线程的各个属性

7、未捕获异常如何处理?

8、双刃剑:多线程会导致的问题

第3章

实现多线程的官方正确方法:2种

方法1:实现Runnable接口

方法2:继承Thread类

两种方法的对比:

方法1(实现Runnable接口)更好

两种方法的本质对比:

  • 方法1:最终调用target.run()方法
  • 方法2:run()整个方法都被重写

同时使用两种方法会怎么样?

应该会执行子类的方法。(从面向对象的角度去考虑这个问题)

package threadcoreknowledge.createThreads;

public class BothRunnableThread {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            public void run() {
                System.out.println("我来自Runnable");
            }
        }) {
            @Override
            public void run() {
                System.out.println("我来自Thread类");
            }
        }.start();
    }
}

执行结果:

我来自Thread类

总结

最精准的描述。

  • 通常我们可以分为两类,Oracle也是这么说的。
  • 准确的讲,创建线程只有一种方式,那就是构造Thread类,而实现线程的执行单元有两种方式。
    • 实现Runnable接口的run方法,并把Runnable接口的实例传给Thread类。
    • 方法二:重写Thread类的run方法(继承Thread类)

典型错误观点分析

错误观点1:“线程池创建线程也算是一种新建线程的方式”

错误观点2:“通过Callable和FutureTask创建线程,也算是一种新建线程的方式”

错误观点3:“无返回值是实现Runnable接口,有返回值是实现Callable接口,所以Callable接口是新的实现线程的方式”

错误观点4:使用定时器创建线程

package threadcoreknowledge.createThreads.wrongways;

import java.util.Timer;
import java.util.TimerTask;

/**
 * 定时器创建线程
 */
public class DemoTimmerTask {

    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        }, 1000, 1000);
    }
}

错误观点5:匿名内部类

错误观点6:Lambda表达式

观点5、观点6其实只是利用了新的语法特性而已。

总结

多线程的实现方式,在代码中写法千变万化,但其本质万变不离其宗。

彩蛋环节

彩蛋1:学习编程知识的优质路径

1、看经典书籍(比如:Java并发编程实战、自顶向下计算机网络)

2、看官方文档

3、英文搜google和stackoverflow

4、自己动手写,实践写demo,尝试用到项目里

5、对于不理解的知识点,参考该领域的多个书本,综合判断

6、学习开源项目,分析源码(学习synchronized原理,反编译,看cpp代码)

彩蛋2:如何了解技术领域的最新动态

  • 高质量固定途径:ohmyrss.com(信息源筛选,为我所用)
  • 订阅技术网址的邮件:InfoQ(每周都看)
  • 公众号不推荐作为技术知识来源,质量无法保证。自注:但是强于CSDN.

彩蛋3:如何在业务开发中成长

  • 偏业务方向
  • 偏技术方向
  • 两个25%理论

偏业务方向:比如天猫淘宝电商领域、百度搜索领域、滴滴的出行领域,这时候可以了解业务的核心模型。把复杂的业务进行合理的抽象。

偏技术方向:比如中间件、存储系统、消息中间件、RPC等

常见面试问题

题目1:有多少种实现线程的方法?思路有5点。

  1. 从不同的角度看,会有不同的答案。
  2. 典型答案是两种
  3. 我们看原理,两种方式本质都是一样的
  4. 具体展开说其他方式
  5. 结论

题目2:实现Runnable接口和继承Thread类哪种方式更好?

  1. 从代码架构角度:runnable接口更能解耦
  2. 新建线程的损耗:给Thread类传递Runnable接口实例的方式可以反复使用Thread对象,线程池就是基于此想法。
  3. Java不支持双继承。如果继承了Thread类,就不能继续继承其他类了。

第4章 开启多线程启动的世界

启动线程的正确方式

package threadcoreknowledge.startthread;

public class StartAndRunMethod {

    public static void main(String[] args) {
        Runnable runnable = () -> {
            System.out.println(Thread.currentThread().getName());
        };

        runnable.run();

        new Thread(runnable).start();
    }
}

start()方法含义

启动新线程:请求JVM启动一个线程,至于何时被启动,由JVM决定。

准备工作:新线程处于就绪状态,即已获得除CPU外的其他资源。

start方法的实际过程:

  1. 启动新线程,检查线程状态,threadstatus变量
  2. 加入线程组
  3. 调用start0()方法,start0是一个native方法

run方法原理解读

源码解析

public void run() {
    if (target != null) {
    	target.run();
    }
}

两种情况:

  1. 继承Thread类,重写run()方法,将执行子类的run()方法。

  2. 将runnable实例传递给Thread类,作为Thread类的target属性,然后调用runnable.run()

常见面试题

1、一个线程两次调用start()方法会出现什么情况?为什么 ?

会抛出一个异常,java.lang.IllegalThreadStateException

start()代码实现的开头,会对线程状态做检查。

2、既然start()方法会调用run()方法,为什么我们选择调用start()方法,而不是直接调用run()方法呢?

因为调用start()方法才会真正意义上启动一个线程,经历线程的各个生命周期。如果直接调用run()方法,那它就是一个普通的方法调用而已。

第5章 线程停止、中断之最佳实践

如何正确停止线程?

image-20210724232452803

原理介绍:

使用interrupt来通知,而不是强制。

  1. 通常线程会在什么情况下停止?
  2. 正确的停止方法:interrupt
  3. 正确停止带来的好处

通常线程会在什么情况下停止?

  • run方法执行完毕,线程停止。
  • 出现异常,方法中并没有捕获,线程也会停止。

线程被通知停止时,线程可能处于的状态?

  • 通常线程会在什么情况下停止?普通情况。
  • 线程可能被阻塞
  • 如果线程在每次迭代后都阻塞

通常线程会在什么情况下停止?普通情况(没有阻塞的情况)。

package threadcoreknowledge.stopthread;

/**
 * run 方法内没有调用sleep 或者 wait方法时,停止线程
 */
public class RightWayStopThreadWithoutSleep implements Runnable {
    @Override
    public void run() {
        int num = 0;
        while (!Thread.currentThread().isInterrupted()
                && num <= Integer.MAX_VALUE / 2) {
            if (num % 10000 == 0) {
                System.out.println(num + "是10000的倍数");
            }
            num++;
        }
        System.out.println("任务运行结束了");
    }
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new RightWayStopThreadWithoutSleep());
        t.start();
        Thread.sleep(800);
        t.interrupt();
    }
}

线程可能被阻塞

package threadcoreknowledge.stopthread;

/**
 * 带有sleep的中断线程的写法
 */
public class RightWayStopThreadWithSleep {
    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = () -> {
            int num = 0;
            try {
                while (num <= 300 && !Thread.currentThread().isInterrupted()) {
                    if (num % 100 == 0) {
                        System.out.println(num + "是100的倍数");
                    }
                    num++;
                }
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                System.out.println("我被interrupt了");
                e.printStackTrace();
            }

            for (int i = 0; i < 30; i++) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("interrupte后...还会执行吗" + i);
            }
        };

        Thread t = new Thread(runnable);
        t.start();
        Thread.sleep(500);
        t.interrupt();
    }
}

如果线程在每次迭代后都阻塞

package threadcoreknowledge.stopthread;

/**
 * 如果在执行过程中,每次循环都会调用sleep或者wait方法,那么不需要每次迭代都检查是否已经中断
 */
public class RightWayStopThreadWithSleepEveryLoop {
    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = () -> {
            int num = 0;
            try {
                while (num <= 10000) {
                    if (num % 100 == 0) {
                        System.out.println(num + "是100的倍数");
                    }
                    num++;
                    // 每次循环都会sleep
                    Thread.sleep(10);
                }
            } catch (InterruptedException e) {
                System.out.println("我被interrupt了");
                e.printStackTrace();
            }

            for (int i = 0; i < 30; i++) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("interrupte后...还会执行吗" + i);
            }
        };

        Thread t = new Thread(runnable);
        t.start();
        Thread.sleep(5000);
        t.interrupt();
    }
}

为什么线程感知到被interrupt信号,仍然执行下面的业务逻辑呢?这点现在还不明白。可能原因:异常被catch处理掉了,所以继续执行呢。如果把异常往上层调用去抛出,下面的代码就不会被执行了。

此处为什么可以去掉while (num <= 300 && !Thread.currentThread().isInterrupted())中的!Thread.currentThread().isInterrupted()?也不太明白。

中断不了的情况

package threadcoreknowledge.stopthread;

/**
 * 如果while里面有try/catch,会导致中断失效
 */
public class CantInterrupt {
    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = () -> {
            int num = 0;

            while (num <= 10000) {
                if (num % 100 == 0) {
                    System.out.println(num + "是100的倍数");
                }
                num++;
                try {
                    // 每次循环都会sleep
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    System.out.println("我被interrupt了");
                    e.printStackTrace();
                }
            }


            for (int i = 0; i < 30; i++) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("interrupte后...还会执行吗" + i);
            }

        };

        Thread t = new Thread(runnable);
        t.start();
        Thread.sleep(5000);
        t.interrupt();
    }
}

Thread-0线程在某一次循环中接收到主线程的中断请求,使用catch进行处理,不影响下一次循环,所以不会停止,会继续执行循环体。

即使把while (num <= 10000) {改成while (num <= 10000 && !Thread.currentThread().isInterrupted())也不会停止while (num <= 10000)循环的执行。

原因是sleep会把interrupt标记位清除。

两种最佳实践

  1. 优先选择:传递中断
  2. 不想或者无法传递:恢复中断

不应该屏蔽中断

先看一个错误的处理方式,把异常处理在底层。

package threadcoreknowledge.stopthread;

/**
 * 最佳实践:catch了InterruptedException后优先选择:在方法签名中抛出异常
 * 那么在run()中就会强制try/catch
 */
public class RightWayStopThreadInProduction implements Runnable {
    @Override
    public void run() {
        while (true && !Thread.currentThread().isInterrupted()) {
            System.out.println("go");
            throwInMethod();
        }
    }

    private void throwInMethod() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new RightWayStopThreadInProduction());
        t.start();

        Thread.sleep(500);
        t.interrupt();
    }
}

run 方法会一直执行。需要手动结束此java进程。

run方法无法抛出checked Exception, 只能使用try/catch去处理。

package threadcoreknowledge.stopthread;

/**
 * run方法无法抛出checked Exception, 只能使用try/catch去处理
 */
public class RunThrowException {

    // 普通函数可以往上层抛出异常
    public void aVoid () throws Exception{
        throw new Exception();
    }

    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
//            @Override
//            public void run() throws Exception {
//                // 因此Runnable接口的run方法签名是 public abstract void run();
//                // 没有提及异常
//                throw new Exception();
//            }
            @Override
            public void run() {
                try {
                    throw new Exception();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        };

        new Thread(runnable);

    }
}

最佳实践1:传递中断

向上抛出异常。

package threadcoreknowledge.stopthread;

/**
 * 最佳实践:catch了InterruptedException后优先选择:在方法签名中抛出异常
 * 那么在run()中就会强制try/catch
 */
public class RightWayStopThreadInProduction implements Runnable {
    @Override
    public void run() {
        while (true && !Thread.currentThread().isInterrupted()) {
            System.out.println("go");
            try {
                throwInMethod();
            } catch (InterruptedException e) {
                // 此处可以做一些保存日志 停止程序的操作
                System.out.println("保存日志");
                e.printStackTrace();
                // 跳出while循环
                break;
            }
        }
    }

    private void throwInMethod() throws InterruptedException {
        Thread.sleep(1000);
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new RightWayStopThreadInProduction());
        t.start();

        Thread.sleep(500);
        t.interrupt();
    }
}

最佳实践2:恢复中断

package threadcoreknowledge.stopthread;

/**
 * 最佳实践2:在catch子语句中调用Thread.currentThread().interrupt()来恢复设置中断状态
 * 以便于在后续的执行中,依然能够检查到刚才发生了中断
 */
public class RightWayStopThreadInProduction2 implements Runnable {
    @Override
    public void run() {
        while (true) {
            if(Thread.currentThread().isInterrupted()) {
                System.out.println("Interrupted, 程序运行结束");
                break;
            }
            System.out.println("go");
            reInterrupt();
        }
    }

    private void reInterrupt() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new RightWayStopThreadInProduction2());
        t.start();

        Thread.sleep(500);
        t.interrupt();
    }
}

能够响应中断的方法总结

image-20210726145250645

image-20210726145321724

为什么使用interrupt来停止线程?有什么好处?

被中断的线程拥有如何响应中断的权利,因为有些线程的某些代码是比较重要的,我们必须等待这些线程处理完之后再由他们主动终止,或者他们决定忽略中断请求。不应该鲁莽地使用stop方法停止线程,而是使用interrupt发出一个中断信号,让线程自己去处理,这样使得线程代码更加安全,也能完成清理工作。

彩蛋环节:Java异常体系

image-20210726150239235

停止线程的错误方法

  1. 被弃用的stop、suspend和resume方法
  2. volatile设置boolean标志位

stop方式

package threadcoreknowledge.stopthread;

/**
 * 描述:错误的停止方法:使用stop()来停止线程,可能会导致线程运行一半突然停止,没有办法完成一个基本单位的操作
 * (一个连队),会造成脏数据(有的连队多领取少领取装备)
 */
public class StopThread implements Runnable {
    @Override
    public void run() {
        // 模拟指挥军队:一共有5个连队,每个连队有10人,以连队为单位,发放武器弹药,叫到号的士兵前去领取
        for (int i = 0; i < 5; i++) {
            System.out.println("连队" + i+ "开始领取武器");
            for (int j = 0; j < 10; j++) {
                System.out.println(j);
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("连队" + i+ "领取完毕");
        }
    }

    public static void main(String[] args) {
        Thread t = new Thread(new StopThread());
        t.start();

        try {
            Thread.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        t.stop();
    }
}

resume 、suspend在挂起的时候不会释放锁,可能导致死锁。

stop()方法会释放锁,但是由于它的停止操作比较粗鲁,因此被弃用。

volatile boolean标志位

volatile 方法能正常使用的场景,线程不会一直阻塞才可以正常停止。

package threadcoreknowledge.stopthread.volatiledemo;

/**
 * 演示使用volatile的局限:part1
 * 这种方式看似可行
 */
public class WrongWayVolatile implements Runnable {
    // 可见性粗糙的理解:多个线程都可以看到它的真实值
    // 在JMM那块再深入了解volatile
    private volatile boolean canceled = false;

    @Override
    public void run() {
        int num = 0;
        try {
            while (num <= 100000 && !canceled) {
                if (num % 100 == 0) {
                    System.out.println(num + "是100的倍数");
                }
                num++;
                Thread.sleep(1);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        WrongWayVolatile r = new WrongWayVolatile();
        Thread t = new Thread(r);
        t.start();
        Thread.sleep(5000);
        r.canceled = true;
    }
}

线程不能正常停止的场景

package threadcoreknowledge.stopthread.volatiledemo;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

/**
 * 演示用volatile的局限 part2
 * 陷入阻塞时,volatile是无法停止线程的
 * 此例中,生产者的生产速度很快,消费者消费速度很慢,
 * 所以阻塞队列满了以后,生产者会阻塞,等待消费者进一步消费
 */
public class WrongWayVolatileCantStop {

    public static void main(String[] args) throws InterruptedException {
        ArrayBlockingQueue storage = new ArrayBlockingQueue(10);
        Producer producer = new Producer(storage);

        Thread producerThread = new Thread(producer);
        producerThread.start();

        // 留出时间,让阻塞队列塞满
        Thread.sleep(1000);

        Consumer consumer = new Consumer(storage);
        while (consumer.needMoreNums()){
            System.out.println(consumer.storage.take() + "被消费了");
            Thread.sleep(100);
        }

        System.out.println("消费者不需要更多数据了");
        // 消费者不需要消费了,因此生产者不要继续生产了
        producer.canceled = true;

        System.out.println(producer.canceled);
    }
}

class Producer implements Runnable {
    public volatile boolean canceled = false;

    BlockingQueue storage;

    public Producer (BlockingQueue storage) {
        this.storage = storage;
    }
    @Override
    public void run() {
        int num = 0;
        try {
            while (num <= 100000 && !canceled) {
                if (num % 100 == 0) {
                    // 生产者阻塞到这里了,所以程序一直运行,不结束
                    // 无法走到下一次while判断
                    storage.put(num);
                    System.out.println(num + "是100的倍数,被放到仓库中了");
                }
                num++;

                // 此处不能sleep,否则生产者不能生产足够的数据
                // 生产者不阻塞,volatile boolean类型的变量可以被改变值
                // 然后循环结束
                // Thread.sleep(1);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println("生产者停止运行");
        }
    }
}

class Consumer {
    BlockingQueue storage;

    public Consumer(BlockingQueue storage) {
        this.storage = storage;
    }

    public boolean needMoreNums() {
        if (Math.random() > 0.95) {
            return false;
        }
        return true;
    }
}

对上面的生产者消费者问题的改进,使用interrupt停止线程。

package threadcoreknowledge.stopthread.volatiledemo;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

/**
 * 使用中断来修复刚才的无尽等待问题
 */
public class WrongWayVolatileFixed {
    public static void main(String[] args) throws InterruptedException {

        WrongWayVolatileFixed body = new WrongWayVolatileFixed();
        ArrayBlockingQueue storage = new ArrayBlockingQueue(10);
        Producer producer = body.new Producer(storage);

        Thread producerThread = new Thread(producer);
        producerThread.start();

        // 留出时间,让阻塞队列塞满
        Thread.sleep(1000);

        Consumer consumer = body.new Consumer(storage);
        while (consumer.needMoreNums()) {
            System.out.println(consumer.storage.take() + "被消费了");
            Thread.sleep(100);
        }

        System.out.println("消费者不需要更多数据了");
        // 消费者不需要消费了,因此生产者不要继续生产了

        producerThread.interrupt();

    }


    class Producer implements Runnable {

        BlockingQueue storage;

        public Producer(BlockingQueue storage) {
            this.storage = storage;
        }

        @Override
        public void run() {
            int num = 0;
            try {
                while (num <= 100000 && !Thread.currentThread().isInterrupted()) {
                    if (num % 100 == 0) {
                        // BlockingQueue take() put()方法能够响应中断
                        storage.put(num);
                        System.out.println(num + "是100的倍数,被放到仓库中了");
                    }
                    num++;

                    // 此处不能sleep,否则生产者不能生产足够的数据
                    // 生产者不阻塞,volatile boolean类型的变量可以被改变值
                    // 然后循环结束
                    // Thread.sleep(1);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println("生产者停止运行");
            }
        }
    }

    class Consumer {
        BlockingQueue storage;

        public Consumer(BlockingQueue storage) {
            this.storage = storage;
        }

        public boolean needMoreNums() {
            if (Math.random() > 0.95) {
                return false;
            }
            return true;
        }
    }
}

能够修正的原因在于:即使线程处于阻塞状态,interrupt依旧能够唤醒线程。

重要函数的源码解析

interrupt方法调用interrupt0方法,interrupt0方法是一个native方法,由c++实现。可以在GitHub上找到其实现。

下面两个方法做一下对比:

// java.lang.Thread
public boolean isInterrupted() {
    return isInterrupted(false);
}
private native boolean isInterrupted(boolean ClearInterrupted);
// java.lang.Thread
public static boolean interrupted() {
	return currentThread().isInterrupted(true);
}

区别在于是否清除标志位

java.lang.Thread#interrupted的目标对象是什么?

Thread.interrupted()方法的目标对象是“当前线程”,而不管本方法来自哪个对象。

package threadcoreknowledge.stopthread;

/**
 * 注意:Thread.interrupted()方法的目标对象是“当前线程”,而不管本方法来自哪个对象。
 */
public class RightWayInterrupted {
    public static void main(String[] args) throws InterruptedException {

        Thread threadOne = new Thread(new Runnable() {
            @Override
            public void run() {
                for (; ; ) {

                }
            }
        });

        // 启动线程
        threadOne.start();
        // 设置中断标志
        threadOne.interrupt();
        //
        System.out.println(threadOne.isInterrupted());
        System.out.println(threadOne.interrupted());
        System.out.println(Thread.interrupted());
        System.out.println(threadOne.isInterrupted());

        threadOne.join();
        System.out.println("Main thread is over.");
    }
}

常见面试题

题目1:

image-20210726192711936

对于第1点,可以保证被停止线程的数据安全,让被停止线程自己决定是否停止以及什么时候停止

对于第2点,请求方发出请求信号,调用interrupt()方法;被停止方必须要循环控制条件或者适当的时侯检查停止信号;子方法要传递中断,向上抛出异常,或者在try/catch后恢复中断,这都是最佳实践。

题目2:

image-20210726193413533

不可中断的阻塞有socket io等。

很遗憾,没有通用的解决方案。

举个例子,特例

java.util.concurrent.locks.ReentrantLock#lock 无法响应中断,但是java.util.concurrent.locks.ReentrantLock#lockInterruptibly可以响应中断。

有些I/O操作也可以响应中断,但是我目前还不知道。

第6章 线程的生命周期

线程的一生,6个状态,生命周期

有哪6种状态?

  1. New
  2. Runnable
  3. Blocked
  4. Waiting
  5. Timed Waiting
  6. Terminated

每种状态有什么含义?

new 状态:执行了Thread thread = new Thread();, 但是还没有执行thread.start();

Runnable 状态:调用了thread.start()后。Java的runnable对应操作系统中的ready和running状态。

Blocked 状态:当一个线程进入到synchronized修饰的代码块的时候,并且该锁已经被其他线程拿走了,此时线程是blocked状态。只有synchronized才和Blocked关联,其他的锁不会和Blocked关联。

状态间的转化图示

img

状态代码演示

展示线程的 NEW RUNNABLE TERMINATED 状态

package threadcoreknowledge.sixstates;

/**
 * 展示线程的 NEW RUNNABLE TERMINATED 状态
 * 即使是正在运行,也是Runnable状态,而不是Running.
 */
public class NewRunableTerminated implements Runnable {
    public static void main(String[] args) {
        Thread thread = new Thread(new NewRunableTerminated());
        // 打印出NEW的状态
        System.out.println(thread.getState());
        thread.start();
        System.out.println(thread.getState());
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 打印出Runnable的状态,即使是正在运行的线程,也是Runnable, 而不是Running
        System.out.println(thread.getState());

        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 将会打印出TERMINATED状态
        System.out.println(thread.getState());
    }
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println(i);
        }
    }
}

展示 Blocked, Waiting, Timed Waiting 状态

package threadcoreknowledge.sixstates;

/**
 * 展示 Blocked, Waiting, Timed Waiting
 */
public class BlockedWaitingTimedWaiting implements Runnable {

    public static void main(String[] args) throws InterruptedException {
        BlockedWaitingTimedWaiting runnable = new BlockedWaitingTimedWaiting();
        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable);
        thread1.start();
        thread2.start();

        Thread.sleep(10);
        // 打印出Timed Waiting状态,因为正在执行Thread.sleep(1000);
        System.out.println(thread1.getState());
        // 打印出Blocked状态,因为thread2想拿到syn()的锁却拿不到
        System.out.println(thread2.getState());

        Thread.sleep(1300);
        // 打印出Waiting状态,因为执行了wait()
        System.out.println(thread1.getState());
    }

    @Override
    public void run() {
        syn();
    }

    public synchronized void syn() {
        try {
            Thread.sleep(1000);
            wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

阻塞状态是什么?

image-20210726205759139

常见面试问题

image-20210726210001009

回答状态转换图。

第7章 趣解Thread和Object类中线程相关方法

思考题

image-20210726210619549

本小节概览

image-20210726210713940

image-20210726210741741

相关方法概览

image-20210726210911142

image-20210726211108481

wait, notify, notifyall的作用

阻塞阶段

调用wait()进入阻塞阶段

synchronized里面使用wait(), 然后线程进入阻塞状态。

直到以下4种情况之一发生时,才会被唤醒。

image-20210726212946885

唤醒阶段

调用notify() 、notifyAll()

遇到中断

代码展示1:

package threadcoreknowledge.threadobjectclasscommonmethods;

/**
 * 展示 wait 和 notify 的基本用法
 * 1. 研究代码执行顺序
 * 2. 证明wait释放锁
 */
public class Wait {
    public static Object object = new Object();
    static class Thread1 extends Thread {
        @Override
        public void run() {
            synchronized (object) {
                System.out.println(Thread.currentThread().getName() + "开始执行了");
                try {
                    object.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "获取到了锁,继续执行");
            }
        }
    }

    static class Thread2 extends Thread {
        @Override
        public void run() {
            synchronized (object) {
                object.notify();
                System.out.println(Thread.currentThread().getName() + "调用了notify()");
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread1 thread1 = new Thread1();
        Thread2 thread2 = new Thread2();

        thread1.start();
        Thread.sleep(200);
        thread2.start();
    }
}

可能的执行结果:

Thread-0开始执行了
Thread-1调用了notify()
Thread-0获取到了锁,继续执行

此段代码说明了两点:

  1. 研究代码执行顺序
  2. 证明wait释放锁

代码展示2:

演示 notifyAll() 用法。

package threadcoreknowledge.threadobjectclasscommonmethods;

/**
 * 描述: 3个线程,线程1和线程2首先被阻塞,线程3唤醒它们。有两种选择,notify, notifyAll
 *
 * start先执行,不代表线程先启动
 */
public class WaitNotifyAll implements Runnable {
    private static final Object resourceA = new Object();

    public static void main(String[] args) throws InterruptedException {
        WaitNotifyAll r = new WaitNotifyAll();
        Thread thread1 = new Thread(r);
        Thread thread2 = new Thread(r);

        Thread thread3 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (resourceA) {
                    resourceA.notifyAll();
                    System.out.println("Thread C notified.");
                }
            }
        });

        thread1.start();
        thread2.start();
        Thread.sleep(200);
        thread3.start();
    }

    @Override
    public void run() {
        synchronized (resourceA) {
            System.out.println(Thread.currentThread().getName() + " got resourceA lock.");
            try {
                System.out.println(Thread.currentThread().getName() + " waits to start.");
                resourceA.wait();
                System.out.println(Thread.currentThread().getName() + " is waiting to end.");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

可能的执行结果:

Thread-0 got resourceA lock.
Thread-0 waits to start.
Thread-1 got resourceA lock.
Thread-1 waits to start.
Thread C notified.
Thread-1 is waiting to end.
Thread-0 is waiting to end.

代码展示3:

证明wait()只释放当前对象的那把锁

package threadcoreknowledge.threadobjectclasscommonmethods;

/**
 * 证明wait()只释放当前的那把锁
 */
public class WaitNotifyReleaseOwnMonitor {

    public static volatile Object resourceA = new Object();
    public static volatile Object resourceB = new Object();

    public static void main(String[] args) {
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (resourceA) {
                    System.out.println("ThreadA gets resourceA lock.");
                    synchronized (resourceB) {
                        System.out.println("ThreadA gets resourceB lock.");
                        try {
                            System.out.println("ThreadA releases resourceA lock.");
                            resourceA.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (resourceA) {
                    System.out.println("ThreadB gets resourceA lock.");
                    System.out.println("ThreadB tries to get resourceB lock.");

                    synchronized (resourceB) {
                        System.out.println("ThreadB gets resourceB lock.");
                    }
                }
            }
        });

        thread1.start();
        thread2.start();
    }
}

可能的执行结果:

ThreadA gets resourceA lock.
ThreadA gets resourceB lock.
ThreadA releases resourceA lock.
ThreadB gets resourceA lock.
ThreadB tries to get resourceB lock.

wait, notify, notifyall的特点性质

  • 使用的时候必须先拥有monitor
  • notify只能唤醒其他线程的其中一个,不确定具体唤醒哪一个,这由JVM决定。
  • 三个方法都属于Object类。都是用final native修饰的。
  • 类似功能的Condition类。
  • 同时持有多个锁的情况

wait原理

image-20210726224751247

img

状态转化的特殊情况

image-20210726225545564

wait/notify实现生产者/消费者模式

package threadcoreknowledge.threadobjectclasscommonmethods;

import java.util.Date;
import java.util.LinkedList;

/**
 * 用wait/notify实现生产者消费者模式
 */
public class ProducerConsumerModel {


    public static void main(String[] args) {
        EventStorage storage = new EventStorage();
        Producer producer = new Producer(storage);
        Consumer consumer = new Consumer(storage);
        new Thread(producer).start();
        new Thread(consumer).start();
    }

}

class Producer implements Runnable {
    private EventStorage storage;

    public Producer(EventStorage storage) {
        this.storage = storage;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            storage.put();
        }
    }
}

class Consumer implements Runnable {
    private EventStorage storage;

    public Consumer(EventStorage storage) {
        this.storage = storage;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            storage.take();
        }
    }
}

class EventStorage {
    private int maxSize;
    private LinkedList<Date> storage;

    public EventStorage() {
        this.maxSize = 10;
        this.storage = new LinkedList<>();
    }

    public synchronized void put() {
        while (storage.size() == 10) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        storage.add(new Date());
        System.out.println("仓库里有了" + storage.size() + "个产品");
        // 通知消费者消费
        notify();
    }

    public synchronized void take() {
        // 如果队列里取空了,则等待
        while (storage.size() == 0) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println("拿到了" + storage.poll() + ", 现在仓库还剩下" + storage.size());
        // 通知生产者继续生产
        notify();
    }
}

交替打印0-100奇偶数

synchronized实现

package threadcoreknowledge.threadobjectclasscommonmethods;

/**
 * 使用synchronized控制两个线程交替打印0-100的奇偶数
 */
public class WaitNotifyPrintOddEvenSync {
    private static int count;
    public static final Object lock = new Object();
    private static int maxCount = 100;

    // 新建两个线程
    // 一个只处理偶数,一个只处理奇数
    // 使用synchronized处理

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (count <= maxCount) {
                    synchronized (lock) {
                        if (count <= maxCount && (count & 1) == 0) {
                            System.out.println(Thread.currentThread().getName()
                                    + " : "
                                    + count);
                            count++;
                        }
                    }
                }
            }
        }, "偶数").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (count <= maxCount) {
                    synchronized (lock) {
                        if (count <= maxCount && (count & 1) == 1) {
                            System.out.println(Thread.currentThread().getName()
                                    + " : "
                                    + count);
                            count++;
                        }
                    }
                }
            }
        }, "奇数").start();
    }
}

这种方法存在线程的空转等待,这是一种缺陷。

wait/notify方式

package threadcoreknowledge.threadobjectclasscommonmethods;

/**
 * 使用wait/notify控制两个线程交替打印0-100的奇偶数
 * 1. 拿到锁就打印
 * 2. 打印完就,唤醒其他线程,自己就休眠
 */
public class WaitNotifyPrintOddEvenWait {
    private static int count;
    public static final Object lock = new Object();
    private static int maxCount = 100;

    public static void main(String[] args) throws InterruptedException {
        new Thread(new TurningRunner(), "偶数").start();
        Thread.sleep(10);
        new Thread(new TurningRunner(), "奇数").start();
    }

    static class TurningRunner implements Runnable {
        @Override
        public void run() {
            while (count <= maxCount) {
                synchronized (lock) {
                    System.out.println(Thread.currentThread().getName() + " : " + count);
                    count++;

                    // 唤醒其他线程
                    lock.notify();

                    // 如果还要继续打印,则休眠等待
                    if (count <= maxCount) {
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }
}

面试题

面试题1:

用程序实现两个线程交替打印0-100的奇偶数

上面已经作答。

面试题2:

手写生产者消费者设计模式

面试题3:

为什么wait()需要在同步代码块中使用,而sleep()不需要

可以避免死锁,防止notify执行完了, wait没有与之相对应的notify来唤醒自己。

而sleep是针对自己这个线程的,不与其他线程对话,所以不需要放在同步代码块中。

面试题4:

为什么线程通信的方法wait() notify() notifyAll()被定义在Object类里?而sleep()定义在Thread类里?

在Java中,wait notify notifyAll是锁级别的操作,而锁是属于某一个对象的,每个对象的对象头中都有几位用于保存锁的状态的。所以,锁是绑定在某一个对象中,而不是某一个线程中。

每个线程可以持有多把锁,如果把锁放到Thread类中,就无法实现灵活的控制逻辑了。

面试题5:

wait方法是属于Object对象的,那么调用Thread.wait()会怎么样?

线程退出的时候,会自动执行notify()代码,这会给预想的流程带来干扰,不建议使用Thread实例作为锁对象。

面试题6:

如何选择用notify还是notifyAll ?

需要唤醒一个线程就用notify, 需要唤醒多个线程就用notifyAll

面试题7

notifyAll之后,所有的线程都会再次抢夺锁,如果某线程抢夺失败怎么办?

线程回到ready状态,等待获得锁,然后运行。

面试题8

用suspend() 和 resume() 可以来阻塞线程吗?为什么?

它们都是由于安全问题被弃用的API了。推荐使用wait notify去实现同样的休眠、恢复功能。

sleep()方法

作用:我只想让线程在预期的时间执行,其他时候不要占用CPU资源。

sleep方法不释放锁

sleep方法不释放锁,包括synchronized和Lock锁。这一点和wait()不一样。

代码演示:

package threadcoreknowledge.threadobjectclasscommonmethods;

/**
 * 展示线程sleep的时候不释放synchronized的monitor,等sleep时间到了以后,正常结束后才释放锁。
 */
public class SleepDontReleaseMonitor implements Runnable {
    public static void main(String[] args) {
        SleepDontReleaseMonitor r = new SleepDontReleaseMonitor();
        new Thread(r).start();
        new Thread(r).start();
    }
    @Override
    public void run() {
        sync();
    }
    public synchronized void sync() {
        System.out.println("线程" + Thread.currentThread().getName() + "获得了monitor");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程" + Thread.currentThread().getName() + "释放了monitor");
    }
}

代码演示2:sleep 不释放 Lock (Lock需要手动释放)

package threadcoreknowledge.threadobjectclasscommonmethods;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 演示 sleep 不释放 Lock (Lock需要手动释放)
 */
public class SleepDontReleaseLock implements Runnable {
    private static final Lock lock = new ReentrantLock();

    @Override
    public void run() {
        lock.lock();
        System.out.println("线程" + Thread.currentThread().getName() + "获得了锁");
        try {
            Thread.sleep(3000);
            System.out.println("线程" + Thread.currentThread().getName() + "已经苏醒");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        SleepDontReleaseLock runnable = new SleepDontReleaseLock();
        new Thread(runnable).start();
        new Thread(runnable).start();
    }
}

sleep方法响应中断

  • 抛出InterruptedException
  • 清除中断状态

两种sleep API

  • Thread.sleep()
  • TimeUnit.SECONDS.sleep()
package threadcoreknowledge.threadobjectclasscommonmethods;

import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * 每一秒钟输出当前时间,被中断,然后观察
 * Thread.sleep()
 * TimeUnit.SECONDS.sleep()
 */
public class SleepInterrupted implements Runnable {

    public static void main(String[] args) throws InterruptedException {
        SleepInterrupted runnable = new SleepInterrupted();
        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(6500);
        thread.interrupt();
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(new Date());
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                System.out.println("我被中断了");
                e.printStackTrace();
            }
        }
    }
}

image-20210727172800818

面试题

image-20210727190601278

join

join的作用:

因为新的线程加入了我们,所以我们要等它执行完再出发。

用法:main等待thread1执行完毕,注意谁等谁。等待,主线程等待子线程。

join的普通用法

package threadcoreknowledge.threadobjectclasscommonmethods;

/**
 * 演示join,注意语句的输出顺序,会变化
 */
public class Join {
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "执行完毕");
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "执行完毕");
            }
        });

        thread1.start();
        thread2.start();
        System.out.println("开始等待子线程运行完毕");
        thread1.join();
        thread2.join();
        System.out.println("所有子线程执行完毕");
    }
}

join遇到中断代码演示

package threadcoreknowledge.threadobjectclasscommonmethods;

/**
 * 演示join期间被中断的效果
 */
public class JoinInterrupt {
    public static void main(String[] args) {
        Thread mainThread = Thread.currentThread();
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    mainThread.interrupt();
                    Thread.sleep(5000);
                    System.out.println("Thread1 finished.");
                } catch (InterruptedException e) {
                    System.out.println("Thread1 interrupted exception.");
                }
            }
        });
        thread1.start();
        System.out.println("主线程等待子线程执行完毕");
        try {
            thread1.join();
        } catch (InterruptedException e) {
            System.out.println(Thread.currentThread().getName() + "主线程中断了");
            // e.printStackTrace();
            // 传递中断
            thread1.interrupt();
        }
        System.out.println("子线程已经运行完毕");
    }
}

在join期间,线程到底是什么状态?:Waiting

package threadcoreknowledge.threadobjectclasscommonmethods;

/**
 * 先join再mainThread.getState()
 * <p>
 * 通过debugger看线程join前后状态的对比
 */
public class JoinThreadState {
    public static void main(String[] args) throws InterruptedException {
        Thread mainThread = Thread.currentThread();
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                    System.out.println(mainThread.getState());
                    System.out.println("Thread-0 运行结束");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.start();
        System.out.println("等待子线程运行完毕");
        thread.join();
        System.out.println("子线程运行完毕");
    }
}

join的源码

public final void join() throws InterruptedException {
    join(0);
}

public final synchronized void join(long millis)
    throws InterruptedException {
	
	long base = System.currentTimeMillis();
	long now = 0;

	if (millis < 0) {
		throw new IllegalArgumentException("timeout value is negative");
	}

	if (millis == 0) {
		while (isAlive()) {
			wait(0);
		}
	} else {
		while (isAlive()) {
			long delay = millis - now;
			if (delay <= 0) {
				break;
			}
			wait(delay);
			now = System.currentTimeMillis() - base;
		}
	}
}

join的源码中没有notify的操作,那么子线程结束之后,是怎么通知/唤醒主线程的呢?

image-20210727214839898

线程执行完毕退出的时候,执行了notifyall操作。

等价的Java代码,实现join这个功能。

package threadcoreknowledge.threadobjectclasscommonmethods;

/**
 * 通过讲解join的原理,讲解join的代替写法
 */
public class JoinPrinciple {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "运行结束");
            }
        });
        thread.start();
        System.out.println("等待子线程运行完毕");
//        thread.join();
        synchronized (thread) {
            thread.wait();
        }
        System.out.println("子线程运行完毕");
    }
}

但是

synchronized (thread) {
	thread.wait();
}

换成

Object lock = new Object();
synchronized (lock) {
    lock.wait();
}

并不等价。不使用thread变量作为锁,会导致主线程一直等待。

join面试题:

image-20210727221739631

WAITING状态。

yield

作用:释放我的CPU时间片,释放了时间片,线程仍处于runnable状态。

定位:JVM不保证遵循。

yield和sleep的区别:是否随时可能再次被调度。yield是可以随时可能再次被调度的。

package threadcoreknowledge.threadobjectclasscommonmethods;

/**
 * 演示打印main, Thread-0, Thread-1
 */
public class CurrentThread implements Runnable {
    public static void main(String[] args) {
        new CurrentThread().run();
        new Thread(new CurrentThread()).start();
        new Thread(new CurrentThread()).start();
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

Thread.currentThread()获取当前执行线程的引用。

线程的各个属性

什么时候我们需要设置守护线程?

通常情况下不需要设置。JVM提供的守护线程足够我们使用。

我们应该如何应用线程优先级来帮助程序运行?有哪些禁忌?

因为不同操作系统对于优先级的映射和调度都不一样,所以不应该使用优先级来帮助程序运行。

不同操作系统如何处理优先级问题?

不同操作系统对于优先级的映射和调度都不一样

线程各属性

image-20210728110359203

package threadcoreknowledge.threadobjectclasscommonmethods;

/**
 * ID是从1开始的,JVM运行起来后,我们自己创建的线程的ID早已不是2
 */
public class Id {
    public static void main(String[] args) {
        Thread thread = new Thread();
        System.out.println("主线程的ID: " + Thread.currentThread().getId());
        System.out.println("子线程的ID: " + thread.getId());
    }
}

可能的执行结果:

主线程的ID: 1
子线程的ID: 11

image-20210728111605587

JVM默认创建、启动了一些守护线程,完成特定的功能。

守护线程

作用:给用户线程提供服务。

3个特性:

  1. 线程类型默认继承自父线程。用户线程创建出来的线程默认是用户线程,守护线程创建出来的线程默认是守护线程。
  2. 被谁启动。通常守护线程由JVM启动。main线程除外,main线程是非守护线程。
  3. 守护线程不影响JVM退出。

守护线程和普通线程的区别:

整体上无太大差别,都是执行代码的。用户线程执行我们的业务逻辑,守护线程服务于用户线程。

唯一区别在于JVM的离开。如果线程是一个用户线程,那么它会影响到JVM的退出,而守护线程并不会影响。

线程优先级:

10个级别,默认为5.

程序设计不应该依赖于优先级

  • 不同操作系统不一样。在windows系统中只有7个优先级。
  • 优先级会被操作系统改变。

image-20210728120623884

多线程异常处理

自定义UncaughtExceptionHandler

package threadcoreknowledge.uncaughtexception;

import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * 自己编写的 UncaughtExceptionHandler
 */
public class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
    private String name;

    public MyUncaughtExceptionHandler(String name) {
        this.name = name;
    }

    @Override
    public void uncaughtException(Thread t, Throwable e) {
        Logger logger = Logger.getAnonymousLogger();
        logger.log(Level.WARNING, "线程异常,终止啦" + t.getName(), e);
        System.out.println(name + "捕获了异常" + t.getName() + "异常" + e);
    }
}

使用UncaughtExceptionHandler

package threadcoreknowledge.uncaughtexception;

/**
 * 使用自己定义的 UncaughtExceptionHandler
 */
public class UseOwnUncaughtExceptionHandler implements Runnable {
    public static void main(String[] args) {
        Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler("自定义捕获器"));
        new Thread(new UseOwnUncaughtExceptionHandler(), "Thread-1").start();
        new Thread(new UseOwnUncaughtExceptionHandler(), "Thread-2").start();
        new Thread(new UseOwnUncaughtExceptionHandler(), "Thread-3").start();
        new Thread(new UseOwnUncaughtExceptionHandler(), "Thread-4").start();
    }
    @Override
    public void run() {
        throw new RuntimeException();
    }
}

双刃剑:多线程会导致的问题

考考你:

一共有哪几类线程安全问题?3种。

a++

哪些场景需要额外注意线程安全问题?

什么是多线程带来的上下文切换?

线程安全

什么是线程安全?

《Java Concurrency In Practice》的作者Brian Goetz对“线程安全”有一个比较恰当的定义:“当多个线程访问同一个对象时,如果不需要考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那么这个对象是线程安全的。”

白话翻译:

不管业务中遇到怎样多个线程访问某对象或者某方法的情况,而在编写这个业务逻辑的时候,都不需要做任何额外的处理(也就是可以像单线程编程一样),程序也可以正常运行(不会因为多线程而出错),就可以称为线程安全。

什么情况下会出现线程安全问题?

  • 运行结果错误:a++多线程下出现消失的请求现象
  • 活跃性问题:死锁、活锁、饥饿
  • 对象发布初始化的时候的安全问题。

运行结果错误演示示例:

package background;

/**
 * 第一种:运行结果出错
 * 演示计数不准确(减少),找出具体出错的位置
 */
public class MultiThreadsError implements Runnable {
    int index = 0;
    static MultiThreadsError instance = new MultiThreadsError();

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(instance);
        Thread thread2 = new Thread(instance);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(instance.index);
    }

    @Override
    public void run() {
//        while (index < 10000) {
//            index++;
//        }
        for (int i = 0; i < 10000; i++) {
            index++;
        }
    }
}

原因是index++不是原子操作。

image-20210728201639344

a++具体消失在哪里?请看代码。来自悟空老师。我目前还看不懂。

package background;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 第一种:运行结果出错
 * 演示计数不准确(减少),找出具体出错的位置
 */
public class MultiThreadsError implements Runnable {
    int index = 0;
    static MultiThreadsError instance = new MultiThreadsError();
    final boolean[] marked = new boolean[1000000];

    static AtomicInteger realIndex = new AtomicInteger();
    static AtomicInteger wrongIndex = new AtomicInteger();

    static volatile CyclicBarrier cyclicBarrier1 = new CyclicBarrier(2);
    static volatile CyclicBarrier cyclicBarrier2 = new CyclicBarrier(2);

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(instance);
        Thread thread2 = new Thread(instance);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println("表面上结果是:" + instance.index);
        System.out.println("真正运行的次数:" + realIndex.get());
        System.out.println("错误次数:" + wrongIndex.get());
    }

    @Override
    public void run() {
//        while (index < 10000) {
//            index++;
//        }
        marked[0] = true;
        for (int i = 0; i < 10000; i++) {
            try {
                cyclicBarrier2.reset();
                cyclicBarrier1.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
            index++;
            try {
                cyclicBarrier1.reset();
                cyclicBarrier2.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }

            realIndex.incrementAndGet();
            synchronized (instance) {
                if (marked[index] && marked[index - 1]) {
                    System.out.println("发生错误" + index);
                    wrongIndex.incrementAndGet();
                }
                marked[index] = true;
            }
        }
    }
}

必然出现死锁的例子

package background;

/**
 * 第二种线程安全问题,演示死锁
 */
public class MultiThreadError2 implements Runnable {
    int flag = 1;
    // 锁对象 o1 o2 是两个线程共享的
    static Object o1 = new Object();
    static Object o2 = new Object();
    public static void main(String[] args) {
        MultiThreadError2 r1 = new MultiThreadError2();
        MultiThreadError2 r2 = new MultiThreadError2();
        r1.flag = 1;
        r2.flag = 0;
        new Thread(r1).start();
        new Thread(r2).start();
    }
    @Override
    public void run() {
        System.out.println("flag = " + flag);
        if (flag == 1) {
            synchronized (o1) {
                try {
                    Thread.sleep(500);
                } catch(InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o2) {
                    System.out.println("1");
                }
            }
        }

        if (flag == 0) {
            synchronized (o2) {
                try {
                    Thread.sleep(500);
                } catch(InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o1) {
                    System.out.println("0");
                }
            }
        }
    }
}

对象发布和初始化

image-20210728212346522

image-20210729000407490

对象逸出情况1:方法返回一个private对象

package background;

import java.util.HashMap;
import java.util.Map;

public class MultiThreadsError3 {
    private Map<String, String> states;

    public MultiThreadsError3() {
        states = new HashMap<>();
        states.put("1", "周一");
        states.put("2", "周二");
        states.put("3", "周三");
        states.put("4", "周四");
    }

    public Map<String, String> getStates() {
        return states;
    }

    public static void main(String[] args) {
        MultiThreadsError3 multiThreadsError3 = new MultiThreadsError3();
        Map<String ,String> map = multiThreadsError3.getStates();
        System.out.println(map.get("1"));
        map.remove("1");
        System.out.println(map.get("1"));
    }
}

对象逸出情况2:构造函数初始化未完毕,就this赋值。

package background;

/**
 * 初始化未完毕,就this赋值
 */
public class MultiThreadsError4 {
    static Point point;

    public static void main(String[] args) throws InterruptedException {
        new PointMaker().start();
        Thread.sleep(10); // 休眠10ms, 打印1,0
        // Thread.sleep(100); // 休眠100ms, 打印1,1
        if (point != null) {
            System.out.println(point);
        }
    }

}

class Point {
    private final int x, y;

    public Point(int x, int y) throws InterruptedException {
        this.x = x;
        MultiThreadsError4.point = this;
        Thread.sleep(100);
        this.y = y;
    }

    @Override
    public String toString() {
        return x + "," + y;
    }
}

class PointMaker extends Thread {
    @Override
    public void run() {
        try {
            new Point(1,1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

对象逸出情况3:

package background;

import java.util.HashMap;
import java.util.Map;

/**
 * 构造函数中运行线程
 */
public class MultiThreadsError6 {
    private Map<String, String> states;

    public MultiThreadsError6() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                states = new HashMap<>();
                states.put("1", "周一");
                states.put("2", "周二");
                states.put("3", "周三");
                states.put("4", "周四");
            }
        }).start();
    }

    public Map<String, String> getStates() {
        return states;
    }

    public static void main(String[] args) throws InterruptedException {
        MultiThreadsError6 multiThreadsError6 = new MultiThreadsError6();
        // Thread.sleep(10);
        System.out.println(multiThreadsError6.getStates().get("1"));
    }
}

如何解决逸出?

返回”副本“。

package background;

import java.util.HashMap;
import java.util.Map;

public class MultiThreadsError3 {
    private Map<String, String> states;

    public MultiThreadsError3() {
        states = new HashMap<>();
        states.put("1", "周一");
        states.put("2", "周二");
        states.put("3", "周三");
        states.put("4", "周四");
    }

    public Map<String, String> getStates() {
        return states;
    }

    public Map<String, String> getStatesImproved() {
        return new HashMap<>(states);
    }

    public static void main(String[] args) {
        MultiThreadsError3 multiThreadsError3 = new MultiThreadsError3();
//        Map<String ,String> map = multiThreadsError3.getStates();
//        System.out.println(map.get("1"));
//        map.remove("1");
//        System.out.println(map.get("1"));

        System.out.println(multiThreadsError3.getStatesImproved().get("1"));
        multiThreadsError3.getStatesImproved().remove("1");
        System.out.println(multiThreadsError3.getStatesImproved().get("1"));
    }
}

执行结果:

周一
周一

用工厂模式修复监听初始化问题:

package background;

/**
 * 观察者模式 对象隐式逸出:注册监听事件
 *
 * 用工厂模式修复初始化问题
 */
public class MultiThreadsError7 {
    int count;
    private EventListener listener;
    private MultiThreadsError7(MySource source) {
        listener = new EventListener() {
            @Override
            public void onEvent(Event e) {
                System.out.println("\n我得到的数字是" + count);
            }
        };
        for (int i = 0; i < 1000; i++) {
            System.out.print(i + " ");
        }
        System.out.println();
        count = 100;
    }

    public static MultiThreadsError7 getInstance(MySource source) {
        MultiThreadsError7 safeListener = new MultiThreadsError7(source);
        // 完成所有准备工作再注册
        source.registerListener(safeListener.listener);
        return safeListener;
    }

    public static void main(String[] args) {
        MySource mySource = new MySource();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                mySource.eventCome(new Event() {});
            }
        }).start();

        MultiThreadsError7 multiThreadsError5 = MultiThreadsError7.getInstance(mySource);
    }
    static class MySource {
        private EventListener listener;
        void registerListener(EventListener listener) {
            this.listener = listener;
        }
        void eventCome(Event e) {
            if (listener != null) {
                listener.onEvent(e);
            } else {
                System.out.println("还未初始化完毕");
            }
        }
    }
    interface EventListener {
        void onEvent(Event e);
    }
    interface Event {
    }
}

各种需要考虑线程安全的情况

image-20210728233720966

多线程会导致的问题

  • 性能问题有哪些体现?什么是性能问题?
  • 为什么多线程会带来性能问题?

性能问题有哪些体现?什么是性能问题?

接口响应慢。

为什么多线程会带来性能问题?

调度:上下文切换

  • 什么是上下文切换?
  • 缓存开销
  • 何时会导致密集的上下文切换

协作:内存同步

上下文主要是和一些寄存器相关的,一次上下文切换主要包含以下活动:挂起一个线程,保存现场,把当前线程的状态存储在内存的某处,保存程序计数器等。

image-20210729000624912

缓存开销:缓存失效。局部性原理。

何时会导致密集的上下文切换?抢锁、I/O

image-20210729000653747

协作:内存同步。

image-20210729000807372

常见面试问题:

image-20210729000848302

Java内存模型

关键词区分:JVM内存结构、Java内存模型、Java对象模型

image-20210802141748810

底层原理:

过程:Java源代码->JVM ByteCode->具体CPU指令

需要JVM对内存模型定制一个规范,使得在不同的硬件平台字节码的运行结果是一致的。

三兄弟:

JVM内存结构、Java内存模型、Java对象模型

JVM内存结构:和Java虚拟机的运行时区域有关。

Java内存模型:和Java的并发编程有关。

Java对象模型:和Java对象在虚拟机中的表现形式有关。

image-20210802144055486

image-20210802144340783

image-20210802144520062

JMM:

为什么需要JMM?

是规范。

是工具类和关键字的原理。

最重要的3点内容:重排序、可见性、原子性。

为什么需要JMM?

是规范:

C语言不存在内存模型的概念。这就导致了C语言依赖处理器,不同处理器结果不一样。可能一段代码在一种处理器上运行正常,而在另一种处理上运行不正常,无法保证并发安全

因此需要一个标准,让多线程运行的结果可预期

因此,JMM是规范。

是一组规范,需要各个JVM的实现来遵守JMM规范,以便于开发者可以利用这些规范,更方便地开发多线程程序

如果没有这样的一个JMM内存模型来规范,那么很可能经过了不同JVM的不同规则的重排序之后,导致不同的虚拟机上运行的结果不一样,那是很大的问题。

是工具类和关键字的原理:

volatile synchronized Lock等的原理都是JMM。

如果没有JMM,那么就需要我们自己指定什么时候用内存栅栏等,那是相当麻烦的,幸好有了JMM,让我们只需要使用同步工具和关键字就可以开发多线程并发程序。

最重要的3点内容:重排序、可见性、原子性。

重排序

什么是重排序

演示重排序的现象:

package jmm;

/**
 * 演示重排序的现象
 * “直到达到某个条件才停止”,测试小概率事件
 */
public class OutOfOrderExecution {
    private static int x = 0, y = 0;
    private static int a = 0, b = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread one = new Thread(new Runnable() {
            @Override
            public void run() {
                a = 1;
                x = b;
            }
        });

        Thread two = new Thread(new Runnable() {
            @Override
            public void run() {
                b = 1;
                y = a;
            }
        });

        one.start();
        two.start();
        one.join();
        two.join();
        System.out.println("x = " + x + ", y = " + y);
    }
}

期待出现x=1,y=1的结果

package jmm;

import java.util.concurrent.CountDownLatch;

/**
 * 演示重排序的现象
 * “直到达到某个条件才停止”,测试小概率事件
 */
public class OutOfOrderExecution {
    private static int x = 0, y = 0;
    private static int a = 0, b = 0;

    public static void main(String[] args) throws InterruptedException {
        int i = 0;
        for (; ; ) {
            i++;
            a = 0;
            b = 0;
            x = 0;
            y = 0;

            CountDownLatch countDownLatch = new CountDownLatch(1);
            Thread one = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        countDownLatch.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    a = 1;
                    x = b;
                }
            });
            Thread two = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        countDownLatch.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    b = 1;
                    y = a;
                }
            });

            one.start();
            two.start();
            countDownLatch.countDown();
            one.join();
            two.join();
            String result = "第" + i + "次" + " (x = " + x + ", y = " + y + ")";
            if (x == 1 && y == 1) {
                System.out.println(result);
                break;
            } else {
                System.out.println(result);
            }
        }
    }
}

image-20210802155718634

可能出现x=0,y=0的情况,产生了重排序。

image-20210802155957063

image-20210802160037255

重排序的好处:提高处理速度

image-20210802160221028

重排序的3种情况:

编译器优化:包括JVM, JIT编译器等

CPU指令重排:就算编译器不发生重排,CPU也可能对指令进行重排。

内存的”重排序“:线程A的修改线程B却看不到,引出可见性问题。

可见性

package jmm;

/**
 * 演示可见性带来的问题
 */
public class FieldVisibility {
    int a = 1;
    int b = 2;

    private void change() {
        a = 3;
        b = a;
    }
    private void print() {
        System.out.println("b=" + b + ",a=" + a);
    }
    public static void main(String[] args) {
        while (true) {
            FieldVisibility test = new FieldVisibility();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    test.change();
                }
            }).start();

            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    test.print();
                }
            }).start();
        }
    }
}

这段代码可能的执行结果:

b=2,a=1
b=3,a=3
b=2,a=3

还有一种不常见的结果是:

b=3,a=1
package jmm;

/**
 * 演示可见性带来的问题
 */
public class FieldVisibility {
    volatile int a = 1;
    volatile int b = 2;

    private void change() {
        a = 3;
        b = a;
    }
    private void print() {
        System.out.println("b=" + b + ",a=" + a);
    }
    public static void main(String[] args) {
        while (true) {
            FieldVisibility test = new FieldVisibility();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    test.change();
                }
            }).start();

            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    test.print();
                }
            }).start();
        }
    }
}

使用volatile保证可见性。

为什么会有可见性问题?

image-20210802164843618

image-20210802165811214

image-20210802165903069

主内存和本地内存

image-20210802170428240

image-20210802171220482

happens-before

image-20210802172032258

Happens-before规则有哪些?

  1. 单线程规则
  2. 锁操作(Synchronized Lock)
  3. volatile变量
  4. 线程启动
  5. 线程join()
  6. 传递性原则。如果hb(A, B)而且hb(B, C),那么可以推出hb(A,C)

image-20210802174709893

image-20210802174958118

image-20210802175336535

image-20210802175419734

volatile

volatile是什么

  • volatile是一种同步机制,比synchronized或者Lock相关类更轻量,因为使用volatile并不会发生上下文切换等开销很大的行为。
  • 如果一个变量被修饰成volatile,那么JVM就知道了这个变量可能会被并发修改。
  • 但是开销小,相应的能力也小,虽然说volatile是用来同步地保证线程安全的,但是volatile做不到synchronized那样的原子保护,volatile仅在很有限的场景下才能发挥作用。

volatile的适用场合

不适用的场景:a++

package jmm;

import java.util.concurrent.atomic.AtomicInteger;

public class NoVolatile implements Runnable {
    volatile int a;
    AtomicInteger realA = new AtomicInteger();

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            a++;
            realA.incrementAndGet();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        NoVolatile r = new NoVolatile();
        Thread thread1 = new Thread(r);
        Thread thread2 = new Thread(r);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(r.a);
        System.out.println(r.realA.get());
    }
}

适用场合1:

image-20210802185509538

package jmm;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * 适用volatile的情况: 赋值操作
 */
public class UseVolatile1 implements Runnable {
    volatile boolean done = false;
    AtomicInteger realA = new AtomicInteger();
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            setDone();
            realA.incrementAndGet();
        }
    }

    private void setDone() {
        done = true;
    }

    public static void main(String[] args) throws InterruptedException {
        UseVolatile1 r = new UseVolatile1();
        Thread thread1 = new Thread(r);
        Thread thread2 = new Thread(r);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(r.done);
        System.out.println(r.realA.get());
    }
}

done=true;语句不受之前状态的影响。

运行结果一定是

true
20000

下面的例子与之形成对比。

package jmm;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * 不适用volatile的情况
 */
public class NoVolatile1 implements Runnable {
    volatile boolean done = false;
    AtomicInteger realA = new AtomicInteger();
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            flipDone();
            realA.incrementAndGet();
        }
    }

    private void flipDone() {
        done = !done;
    }

    public static void main(String[] args) throws InterruptedException {
        NoVolatile1 r = new NoVolatile1();
        Thread thread1 = new Thread(r);
        Thread thread2 = new Thread(r);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(r.done);
        System.out.println(r.realA.get());
    }
}

运行结果可能为:

false
20000

也可能为:

true
20000

适用场合2:

image-20210802190807490

package jmm;

/**
 * 演示可见性带来的问题
 */
public class FieldVisibility {
    int a = 1;
    int abc = 1;
    int abcd = 1;
    volatile int b = 2;

    private void change() {
        a = 3;
        abc = 8;
        abcd = 80;
        b = 0;
    }
    private void print() {
        if (b == 0) {
            System.out.println("b=" + b + ",a=" + a + ",abc="+ abc + ",abcd=" + abcd);
        }
    }
    public static void main(String[] args) {
        while (true) {
            FieldVisibility test = new FieldVisibility();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    test.change();
                }
            }).start();

            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    test.print();
                }
            }).start();
        }
    }
}

此例中,变量b作为了触发器。

image-20210802191349890

volatile作用:可见性、禁止重排序

image-20210802191648659

volatile和synchronized的关系

image-20210802191825076

学以致用:用volatile修正重排序问题

package jmm;

import java.util.concurrent.CountDownLatch;

/**
 * 演示重排序的现象
 * “直到达到某个条件才停止”,测试小概率事件
 */
public class OutOfOrderExecution {
    private static volatile int x = 0, y = 0;
    private static volatile int a = 0, b = 0;

    public static void main(String[] args) throws InterruptedException {
        int i = 0;
        for (; ; ) {
            i++;
            a = 0;
            b = 0;
            x = 0;
            y = 0;

            CountDownLatch countDownLatch = new CountDownLatch(1);
            Thread one = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        countDownLatch.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    a = 1;
                    x = b;
                }
            });
            Thread two = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        countDownLatch.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    b = 1;
                    y = a;
                }
            });

            one.start();
            two.start();
            countDownLatch.countDown();
            one.join();
            two.join();
            String result = "第" + i + "次" + " (x = " + x + ", y = " + y + ")";
            if (x == 0 && y == 0) {
                System.out.println(result);
                break;
            } else {
                System.out.println(result);
            }
        }
    }
}

加了volatile就会禁止指令重排序。执行结果就不会出现x = 0, y = 0的情况。

小结

image-20210802192649272

image-20210802192739335

image-20210802192833882

image-20210802193353354

image-20210802193225301

使用synchronized保证可见性。

原子性

什么是原子性?

Java中的原子操作有哪些?

long double的原子性问题?

原子操作 + 原子操作 != 原子操作

什么是原子性?

image-20210802194021291

image-20210802194227546

Java中的原子操作有哪些?

  • 除了long double之外的基本类型(int byte short boolean char float)的赋值操作
  • 所有引用reference的赋值操作,不管是32位的机器还是64位的机器。
  • java.util.concurrent.Atomic.*包中所有类的原子操作

long、double的原子性问题?

image-20210802194922175

原子操作 + 原子操作 != 原子操作

image-20210802195202921

单例模式

为什么需要单例?

  • 节省内存和计算
  • 保证结果正确
  • 方便管理

单例模式适用场景

image-20210802214035801

单例模式的8种写法

饿汉式(静态常量)(可用)

package singleton;

/**
 * 饿汉式(静态常量)(可用)
 * 类装载的时候就完成了实例化
 */
public class Singleton1 {
    private final static Singleton1 INSTANCE = new Singleton1();

    private Singleton1() {
    }

    public static Singleton1 getInstance() {
        return INSTANCE;
    }
}

饿汉式(静态代码块)(可用)

package singleton;

/**
 * 饿汉式(静态代码块)(可用)
 * 类装载的时候就完成了实例化
 */
public class Singleton2 {
    private static final Singleton2 INSTANCE;
    static {
        INSTANCE = new Singleton2();
    }

    private Singleton2() {
    }

    public static Singleton2 getInstance() {
        return INSTANCE;
    }
}

懒汉式(线程不安全)(不可用)

package singleton;

/**
 * 懒汉式(线程不安全)(不可用)
 */
public class Singleton3 {
    private static Singleton3 instance;
    private Singleton3() {

    }
    public static Singleton3 getInstance() {
        if (instance == null) {
            instance = new Singleton3();
        }
        return instance;
    }
}

懒汉式(线程安全,使用同步方法)(不推荐使用)

package singleton;

/**
 * 懒汉式(线程安全,使用同步方法)(不推荐使用)
 */
public class Singleton4 {
    private static Singleton4 instance;
    private Singleton4() {

    }
    public synchronized static Singleton4 getInstance() {
        if (instance == null) {
            instance = new Singleton4();
        }
        return instance;
    }
}

懒汉式(线程不安全,使用同步代码块)(不可用)

package singleton;

/**
 * 懒汉式(线程不安全,使用同步代码块)(不可用)
 */
public class Singleton5 {
    private static Singleton5 instance;
    private Singleton5() {

    }
    public static Singleton5 getInstance() {
        if (instance == null) {
            synchronized (Singleton5.class) {
                instance = new Singleton5();
            }
        }
        return instance;
    }
}

懒汉式(双重检查)(推荐面试使用)

package singleton;

/**
 * 懒汉式(双重检查)(推荐面试使用)
 */
public class Singleton6 {
    private volatile static Singleton6 instance;
    private Singleton6() {

    }
    public static Singleton6 getInstance() {
        if (instance == null) {
            synchronized (Singleton6.class) {
                // 此处可以用happens-before解释,
                // 第二个执行到此的线程一定可以看到第一个执行到此的线程所做的改变
                if (instance == null) {
                    instance = new Singleton6();
                }
            }
        }
        return instance;
    }
}

双重检测锁方式的优点:线程安全;延迟加载;效率较高。

为什么要double-check?

  1. 线程安全
  2. 单check行不行?不行,可能有两个线程同时走过外部if判断,到达synchronized代码块。
  3. 性能问题。比同步方法性能更好。

为什么要用volatile?

  1. 新建对象实际上有3个步骤
    1. construct empty resource()
    2. call constructor
    3. assign to rs
  2. 重排序会带来NPE
  3. 防止重排序

7、静态内部类【可用】

package singleton;

/**
 * 静态内部类(可用),属于懒汉的行为方式
 */
public class Singleton7 {

    private Singleton7() {

    }

    private static class SingletonInstance {
        // 既线程安全,又懒加载
        private static final Singleton7 INSTANCE = new Singleton7();
    }

    public static Singleton7 getInstance() {
        return SingletonInstance.INSTANCE;
    }
}

8、枚举

生产实践中最佳的写法

package singleton;

import org.omg.PortableInterceptor.INACTIVE;

/**
 * 枚举方式的单例
 */
public enum Singleton8 {
    INSTANCE;
    public void whatever() {
    }
}

class Test {
    public static void main(String[] args) {
        Singleton8.INSTANCE.whatever();
    }
}

不同方式的对比

image-20210802225129519

为什么枚举实现的单例最好?

image-20210802230719160

枚举是一种特殊的类。

枚举是一种final Class,继承自java.lang.Enum类。

枚举也能做到懒加载

各种写法的适用场景

  • 最好的方法是利用枚举,因为还可以防止反序列化重新创建新的对象。
  • 非线程同步的方法不能使用。
  • 如果程序一开始要加载的资源太多,那么就应该使用懒加载。
  • 对于饿汉式,如果是对象的创建需要配置文件就不适用。
  • 懒加载虽然好,但是静态内部类这种方式会引入编程复杂性。

面试题

image-20210802232550616

为什么双重检查模式要用volatile?

  1. 禁止重排序
  2. 保证可见性

image-20210802233420314

image-20210802233537874

讲一讲什么是Java内存模型?

  • 讲一下为什么会出现Java内存模型,C语言为什么没有内存模型?
  • 阐述相关概念的区别:JVM内存结构、Java内存模型、Java对象模型
    • Java内存模型是一种规范,有三个特点:重排序、原子性、可见性。
    • 从可见性可以引入到本地内存和主内存。引入volatile关键字,引入happens-before。
    • volatile适用场合,以及volatile和synchronized的关系。
    • 原子性。介绍下哪些操作是原子性。除了long double之外的基本类型赋值、引用类型赋值、atomic包下的操作

什么是内存可见性?

讲一下操作系统的内存结构图以及JMM

死锁

死锁的4个必要条件

  1. 互斥条件
  2. 请求与保持条件
  3. 不剥夺条件
  4. 循环等待条件

缺一不可,逐个分析之前的例子。

空文件

简介

Java并发核心技术精讲 展开 收起
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
1
https://gitee.com/cecurio/java-concurrency-in-practice.git
git@gitee.com:cecurio/java-concurrency-in-practice.git
cecurio
java-concurrency-in-practice
java-concurrency-in-practice
master

搜索帮助

53164aa7 5694891 3bd8fe86 5694891