JAVA成神之路---线程八大基础--线程锁(死锁深究)-内存模型(持续更新中)

java线程 八大核心基础

一、实现线程的方法 到底有一种 还是两种 还是四种

	1、整体介绍

	2、正确的理解
		百度搜索 会出现 两种 三种 四种 六种 等不同的实现方式(此处需要进行自我思考)
		oracle 官方提供的官方文档是说明 实现线程有两种方案: 1 实现Runnable接口 2 继承Thead 类中
		代码介绍

使用Runnable方式实现


package com.manbu.manbudemo.threadcorenowledge.cheatethread;

/**
 * @description: 使用 runable 方式创建线程
 * @author: wanghan
 * @time: 2020/12/12 下午10:22
 */
public class RunnableStyle implements  Runnable{
    // 使用子线程执行方法
    public static void main(String[] args) {
        Thread thread = new Thread(new RunnableStyle());
        thread.start();

    }

    @Override
    public void run() {
        System.out.println("用Runnable方法实现线程");
    }
}

使用Thread方式实现

package com.manbu.manbudemo.threadcorenowledge.cheatethread;

/**
 * @description: 用Thread 方式 实现线程
 * @author: wanghan
 * @time: 2020/12/12 下午10:24
 */
public class ThreadStyle extends  Thread{
    @Override
    public void run() {

        System.out.println("使用Thread类实现线程");
    }

    public static void main(String[] args) {
        new ThreadStyle().start();


    }
}


解析:

Thread 官方文档有提到过 有两个常用的构造器 (构造函数):
方法一(实现Runable接口) 使用的构造函数 是需要加一个参数 是Runale 这个参数
方法二(继承Thread类) 使用的构造函数 是无参构造函数
两种方法对比 我们通常会选择 方法一 实现效果会更好:
那么具体好在哪里呢
方法二 的缺点呢 一共有以下三个
一、从解藕角度讲:从宏观的代码风格角度讲 :我们线程具体执行的内容 任务 就是run方法内具体的执行内容 要与Thread 分开来的 解藕
二、从资源角度考虑:如果我们使用方法二的话 我们每次想去新建个任务都需要新建个线程而这样会有一些损耗是比较大的 需要创建 运行 销毁 而我们如果使用方法一的话 就可以通过后续的线程池 来管理并且大大节约资源 提高性能
三、从语法和可扩展性角度讲: 因为java是单继承多实现 所以如果继承了Thread 的话 这个类就无法去继承另外其他的类了 大大限制可扩展性

深入源码-进一步了解:

首先观察Thread类里面 是个简单粗暴的方法  那么具体的这个 target 又是什么呢 跟踪上去我们会发现 是Runable 接口 会在我们构造函数的时候被传入 并且被初始化

所以 我们就明白了 : 当我们在使用 第一种方法 实现Runable接口的时候我们是传入了一个 target对象 那么我们在运行到run()方法这一行代码的时候呢就不会为空 可以正常运行 就执行了我们传入的Runable对象的 run()方法
在这里插入图片描述

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

接下来 我们继续看看继承Thread类的方法 : 首先谈到继承 回顾一下继承相关的概念:
子类重写父类后 那么父类里面的方法都会被覆盖 抛弃 将不会被我们这一次的调用所采纳 我们实际执行的 就是继承后重写的那一个语句 尔没有了下面的这个判断方法

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

总结 : 本质上都是执行了run()方法 只是方法的来源不同

思考题:

		如果同时使用两种方法 会怎么样呢?
package com.manbu.manbudemo.threadcorenowledge.cheatethread;

/**
 * @description: 同时使用 runable 和thread 两种实现方式
 * @author: wanghan
 * @time: 2020/12/12 下午10:51
 */
public class BothRunableAndThread {
    public static void main(String[] args) {
		/*
		*代码讲解()->{
		 *System.out.println("我来自Runable实现线程");
		*}
		*等同于
		*new Runable(){
		*	@Override
		*	public void run(){
		*	System.out.println("我来自Runable实现线程");
		*	}
		*}
		*前者为 jdk1.8的lamabda表达式写法
		*
		*/
        new Thread(()->{
            System.out.println("我来自Runable实现线程");
        }) {
            @Override
            public void run() {
                System.out.println("我来自Thread实现线程");
            }
        }.start();
    }
}

在这里插入图片描述
那么执行完成后 为什么显示的是Thread呢

从面向对象的思想去考虑:

刚刚使用的 实际是个匿名的 内部类 在匿名内部类里面我们又重写了 run()方法 那么这个重写的run方法 实际上是 thread类的 run方法于此同时 我们在创建Thread类的时候又传入了一个 runable对象 . 那么根据 java语法的特性 如果我们同时传入了 runable对象 又同时 重写了 run方法 那么由于 Thread 他本身的run方法被重写过了 所以重写过的run方法 也就不包含了原本类里面run方法所包含的经典的三行代码
结论: 就算我们已经传入了 runable 对象了 但是在下面 run方法同时又被thread重写了 那么 target.run() 就被覆盖了 所以 runable 的代码 就将不会被执行
总结: 有两类创建线程方式 准确的讲 实现线程只有一种方式 那么就是构造一个Thread类 但是 在Thread类里面 他的run方法却有两种不同的方式
实现线程单元 一种: 实现runable接口的run方法 并且把runable对象传给Thread 第二种: 直接重写了Thread类里面的run方法 这是线程单元的两种实现方式

3、如何从宏观和微观两个角度去提升技术

4、 经典错误观点
(1):线程池创建线程也算是一种新建线程的方式

package com.manbu.manbudemo.threadcorenowledge.cheatethread.wrongways;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import static java.util.concurrent.Executors.newCachedThreadPool;

/**
 * @description: 线程池创建线程
 * @author: wanghan
 * @time: 2020/12/12 下午11:23
 */
public class ThreadPool {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i=0;i<1000;i++){
            executorService.submit(new Task(){});
        }


    }
}

class  Task implements  Runnable{

    @Override
    public void run() {
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName());
    }
}

首先 我们跟随线程池的源码 我们观测具体创建线程的地方是如何操作的
在这里插入图片描述
具体跟踪到如下代码 发现 其实本质上还是调用了Thread 类去传入了我们的runable 对象 所以 线程池并不可以当作是一个创建线程的方式
(2) Callable和FutureTask创建线程也算是一种新的新建线程的方式!
以上两种实现方式呢 相对来说较为复杂一些 所以我们拉出来类的实现图 去分析下FutureTask的本质
在这里插入图片描述

在这里插入图片描述

从上图我们可以很好的观测到 FutureTask的上层还是有个runable接口的 所以啊 这个FutureTask的本质也是和runable和thread去实现的 从上图我们可以看到 我们的RunableFuture也好我们的FutureTask 也还 都实现了 Runable接口
(3) 无返回值是实现Runable 和有返回值的是实现Callable 所以Callable是实现线程的新的方式?
其实从上图中我们也可以观测到Callable 其实也是依赖于Runable 实现的 所以本质上是没有什么区别的
(4) 关于定时器的观点

package com.manbu.manbudemo.threadcorenowledge.cheatethread.wrongways;

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

/**
 * @description: 定时器创建线程
 * @author: wanghan
 * @time: 2020/12/12 下午11:47
 */
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);
    }
}

在这里插入图片描述
在这里插入图片描述

其实本质上我们观测代码 还是离不开Thread和Runable接口
5、常见面试题
面试题解析: 总共有几种实现线程的方式
从不同角度来谈
从代码实现上有多种实现方式:
从本质来谈 就只有 Runable 和Thread 两种
(亮点答案)从原理上来谈 其实只有Thread 一种方式 因为最终都是调用了Thread 的run方法 实现的线程.
简单谈下两种实现方式的不同 和优缺点
一、从解藕角度讲:从宏观的代码风格角度讲 :我们线程具体执行的内容 任务 就是run方法内具体的执行内容 要与Thread 分开来的 解藕
二、从资源角度考虑:如果我们使用方法二的话 我们每次想去新建个任务都需要新建个线程而这样会有一些损耗是比较大的 需要创建 运行 销毁 而我们如果使用方法一的话 就可以通过后续的线程池 来管理并且大大节约资源 提高性能
三、从语法和可扩展性角度讲: 因为java是单继承多实现 所以如果继承了Thread 的话 这个类就无法去继承另外其他的类了 大大限制可扩展性

二、启动线程的正确和错误方式

1、start()和run()的比较

			代码讲解:

在这里插入图片描述
上边执行后我们观测到了 run 方法实际是main主线程调用的 这个显然不符合本意 我门本意想新建一个子线程去执行的.
而start才是子线程锁调用的

2、start()方法原理

		方法含义:
				1.启动新线程:线程对象在初始化完成之后调用了start方法 当主线程执行到start方法的时候会通知虚拟机去执行子线程 但是具体的执行是移交给线程调度器去执行的. 所以 得到另一个概念,start 方法调用后可能不会立即执行 可能会在排队稍后执行也可能会不执行 比如 遇到了 饥饿的情况.
				最显而易见的地方是 我们同时启动 线程1 和线程2  但是线程2 优先于线程1 执行  start 方法 会同时执行两个线程 一个是当前子线程 一个是主线程

3.准备工作

子线程的准备工作 首先会让自身处于就绪状态(已获取到除cpu以外的其他资源) 比如说已经设置了上下文 、栈、线程状态以及PC(寄存器) PC 指向了我们程序运行的位置

不能重复start:

在这里插入图片描述
出现了 非法的线程状态 报错
解析: 线程从创建到运行 到结束 始终都有一个状态 然而一个进入终止状态的线程 又不能再返回最初的状态

延伸题: 那么线程池 又是怎么实现线程重复运行的呢??
其实并不是让线程终止掉 而是让线程挂起

深入源码
		源码解析:
		 首先启动一个新线程 将会检测线程状态-》加入线程组 -》调用start0()的这个native方法
		 源码对照:

在这里插入图片描述

			流程
			native start()方法	

4、run()方法原理

		源码分析 

在这里插入图片描述
优先判断 传入的target对象是否为空 然后再调用target对象的run方法
那么 如果继承了Thread 类 就讲重写run方法

		如果我们直接如执行run方法的话 就是一个普普通通的run方法和我们写的普通方法没区别 所以就会在主线程启动. 所以要采用start 的方法间接调用start 方法

5、常见面试题

		1、既然start方法会调用run方法 为什么我们会选择调用start方法 而不是直接调用run方法呢?
		2、线程的 六个状态
				1. 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
				2. 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。
				3. 阻塞(BLOCKED):表示线程阻塞于锁。
				4. 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
				5. 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
				6. 终止(TERMINATED):表示该线程已经执行完毕。

三、上山容易下山难–如何正确停止线程

	1、原理介绍(停止线程)
			使用interrupt 来通知 而不是强制停止!
			1、线程任务被创造的时候 大部分都是正常运行到结束的
			2、 什么时候我们会用到停止线程
					1、 用户取消
					2、突然服务需要被快速关闭
					3、运行的超时 或者出错
			使用interrupt 让一个线程去通知另外一个线程停止
			被中断线程本身 拥有决定权 不但能决定何时响应中断何时去停止 还有最高决定权 是否停止线程.如果我们想中断一个线程 而哪个线程本身不想被中断.那么我们无能为力.无法做到强行停止.
			线程通常会在什么情况下停止.
			1、 run 方法所有代码执行完毕
			2、异常出现 方法中没有捕获
	2、最佳实践 如何正确停止线程
		通常的停止过程(无外界干涉的情况下): 通过Interrupted通知中断线程
			普通情况:(run方法里面没有 sleep和wait方法的时候标准写法):
/**
 * @description:
 * @author: wanghan  run 方法内 没有sleep 或者wait 方法时停止线程
 * @time: 2020/12/13 上午1:30
 */
public class RightWayStopThreadWithSleep  implements  Runnable{
    public static void main(String[] args) {
        new Thread(new RightWayStopThreadWithSleep()).start();
        try {
            Thread.sleep(1000);
            //发送中断通知
            Thread.interrupted();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        int num=0;
        //Thread.currentThread().isInterrupted() 接收监听当前线程是否已经中断
        while (!Thread.currentThread().isInterrupted()&&num<Integer.MAX_VALUE/2){
           if (num%10000==0){
               System.out.println(num+"是10000的倍数");
           }
           num++;
        }
        System.out.println("任务运行完成了");
    }


}
			线程可能被阻塞
package com.manbu.manbudemo.threadcorenowledge.stopthread;

/**
 * @description: 带有sleep 中断线程的写法
 * @author: wanghan
 * @time: 2020/12/13 上午1:43
 */
public class RightWayStopThreadWithSleep {
    public static void main(String[] args)  {
        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) {
                e.printStackTrace();
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(500);
        thread.isInterrupted();
    }
}

在这里插入图片描述
观测如上代码 我们应当对sleep的时候产生的异常进行抓取并处理

			如果线程在每次工作迭代后都阻塞 (调用sleep方法等):

在这里插入图片描述

package com.manbu.manbudemo.threadcorenowledge.stopthread;

/**
 * @description: 如果在执行过程中 每次循环中都有 sleep 或者wait 那么 我没每次迭代则不需要检测是否已中断
 * @author: wanghan
 * @time: 2020/12/13 上午2:19
 */
public class RightWayStopWithSleepEveryLoop {
    public static void main(String[] args)  {
        Runnable runnable=()-> {
            int num =0;
            try {
                while (num<=10000   &&!Thread.currentThread().isInterrupted()){
                    if (num%100==0){
                        System.out.println(num+"是100的倍数");
                    }

                    num++;
                    Thread.sleep(10);
                }


            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.isInterrupted();
    }
}

在如上的代码中 其实 &&!Thread.currentThread().isInterrupted() 这段代码是多余的
因为具体在sleep发生的时候 我们已经判断过了通知状态 sleep 其实可以自己感知的 综上所诉 当我们的线程里面的循环的时候拥有 Thread相关的操作我们就不需要去判断线程是否终止的状态了

这是bug么?自动清除中断信号?

while 内的 try/catch的问题
package com.manbu.manbudemo.threadcorenowledge.stopthread;

/**
 * @description: 如果while 里面放try catch 会导致 中断失效
 * @author: wanghan
 * @time: 2020/12/13 上午2:31
 */
public class CantInterrupted {
    public static void main(String[] args) {
        Runnable runnable=()->{
            int num=0;
            while (num<10000){
                if (num%100==0){
                    System.out.println(num+"是100的倍数");
                }
                try {
                    num++;
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.isInterrupted();
    }
}

如上代码报错后为什么还会继续执行 这时候我们想到可以使用
!Thread.currentThread().isInterrupted() 代码来检测状态
但是实际效果是达不到中断的效果的
因为 sleep 已经把当前线程的标记位清除了 所以检测不到任何状态
接下来进入解决环节

实际开发中的两种最佳方案

响应中断的方法总结

两种停止线程最佳处理方案:

优先选择 传递中断
package com.manbu.manbudemo.threadcorenowledge.stopthread;

/**
 * @description: 最佳实践 catch了 Interrupted 之后 优先选择在方法签名中抛出异常   那么在run方法中 就会强制要求 我们在run方法中使用try catch
 * @author: wanghan
 * @time: 2020/12/13 下午3:12
 */
public class RightWayStopThreadInPord implements Runnable {
    @Override
    public void run() {

        while (true&&!Thread.currentThread().isInterrupted()){
            System.out.println("go-----go----go");
                throwInMethod();

        }
    }
        //其他子方法 或者其他同学编写的方法
    private void throwInMethod() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

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

看如上代码 其实执行后
在这里插入图片描述
实际不会把线程中断
那么其实我们应该作的事情是上移try catch 的权限 讲线程异常权限交给上游方法去作后续的中断或者数据保存的操作
就是
throwInMethod 方法去抛出异常 然后由RUN()方法来负责try catch

不想传递或无法传递 选择恢复中断
package com.manbu.manbudemo.threadcorenowledge.stopthread;

/**
 * @description: 最佳实践 catch了 Interrupted 之后 优先选择在方法签名中抛出异常   那么在run方法中 就会强制要求 我们在run方法中使用try catch
 * @author: wanghan
 * @time: 2020/12/13 下午3:12
 */
public class RightWayStopThreadInPord implements Runnable {
    @Override
    public void run() {

        while (true){
            if (Thread.currentThread().isInterrupted()) {
                System.out.println(" 程序运行结束");
               break;
            }
            throwInMethod();

        }
    }
        //其他子方法 或者其他同学编写的方法
    private void throwInMethod() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            e.printStackTrace();
        }

    }

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

不应屏蔽中断

以下方法 均可以响应 中断操作
可以为了响应中断而抛出的interruptedException的常见方法列表总结在这里插入图片描述
在这里插入图片描述

好处
被中断的线程自身拥有如何响应中断的权利,不应该鲁莽使用stop 方法 数据完整性也得到了保障

3、错误的停止方式

		stop 数据错乱  suspend 带锁休息 容易造成死锁  resume 
		用volatile 设置 boolean 标记位

	4、停止线程的相关重要函数解析
	5、常见面试题

四、线程的六个状态

				1. 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
				2. 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。
				3. 阻塞(BLOCKED):表示线程阻塞于锁。
				4. 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
				5. 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
				6. 终止(TERMINATED):表示该线程已经执行完毕。

在这里插入图片描述

五、thead 和 object 类中 和线程相关的重要方法

为什么 wait()和 notify 定义在object里面而 sleep 在thread 里面
控制线程休息 和被唤醒
能不能用三种方式实现队列

json方法 是让主线程/子线程 等待其他子线程执行完毕

六、线程的各种属性 面试题

守护线程:
守护的就是我们用户自己写的线程
守护线程三个特性
1、线程默认继承自父线程
2、被谁启动 jvm 除了main 函数其他自带守护线程
3、不影响jvm退出
守护线程 和普通线程的区别
整体没区别
唯一的区别在于是否影响jvm 退出
用户线程执行我们的逻辑 守护线程是服务于我们的 也就是服务于用户线程的

是否需要我们给线程设置为守护线程
setDaemon 方法可以设置为守护线程

我们不应该 设置守护线程 会有风险 和数据不一致

线程优先级
java 有10个级别 线程默认级别是 5 最低是1 最高是 10

七、线程未补货的异常。UncaughtException 应该如何处理

八、线程是把双刃剑 多线程会导致性能问题(线程引入的开销和上下文切换)

在这里插入图片描述

什么情况下 会遇到线程安全问题
比如 set get 方法 
一个线程在执行set 方法的时候 另一个线程get方法可能会获取新值也可能获取旧值 解决方案 可以加上 volatile 关键字

九、常见面试题

java内存模型

	一、到底什么叫 底层原理 本章节研究的内容是什么


	 二、三兄弟。jvm 内存结构 VS java内存模型VS java对象
		jvm 内存结构:

在这里插入图片描述
其中 堆是占用面积最大的一块 (new 或者其他操作创建的实例对象)
虚拟机栈 就是java栈 这里面保存了基本数据类型 以及对于对象的引用而不是这个对象本身
方法区 已经加载的 static 方法 类信息 和常量信息 包含永久引用
本地方法栈 主要保存和本地方法相关的 什么叫本地方法呢 本地方法指的是 native 方法 就是引入c的 方法
程序计数器 保存当前线程所执行到的字节码的行号数

		java内存模型
		
		 java对象模型

在这里插入图片描述
在这里插入图片描述

	四、JMM 是什么(java Memory Model)

	五、重排序

	六、 可见性

	七、原子性

	八、面试题总结

死锁的前世今生

	一、死锁是什么?有什么危害?

	二、发生死锁的例子

	三、死锁的四个必要条件

	四 如何定位死锁

	五 修复死锁的策略

	六、实际工程中如何避免死锁

	七、其他活性故障(活跃性问题)

	八、常见面试题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值