多线程学习笔记

面试问题切入点

  • 进程:执行程序的一次执行过程,是一个动态的概念,是系统资源分配的单位
  • 线程:线程是cpu调度和执行的单位

线程状态

  • 就绪 runnable
  • 运行 running
  • 阻塞 blocked
  • 死亡 terminated

并发的三大特性

  • 原子性:一个操作全部做完,cpu不可以中途暂停
  • 可见性:volatie,JMM缓存一致性协议
  • 有序性:指令重排,volatile和synchronized

sleep()和wait()的区别

  1. sleep是Thread类的静态本地方法,wait是Object类的本地方法
  2. sleep方法不会释放lock,但是wait会释放,而且会加入到等待队列中
  3. sleep方法不依赖同步器synchronized,但是wait需要依赖synchronized关键字
  4. sleep不需要被唤醒,但是wait需要
  5. sleep一般用于当前线程休眠,wait则多用于多线程之间的通信

阻塞的情况分为三种

  • 等待阻塞:wait方法,需要依靠notify或者notifyAll
  • 同步阻塞:同步锁
  • 其他阻塞:sleep或者join方法,或者发出了IO请求

线程优先级
Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行
线程的优先级用数字表示,范围1-10

thread.setPriority(10);
System.out.println(Thread.currentThread().getName() + "->" + Thread.currentThread().getPriority())

线程同步
由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突的问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized
同步块:synchronized(obj){ }
obj称为同步监视器,同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class

死锁产生的四个必要条件

  • 互斥条件:一个资源每次只能被一个进程使用
  • 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
  • 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
  • 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系

Lambda表达式
函数式接口的定义:任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口

Thread thread = new Thread(()->{
            System.out.println("只有runnable一个接口");
});

Lock
ReentrantLock类实现了Lock,可以显式加锁、释放锁

// 定义lock锁
    private final ReentrantLock lock = new ReentrantLock();
  • Lock是显示锁,synchronized是隐式锁,出了作用域自动释放
  • Lock只有代码块锁,synchronized有代码块锁和方法锁
  • 使用Lock锁,JVM将花费较少的时间来调度进程,性能更好。并且具有更好的扩展性
  • 优先使用顺序:Lock>同步代码块>同步方法

可重入锁

广义上的可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁(前提得是同一个对象或者class),这样的锁就叫做可重入锁。ReentrantLock和synchronized都是可重入锁

synchronized

  • 原子性:synchronized保证语句块内操作是原子的
  • 可见性:synchronized保证可见性(通过“在执行unlock之前,必须先把此变量同步回主内存”实现)
  • 有序性:synchronized保证有序性(通过“一个变量在同一时刻只允许一条线程对其进行lock操作”)

synchronized的使用

  • 修饰实例方法,对当前实例对象加锁
  • 修饰静态方法,多当前类的Class对象加锁
  • 修饰代码块,对synchronized括号内的对象加锁

Synchronized与RentrantLock的对比

  • 对于RentranLock来说,加锁的原理在于修改state的值,类似信号灯法
  • synchronized的原理在于修改对象头,对象的结构有对象的实例数据、对象头(固定12字节)、数据对齐部分(8的倍数)
  • 对象头固定有12字节(64bit mark word、32bit class metadata address)

对象的状态

  • new 无状态
  • 偏向锁
  • 轻量级锁
  • 重量级锁
  • gc标记
    在这里插入图片描述
    在这里插入图片描述

volatile(保证可见性)

简单的描述缓存一致性问题:对于某个共享变量,每个操作单元都缓存一个该变量的副本。当一个操作单元更新其副本时,其他的操作单元可能没有及时发现,进而产生缓存一致性问题。Java内存模型(JMM)应运而生。它规定了必须要遵守哪些访问规则,以保证程序的正确性

  • volatile写的内存语义:当写一个volatile变量时,JMM会把该线程对应的本地中的共享变量值刷新到主内存。
  • volatile读的内存语义:当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。
  • 如果多个线程同时操作volatile变量,那么对该变量的写操作必须在读操作之前执行(禁止重排序),并且写操作的结果对读操作可见(强缓存一致性)

ThreadLocal

  • ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。
  • 每一个Thread对象含有一个ThreadLocalMap类型的成员变量threadLocals,它存储本线程中所有ThreadLocal对象及其对应的值
  • 当执行set、get方法时,ThreadLocal会首先获取当前线程对象,然后获取当前线程的ThreadLocalMap对象,再以当前ThreadLocal对象为key,将值存储进ThreadLocalMap对象中
  • 内存泄漏问题,当ThreadLocalMap的key为弱引用回收ThreadLocal时,value依旧存在,需要调用remove()方法进行回收
    在这里插入图片描述

线程池:在一个应用程序中,我们需要多次使用线程,也就意味着,我们需要多次创建并销毁线程。而创建并销毁线程的过程势必会消耗内存。而在Java中,内存资源是及其宝贵的,所以,我们就提出了线程池的概念。

参数

  • corePoolSize:代表核心线程数,一种常驻线程
  • maxinumPoolSize:代表的最大线程数
  • keepAliveTime、unit:表示超出核心线程数以外的线程的空闲存活时间
  • workQueue:用来存放待执行的任务,在核心线程数已满的情况下,再进入的任务会进入队列,当队列满了之后才会创建新的线程
  • ThreadFactory:一个线程工厂,用来生产线程执行任务
  • Handler:拒绝策略,一种是线程池已经关闭,另一种就是当达到最大线程数

Executors:工具类,线程池的工厂类,用于创建并返回不同类型的线程池
ExecutorService:真正的线程池接口,常见子类ThreadPoolExecutor

ExecutorService是Java提供的用于管理线程池的类。该类的两个作用:控制线程数量和重用线程;具体的4种常用的线程池:

  1. Executors.newCacheThreadPool():可缓存线程池,先查看池中有没有以前建立的线程,如果有,就直接使用。如果没有,就建一个新的线程加入池中,缓存型池子通常用于执行一些生存期很短的异步型任务
  2. Executors.newFixedThreadPool(int n):创建一个可重用固定个数的线程池,以共享的无界队列方式来运行这些线程
  3. Executors.newScheduledThreadPool(int n):创建一个定长线程池,支持定时及周期性任务执行
  4. Executors.newSingleThreadExecutor():创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行

AQS: 队列同步器,是用来构建锁或者其他同步组件的基础框架,它使用了一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作。基于volatile和CAS来实现
队列节点有四种状态

  • Cancled:表示线程因为超时或者中断原因被放入队列
  • Condition:需要等待一个条件才能出队
  • Signal:表示该线程需要被唤醒
  • Propagate:表示在共享模式下,当前节点执行释放操作后,当前节点需要传播通知给后面所有的节点

模式

  • 独占模式,ReentranLock
  • 共享模式,CountDownLatch

同步组件利用同步器进行锁的实现,它简化了锁的实现方式,屏蔽了同步状态管理、线程的排队、等待与唤醒等顶层操作,同步器提供了3个方法来修改和访问同步状态:

  1. getState():获取当前同步状态
  2. setState(int newState):设置当前同步状态
  3. compareAndSetState(int expect, int update):使用CAS设置当前状态,该方法能保证操作的原子性。

Runnable和Callable的区别和联系

  • 1)Runnable提供run方法,无法通过throws抛出异常,所有CheckedException必须在run方法内部处理。Callable提供call方法,直接抛出Exception异常。
  • 2)Runnable的run方法无返回值,Callable的call方法提供返回值用来表示任务运行的结果
  • 3)Runnable可以作为Thread构造器的参数,通过开启新的线程来执行,也可以通过线程池来执行。而Callable只能通过线程池执行。

线程协作

生产者消费者问题:管程法、信号灯法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

寅贝勒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值