Java多线程总结笔记面试干货

一、线程的创建方式

1、继承Thread类(不推荐):只能被单继承,不推荐使用

2、实现Runable的接口(推荐):不需要返回值时,推荐使用

3、实现Callable接口(JDK1.5): 核心方法叫call()方法(相当于run()),有返回值,推荐使用

4、线程池:通过Executor 的工具类可以创建三种类型的普通线程池:固定大小的线程池、单线程池、缓存线程池

问题:线程调用start方法和调用run方法的区别?

a) 直接调用run方法相当于在当前线程执行run方法的方法体(同步,注意 :run方法不是手动调用的)

b) 调用start方法才会在内存中开启一个线程,和当前线程挣夺CPU时间片

二、线程的生命周期

创建----》就绪----》运行----》阻塞—》死亡
在这里插入图片描述
注意:有时线程直接从 运行—》死亡,不经过阻塞,所以阻塞状态不一定执行

三、线程安全

什么是线程安全问题?- 当多个线程访问共享资源时,因为线程调度不确定性,导致该资源最后的属性状态不一致的问题,就称之为线程安全问题。

为什么会有线程安全的问题发生?
1)可见性
2)原子性
3)有序性

当这个3个特性有一个特性没有得到保证时,就会发生线程安全的问题

什么是可见性?

可见性:任意一个线程对内存数据的修改,对其他的线程是立即可见的

/**
* 打印1 - 正常情况99.99%
* 打印0 - 概率很低
* 可能会循环很长一段时间
*/
public class MainTest3 {

    private static int i = 0;
    private static boolean flag = true;
    public  static void main(String[] args) {
        new Thread(){
            @Override
            public void run() {
                while(flag){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(i);
            }
        }.start();
        i=1;
        flag=false;
    }
}

怎么保证可见性:
1、使用同步关键字synchronized(特性:当某个线程释放锁资源时,会强制将工作内存中的数据刷新到主存中)
2、使用volatile关键字,被该关键字修饰的属性具有可见性的特点

什么是原子性?
原子性:某一步操作,是一个原子的不可拆分的操作

public class MainTest3 {

    private static int x = 0;
    public  static void main(String[] args) {
        for (int i = 0; i < 1000; i++) {
            new Thread(){
                @Override
                public void run() {
                    x++;
                }
            }.start();
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("x=" + x);
    }
}
i++;//非原子
i=0;//原子

怎么保证原子性:
1、使用同步关键字synchronized,因为加锁了之后,其他线程就不能执行这个代码,所有代码从头到尾不会被打断
2、使用JDK1.x之后提供的一些具有原子性的工具类(AtomicXxxxxxx)

什么是有序性?
有序性:就是保证代码的执行顺序

CPU的指令重排:cpu在执行一段代码时,从优化的角度考虑,有可能会打乱代码的执行顺序。
指令重排的保证:在单线程情况下,重排后的代码执行的结果一定和重排前的代码执行结果一致。

//共享资源
flag = true;
obj = null;
//线程1
while(flag);
obj.method();
//线程2
obj = new Object();
flag = false;

怎么保证有序性?
1、保证代码的原子性,可以杜绝指令重排所带来的负面效果
2、使用volatile关键字,可以保证属性的局部有序

线程安全的懒汉式单例模式

/**
* 懒汉式 - 线程安全版
*/
public class SingleClass {
    //私有的静态变量,用来保存当前的单例对象
    private volatile static SingleClass singleClass;
    //私有化构造方法
    private SingleClass(){}
    //公有的静态方法,获得单例对象
    //1
    //2
    public static SingleClass getInstance(){
        //2
        if(singleClass == null){
            synchronized(SingleClass.class){
                if(singleClass == null) {
                    //1 - 申请堆内存地址
                    //2 - 初始化内存
                    //3 - 将变量指向该内存地址
                    singleClass = new SingleClass();
                }
            }
        }
        return singleClass;
    }
}

synchronized关键字:

作用范围:代码块/方法(非静态方法、静态方法)

synchronized(同步锁对象){

}

同步锁对象通常用来锁住多个线程中公有的对象,具体锁什么需要根据实际的业务决定。
非静态同步方法默认锁的是this
静态同步方法默认锁的是当前类的Class对象

同步关键字的优化:提高锁的细粒度(降低锁的范围)

四、线程间的通讯

什么是线程间通讯?- 在实际开发时,往往会碰到多线程模型,可能某个线程需要拿到另一个线程的计算数据,但是因为线程调度的不确定性,所以需要通过线程间通讯的技术实现线程之间的数据交换

线程间通讯的方案:
1、wait()/notify()方法
2、阻塞队列

五、线程池

JDK提供了ThreadPoolExecutor对象实现线程池:

形参介绍:
int corePoolSize - 核心线程数的大小
int maximumPoolSize - 最大线程数的大小
long keepAliveTime - 线程的空闲存活时间
TimeUnit unit - 线程存活时间的单位
BlockingQueue<Runnable> workQueue - 阻塞队列的实现
ThreadFactory threadFactory - 线程池中线程的创建方式
RejectedExecutionHandler handler - 当阻塞队列已满,新的任务的拒绝策略(四种)

六、线程的运用场景

MQ消费消息时是一个同步的过程,所以可以通过线程池并发消费提高消费的速率。

希望能帮助到你,你的点赞是我最大的动力,嘻嘻

猜你喜欢

转载自blog.csdn.net/qq_41936224/article/details/106924762
今日推荐