目录
1.J.U.C
- java并发包java.util.concurrent,简称J.U.C
- 按照功能大致划分
- juc-locks 锁框架
- juc-atomic 原子类框架
- juc-sync 同步器框架、工具类
- juc-collections 集合框架
- juc-executors 执行器框架
2.线程执行情况
- jps:查看java程序进程id信息
- jstack:查看指定进程栈信息
- jstack+进程id
- 如何避免死锁
- 避免一个线程同时获取多个锁。
- 避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。
- 尝试使用定时锁,使用lock .tryLock (timeout )来替代使用内部锁机制。
- 上下文切换:CPU 通过时间片分配算法来循环执行任务,所以任务从保存到再加载的过程就是一次上下文切换
- 减少上下文切换
- 无锁并发编程:多线程竞争锁时,会引起上下文切换,所以多线程处理数据时,可以用一些办法来避免使用锁,如将数据的ID按照Hash 算法取模分段,不同的线程处理不同段的数据
- CAS算法:Java的Atomic包使用CAS算法来更新数据,而不需要加锁
- 使用最少线程:避免创建不需要的线程,比如任务很少,但是创建了很多线程来处理,这样会造成大量线程都处于等待状态
- 使用协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换
3.JVM内存模型(JMM)
- JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方称为栈空间),用于存储线程私有的数据。而Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问
- 线程对变量的操作(读取赋值等)必须在工作内存中进行
- 首先要将变量从主内存拷贝的自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,工作内存中存储着主内存中的变量副本拷贝
4.Java并发编程三大特性
- 原子性:相关操作不会中途被其他线程干扰,一般通过同步机制实现。
- synchronized关键字保证原子性
- 可见性:一个线程修改了某个共享变量,其状态能够立即被其他线程知晓,通常被解释为将线程本地状态反映到主内存上,volatile就是负责保证可见性的。
- 使用volatile保证多线程场景下的可见性
- 有序性:保证线程内串行语义,避免指令重排。
- 有序就是要保证代码按照既定的顺序依次执行。但是CPU(或编译器)出于性能优化的目的,在保证不会对程序运行结果产生影响的前提下,代码的执行顺序可能会和我们既定的顺序不一致
- 在关键字上有volatile和synchronized可以禁用重排序
5.volatile关键词
- 轻量级的synchronized,用以实现变量的可见性
- 如果一个字段被声明成volatile,Java线程内存模型确保所有线程看到这个变量的值是一致的
6.等待唤醒方法(配合synchronized使用)
- wait() 等待,使当前线程阻塞
- 当线程执行wait()方法时候,会释放当前的锁,然后让出CPU,进入等待状态
- wait() 需要被try catch包围,以便发生异常中断也可以使wait等待的线程唤醒
- wait(long timeout)
- notity()、notityAll() 唤醒
- notify(),唤醒正在等待的单个线程,选择哪个线程取决于操作系统对多线程管理的实现
- notifyAll(),唤醒所有等待线程,哪一个线程将会第一个处理取决于操作系统的实现
- 注意: wait和notify必须是在同步代码块中,使用锁对象调用
- notify 和wait 的顺序不能错
- 为什么wait和notify方法放在Object
- 因为wait和notify需要使用锁对象来调用,而任何对象都可以作为锁,所以放在Object类中
- sleep和wait的区别
- sleep睡眠的时候不会释放锁
- wait等待的时候会释放锁
7.线程状态
- NEW
- 尚未启动的线程处于此状态。
- RUNNABLE
- 在Java虚拟机中执行的线程处于此状态。
- BLOCKED
- 被阻塞等待监视器锁定的线程处于此状态。
- WAITING
- 正在等待另一个线程执行特定动作的线程处于此状态。
- TIMED_WAITING
- 正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
- TERMINATED
- 已退出的线程处于此状态。
8.join与yield、sleep区别
- join
- t.join()方法 把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程
- 线程调用了join方法,那么就要一直运行到该线程运行结束,才会运行其他进程
- 内部使用了synchronized,会占用锁,线程结束,锁释放
- join(long millis)
- 如果为0表示永远等待,其实是等到t结束后。A timeout of {@code 0} means to wait forever.
- 传入指定的时间会调用wait(millis), 时间到锁释放。不再等待
- yield
- thread.yield() 让CPU的时间片尽量切换其他线程去执行,作用不大
- 使正在运行中的线程重新变成就绪状态,并重新竞争 CPU 的调度权。它可能会获取到,也有可能被其他线程获取到
- yield和sleep的区别
1)优先级:sleep暂停线程后,会给其他线程执行机会,不考虑线程的优先级问题;yield暂停后只有优先级高于或等于当前线程的线程才有执行机会。
2)状态:sleep当前线程由运行态进入阻塞态;yield使当前线程由运行态到就绪态。
3)异常:sleep方法在声明时抛出InterruptedException异常,所以在使用时要么try捕获要么throws抛出;而yield没有声明异常。
4)移植性:sleep的移植性更好。
- 线程优先级
- 范围1-10,默认为5
- 线程的优先级说明在程序中该线程的重要性。系统会根据优先级决定首先使用哪个线程,但这并不意味着优先级低的线程得不到运行
- t1.setPriority(Thread.MAX_PRIORITY)
9.中断线程方法
- interrupt()
- 调用该方法的线程的状态将被置为中断状态
- 注意:线程中断仅仅是置线程的中断状态位,不会停止线程
- intterupted()
- 静态方法,查询当前线程的中断状态,并且清除原状态
- 如果一个线程被中断了,第一次调用 #interrupted() 方法则返回 true ,第二次和后面的就返回 false 了
- isInterrupted()
- 查询线程的中断状态,不会清除原状态
- 安全终止线程方法
- 使用标识状态与boolean类型变量一起判断实现
10.synchronized关键词
- Synchronized通过对象内部的一个叫==监视器锁==来实现的,但是监视器锁本质又是依赖底层的操作系统的Mutex(互斥) Lock来实现
- 操作系统实现线程之间的切换会造成大量的CPU资源浪费,状态之间的转换需要相对比较长的时间,这就是为什么Synchronized效率低的原因,因此这种依赖于操作系统Mutex Lock所实现的锁我们称之为:==重量级锁==
- jdk1.5后引入偏向锁和轻量级锁进行优化
- 三种用法
- 对象锁
- 当使用synchronized修饰类普通方法时,那么当前加锁的级别就是实例对象,当多个线程并发访问该对象的同步方法、同步代码块时,会进行同步。
- 类锁
- 当使用synchronized修饰类静态方法时,那么当前加锁的级别就是类,当多个线程并发访问该类(所有实例对象)的同步方法以及同步代码块时,会进行同步。
- 同步代码块
- 当使用synchronized修饰代码块时,那么当前加锁的级别就是synchronized(X)中配置的x对象实例,当多个线程并发访问该对象的同步方法、同步代码块以及当前的代码块时,会进行同步。
- 使用同步代码块时要注意的是不要使用String类型对象,因为String常量池的存在,所以很容易导致出问题。
- 对象锁
- 同步代码块
- 语法:
synchronized(锁对象){
线程安全问题代码
}
- 哪些对象可以作为锁
- 任意对象
- 注意事项
- 多线程并发方法同步代码块,需要锁同一个对象
- synchronized中的锁的作用
- 同步代码块中有锁的线程进入,无锁的线程需要等待
- 同步方法
- 普通同步方法,对当前一个实例对象加锁,多线程操作统一个对象实例进行同步操作
- 静态同步方法,对类加锁,多线程操作当前类所有实例对象进行同步操作
11.ReentranLock可重入锁
- 对比synchronized
- synchronized是关键字,lock是接口
- synchronized 是内部锁,自动化的上锁与释放锁,而lock是手动的,需要人为的上锁和释放锁,lock比较灵活,但是代码相对较多
- lock接口异常的时候不会自动的释放锁,同样需要手动的释放锁,所以一般写在finally语句块中,而synchronized则会在异常的时候自动的释放锁
- lock可以提高多个线程进行读操作的效率。(可以通过readwritelock实现读写分离)
- 通过lock可以知道有没有成功获取锁
- lock可以通过tryLock(timeout)让等待锁的线程超时响应中断,而synchronized却不行
- 性能上来说,在资源竞争不激烈的情形下,Lock性能稍微比synchronized差点
- 但是当同步非常激烈的时候,synchronized的性能就会下降几十倍。而ReentrantLock确还能维持常态。
-
- 可选择实现公平锁或非公平锁(默认非公平锁)
- 公平锁底层由AQS实现(面试问)
- API方法
- lock()
- 获取锁,如果所被其他线程获取,处于等待状态。必须主动释放锁,通常Lock操作早于try catch进行,在finally中释放锁,防止死锁发生
- tryLock()
- 有返回值,表示尝试获取锁,如果成功,返回true,失败返回false(锁已经被别的线程获取)。优点:获取不到锁不会一直等待
- tryLock(Long time,TimeUnit unit)
- 在等待时间内获取到锁返回true,超时返回false
- unlock()
- 释放锁,在finally中执行
- lockInterruptibly()
- 通过这个这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。
- lock()
- LockSupport
- 构建同步组件的基础工具,提供基本的线程阻塞和唤醒功能
- LockSupport的park和unpark方法都是通过sun.misc.Unsafe类的park和unpark方法实现的
- 与wait、notify区别
- park不需要获取某个对象的锁
- 因为中断的时候park不会抛出InterruptedException异常,所以需要在park之后自行判断中断状态,然后做额外的处理
- park和unpark可以实现类似wait和notify的功能,但是并不和wait和notify交叉,也就是说unpark不会对wait起作用,notify也不会对park起作用。
- unpark可以唤醒特定线程
- AQS队列同步器AbstractQueuedSynchronizer
- 来构建锁或者其他同步组件的基础框架,它使用了一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作
- 底层结构:双向链表
- 队列中的线程实现等待唤醒:通过LockSupport
- AQS定义两种资源共享方式:Exclusive(独占,只有一个线程能执行,如ReentrantLock)和Share(共享,多个线程可同时执行,如Semaphore/CountDownLatch
- Condition
- 将Object类的wait、notify、notifyAll等操作转化为相应的条件对象操作await、signal、signalAll,将复杂的同步操作转变为直观可控的对象行为
- 读写锁ReentrantReadWriteLock
- 一个用来获取读锁,一个用来获取写锁,将文件的读写操作分开
- 读锁使用共享锁
- 写锁使用独占锁(排他锁)
- 优势
- 大大提升读效率
- 适用于读多写少的情景
- 注意
- 如果有一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写锁的线程会一直等待释放读锁
- 如果有一个线程已经占用了写锁,则此时其他线程如果申请写锁或者读锁,则申请的线程会一直等待释放写锁
- 一个用来获取读锁,一个用来获取写锁,将文件的读写操作分开