什么是JUC
并发编程:
// 获取CPU的核数
// CPU 密集型,IO 密集型
System.out.println( Runtime.getRuntime( ) .availableProcessors( )) ;
1 . NEW 新生
2 . RUNNABLE 运行
3 . BLOCKED 阻塞
4 . WAITING 等待,死等
5 . TIMED_WAITING 超时等待
6 . TERMINATED 终止
1 . 来自不同的类:wait:object、sleep:Thread
2 . 关于锁的释放:wait会释放锁,sleep不释放锁
3 . 使用的范围不同:wait必须在同步块中,sleep可以在任何地方
4 . 是否需要捕获异常:wait不需要捕获,sleep需要捕获异常
Lock锁:
// 传统 synchronized 实现线程同步
public class SynchronizedClass {
public static void main( String[ ] args) {
Ticket ticket = new Ticket( ) ;
new Thread(( ) -> {
for ( int i = 0 ; i< 40 ; i++) {
ticket.sale( ) ;
}
} , "A" ) .start( ) ;
new Thread(( ) -> {
for ( int i = 0 ; i< 50 ; i++) {
ticket.sale( ) ;
}
} , "B" ) .start( ) ;
new Thread(( ) -> {
for ( int i = 0 ; i< 60 ; i++) {
ticket.sale( ) ;
}
} , "C" ) .start( ) ;
}
}
class Ticket{
private int number = 30 ;
public synchronized void sale ( ) {
if( number > 0 ) {
System.out.println( Thread.currentThread( ) .getName( ) + "卖出了: " + number-- + ", 剩余: " + number) ;
}
}
}
/ Lock 实现线程同步
public class SynchronizedClass {
public static void main( String[ ] args) {
Ticket ticket = new Ticket( ) ;
new Thread(( ) -> { for ( int i = 0 ; i< 40 ; i++) ticket.sale( ) ; } , "A" ) .start( ) ;
new Thread(( ) -> { for ( int i = 0 ; i< 50 ; i++) ticket.sale( ) ; } , "B" ) .start( ) ;
new Thread(( ) -> { for ( int i = 0 ; i< 60 ; i++) ticket.sale( ) ; } , "C" ) .start( ) ;
}
}
class Ticket{
private int number = 30 ;
Lock lock = new ReentrantLock( ) ;
public void sale ( ) {
lock.lock( ) ;
try{
if( number > 0 ) {
System.out.println( Thread.currentThread( ) .getName( ) + "卖出了: " + number-- + ", 剩余: " + number) ;
}
} catch ( Exception e) {
e.printStackTrace( ) ;
} finally {
lock.unlock( ) ;
}
}
}
1 . synchronized 是内置的java关键字;Lock 是一个接口( java类)
2 . synchronized 无法判断获取锁的状态;Lock 可以判断是否获取到了锁
3 . synchronized 会自动释放锁;Lock必须手动释放锁,如果不释放:死锁
4 . synchronized 如果一个线程获得锁阻塞了,后续的线程都会等待;Lock不一定会等待( tryLock( ))
5 . synchronized 可重入锁,不可以中断,非公平;Lock,可重入锁,可以判断锁,非公平( 可设置)
6 . synchronized 适合锁少量的代码同步问题;Lock 适合锁大量的同步代码
线程虚假唤醒:
// 传统生产者消费者问题
public class OneTest {
public static void main( String[ ] args) {
Data data = new Data( ) ;
new Thread(( ) -> {
try {
for ( int i = 0 ; i < 10 ; i++) data.increment( ) ;
} catch ( InterruptedException e) {
e.printStackTrace( ) ;
}
} , "A" ) .start( ) ;
new Thread(( ) -> {
try {
for ( int i = 0 ; i < 10 ; i++) data.increment( ) ;
} catch ( InterruptedException e) {
e.printStackTrace( ) ;
}
} , "B" ) .start( ) ;
new Thread(( ) -> {
try {
for ( int i = 0 ; i < 10 ; i++) data.decrement( ) ;
} catch ( InterruptedException e) {
e.printStackTrace( ) ;
}
} , "C" ) .start( ) ;
new Thread(( ) -> {
try {
for ( int i = 0 ; i < 10 ; i++) data.decrement( ) ;
} catch ( InterruptedException e) {
e.printStackTrace( ) ;
}
} , "D" ) .start( ) ;
}
}
class Data{
private int number = 0 ;
public synchronized void increment( ) throws InterruptedException {
while ( number != 0 ) {
this.wait( ) ;
}
number++;
System.out.println( Thread.currentThread( ) .getName( ) + "->" + number) ;
this.notifyAll( ) ;
}
public synchronized void decrement( ) throws InterruptedException {
while( number == 0 ) {
this.wait( ) ;
}
number--;
System.out.println( Thread.currentThread( ) .getName( ) + "->" + number) ;
this.notifyAll( ) ;
}
}
Condition:
public class OneTest {
public static void main( String[ ] args) {
Data data = new Data( ) ;
new Thread(( ) -> {
try {
for ( int i = 0 ; i < 10 ; i++) data.increment( ) ;
} catch ( InterruptedException e) {
e.printStackTrace( ) ;
}
} , "A" ) .start( ) ;
new Thread(( ) -> {
try {
for ( int i = 0 ; i < 10 ; i++) data.increment( ) ;
} catch ( InterruptedException e) {
e.printStackTrace( ) ;
}
} , "B" ) .start( ) ;
new Thread(( ) -> {
try {
for ( int i = 0 ; i < 10 ; i++) data.decrement( ) ;
} catch ( InterruptedException e) {
e.printStackTrace( ) ;
}
} , "C" ) .start( ) ;
new Thread(( ) -> {
try {
for ( int i = 0 ; i < 10 ; i++) data.decrement( ) ;
} catch ( InterruptedException e) {
e.printStackTrace( ) ;
}
} , "D" ) .start( ) ;
}
}
class Data{
private int number = 0 ;
Lock lock = new ReentrantLock( ) ;
Condition condition = lock.newCondition( ) ;
public void increment( ) throws InterruptedException {
lock.lock( ) ;
try{
while ( number != 0 ) {
condition.await( ) ; ;
}
number++;
System.out.println( Thread.currentThread( ) .getName( ) + "->" + number) ;
condition.signalAll( ) ;
} catch ( Exception e) {
e.printStackTrace( ) ;
} finally {
lock.unlock( ) ;
}
}
public void decrement( ) throws InterruptedException {
lock.lock( ) ;
try{
while ( number == 0 ) {
condition.await( ) ; ;
}
number--;
System.out.println( Thread.currentThread( ) .getName( ) + "->" + number) ;
condition.signalAll( ) ;
} catch ( Exception e) {
e.printStackTrace( ) ;
} finally {
lock.unlock( ) ;
}
}
}
public class OneTest {
public static void main( String[ ] args) {
Data data = new Data( ) ;
new Thread(( ) -> { for ( int i = 0 ; i < 10 ; i++) data.printA( ) ; } , "A" ) .start( ) ;
new Thread(( ) -> { for ( int i = 0 ; i < 10 ; i++) data.printB( ) ; } , "B" ) .start( ) ;
new Thread(( ) -> { for ( int i = 0 ; i < 10 ; i++) data.printC( ) ; } , "C" ) .start( ) ;
}
}
class Data{
private int number = 1 ;
Lock lock = new ReentrantLock( ) ;
Condition condition1 = lock.newCondition( ) ;
Condition condition2 = lock.newCondition( ) ;
Condition condition3 = lock.newCondition( ) ;
public void printA ( ) {
lock.lock( ) ;
try{
while ( number != 1 ) {
condition1.await( ) ;
}
number = 2 ;
System.out.println( Thread.currentThread( ) .getName( ) + "->" + "AAAAAAAAAAB" ) ;
condition2.signal( ) ;
} catch ( Exception e) {
e.printStackTrace( ) ;
} finally {
lock.unlock( ) ;
}
}
public void printB ( ) {
lock.lock( ) ;
try{
while ( number != 2 ) {
condition2.await( ) ;
}
number = 3 ;
System.out.println( Thread.currentThread( ) .getName( ) + "->" + "BBBBBBBBBBC" ) ;
condition3.signal( ) ;
} catch ( Exception e) {
e.printStackTrace( ) ;
} finally {
lock.unlock( ) ;
}
}
public void printC ( ) {
lock.lock( ) ;
try{
while ( number != 3 ) {
condition3.await( ) ;
}
number = 1 ;
System.out.println( Thread.currentThread( ) .getName( ) + "->" + "CCCCCCCCCCA" ) ;
condition1.signal( ) ;
} catch ( Exception e) {
e.printStackTrace( ) ;
} finally {
lock.unlock( ) ;
}
}
}
八锁现象:
1 . synchronized 锁的是调用的对象 new Object( ) ;
2 . static 修饰后,锁的就是调用的对象类 Class
public class LockTest {
public static void main( String[ ] args) throws InterruptedException {
A a = new A( ) ;
new Thread(( ) - > {
try {
a.sendMsg( ) ;
} catch ( InterruptedException e) {
e.printStackTrace( ) ;
}
}) .start( ) ;
TimeUnit.SECONDS.sleep( 1 ) ;
new Thread(( ) - > a.hello( )) .start( ) ;
}
}
class A{
public synchronized void sendMsg( ) throws InterruptedException {
Thread.sleep( 4000 ) ;
System.out.println( "发短信" ) ;
}
public synchronized void callPhone ( ) {
System.out.println( "打电话" ) ;
}
public void hello ( ) {
System.out.println( "hello" ) ;
}
}
集合类不安全:
// ConcurrentModificationException 并发修改异常
public class ListTest {
public static void main( String[ ] args) {
// 并发下 ArrayList不安全
/**
* 解决方案
* 1 . new Vector<> ( ) ; Vector.add 方法有synchronized修饰
* 2 . Collections.synchronizedList( new ArrayList<> ( )) ;
* 3 . new CopyOnWriteArrayList<> ( ) ; JUC工具类方法
**/
// CopyOnWrite 写入时复制 COW 计算机程序设计领域的优化策略
// 1 和 3 比较:Vector synchronized的性能问题
List< String> list = new CopyOnWriteArrayList<> ( ) ;
for ( int i = 0 ; i < = 10 ; i++) {
new Thread(( ) - > {
list.add( UUID.randomUUID( ) .toString( ) .substring( 0 , 5 )) ;
System.out.println( list) ;
} ) .start( ) ;
}
}
}
// ConcurrentModificationException 并发修改异常
public class SetTest {
public static void main( String[ ] args) {
// 并发下 HashSet不安全
/**
* 解决方案
* 1 . Collections.synchronizedSet( new HashSet<> ( )) ;
* 2 . new CopyOnWriteArraySet<> ( ) ; JUC工具类方法
**/
Set< String> set = new CopyOnWriteArraySet<> ( ) ;
for ( int i = 0 ; i < = 30 ; i++) {
new Thread(( ) - > {
set.add( UUID.randomUUID( ) .toString( ) .substring( 0 , 5 )) ;
System.out.println( set) ;
} ) .start( ) ;
}
}
}
hashSet 底层是hashmap,
hashSet 添加值( add) 实际就是往hashmap添加key值,
所以hashset没有重复值!
// ConcurrentModificationException 并发修改异常
public class MapTest {
public static void main( String[ ] args) {
// 并发下 HashMap不安全
/**
* 解决方案
* 1 . new ConcurrentHashMap<> ( ) ;
**/
Map< String, String> map = new ConcurrentHashMap<> ( ) ;
for ( int i = 0 ; i < = 30 ; i++) {
new Thread(( ) - > {
map.put( Thread.currentThread( ) .getName( ) ,
UUID.randomUUID( ) .toString( ) .substring( 0 , 5 )) ;
System.out.println( map) ;
} ) .start( ) ;
}
}
}
Callable:
public class CallableTest {
public static void main( String[ ] args) throws ExecutionException, InterruptedException {
CallanbeDemo callanbeDemo = new CallanbeDemo( ) ;
FutureTask futureTask = new FutureTask( callanbeDemo) ;
new Thread( futureTask, "A" ) .start( ) ;
String str = ( String) futureTask.get( ) ;
System.out.println( str) ;
}
}
class CallanbeDemo implements Callable< String> {
@Override
public String call( ) throws Exception {
System.out.println( "调用成功!" ) ;
return "返回的是 String 类型" ;
}
}
get 有缓存,下次可以很快得到结果
get 的 结果可能需要等待,会阻塞!
FutureTask实现了RunnableFuture接口,而RunnableFuture继承了Runnable与Future接口,因此FutureTask既可以作为一个Runnable被Thread执行,也可以获取到Future异步计算的结果。
常用辅助类:
// 作用:CountDownLatch能够使一个线程在等待另外一些线程完成各自工作之后,再继续执行。
// CountDownLatch的不足:CountDownLatch是一次性的,计算器的值只能在构造方法中初始化一次,之后没有任何机制再次对其设置值,当CountDownLatch使用完毕后,它不能再次被使用。
// 原理:
// countDownLatch.countDown( ) 数量 -1
// countDownLatch.await( ) 等待计数器归零再向后执行!
// 每次有线程调用countDownLatch.countDown( ) ,假设计数器变为0,那么countDownLatch.await( ) 就 会被唤醒,继续执行!
// 倒计时计算器
public class CountDownLatchDemo {
public static void main( String[ ] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch( 5 ) ;
for ( int i = 0 ; i < = 4 ; i++) {
new Thread(( ) - > {
System.out.println( Thread.currentThread( ) .getName( ) + " Get Out!") ;
countDownLatch.countDown( ) ;
}) .start( ) ;
}
countDownLatch.await( ) ;
System.out.println( "Close Door") ;
}
}
# CyclicBarrier:( 加法计数器)
/ / 作用:让所有线程都等待完成后才会继续下一步行动。
/ / 使用场景:可以用于多线程计算数据,最后合并计算结果的场景。
/ / 加法计数器
public class CyclicBarrierDemo {
public static void main( String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier( 7 , ( ) - > {
System.out.println( "线程数到了一定量,执行了此线程!!!") ;
}) ;
for ( int i = 0 ; i < 7 ; i++ ) {
final int temp = i;
new Thread(( ) - > {
System.out.println( Thread.currentThread( ) .getName( ) + " " + temp + "") ;
try {
cyclicBarrier.await( ) ;
} catch ( InterruptedException e) {
e.printStackTrace( ) ;
} catch ( BrokenBarrierException e) {
e.printStackTrace( ) ;
}
}) .start( ) ;
}
}
}
# CyclicBarrier 与 CountDownLatch 区别
1. CountDownLatch 是一次性的,CyclicBarrier 是可循环利用的
2. CountDownLatch 参与的线程的职责是不一样的,有的在倒计时,有的在等待倒计时结束。 CyclicBarrier 参与的线程职责是一样的。
# Semaphore:( 信号量)
/ / 原理:
/ / acquire( ) 获得,假设已经满了,等待,等待被释放为止!
/ / release( ) 释放,会将当前的信号量释放 + 1 ,然后唤醒等待的线程!
/ / 作用:多个共享资源互斥的使用!并发限流,控制最大的线程数!
public class SemaphoreDemo {
public static void main( String[] args) {
Semaphore semaphore = new Semaphore( 3 ) ;
for ( int i = 0 ; i < 9 ; i++ ) {
new Thread(( ) - > {
try {
semaphore.acquire( ) ; / / 得到
System.out.println( Thread.currentThread( ) .getName( ) + " 抢到车位!") ;
TimeUnit.SECONDS.sleep( 2 ) ;
System.out.println( Thread.currentThread( ) .getName( ) + " 离开车位!") ;
} catch ( InterruptedException e) {
e.printStackTrace( ) ;
}finally {
semaphore.release( ) ; / / 释放
}
}, String.valueOf( i)) .start( ) ;
}
}
}
读写锁:
1 . Java并发库中 ReetrantReadWriteLock 实现了 ReadWriteLock 接口并添加了可重入的特性
2 . ReetrantReadWriteLock读写锁的效率明显高于synchronized关键字
3 . ReetrantReadWriteLock读写锁的实现中,读锁使用共享模式;写锁使用独占模式,换句话说,读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的
4 . ReetrantReadWriteLock读写锁的实现中,需要注意的,当有读锁时,写锁就不能获得;而当有写锁时,除了获得写锁的这个线程可以获得读锁外,其他线程不能获得读锁
// ReadWriteLock:
// 读-读 可以共存,读-写 不能共存,写-写 不能共存
// 独占锁( 写锁) ,共享锁( 写锁)
public class ReadWriteLockDemo {
public static void main( String[ ] args) {
MyCacheLock myCacheLock = new MyCacheLock( ) ;
for ( int i = 0 ; i < 5 ; i++) {
final int temp = i;
new Thread(( ) - > {
myCacheLock.put( temp + "", temp) ;
}, String.valueOf( i)) .start( ) ;
}
for ( int i = 0 ; i < 5 ; i++) {
final int temp = i;
new Thread(( ) - > {
myCacheLock.get( temp + "") ;
}, String.valueOf( i)) .start( ) ;
}
}
}
class MyCacheLock{
private volatile Map< String, Object> map = new HashMap<> ( ) ;
// 读写锁:更加细粒度的控制
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock( ) ;
private Lock lock = new ReentrantLock( ) ;
public void put( String key, Object value) {
readWriteLock.writeLock( ) .lock( ) ;
try{
System.out.println( Thread.currentThread( ) .getName( ) + " 写入" + key) ;
map.put( key, value) ;
System.out.println( Thread.currentThread( ) .getName( ) + "写入OK" ) ;
} catch ( Exception e) {
e.printStackTrace( ) ;
} finally {
readWriteLock.writeLock( ) .unlock( ) ;
}
}
public void get( String key) {
readWriteLock.readLock( ) .lock( ) ;
try {
System.out.println( Thread.currentThread( ) .getName( ) + " 读取" + key) ;
Object o = map.get( key) ;
System.out.println( Thread.currentThread( ) .getName( ) + " 读取OK" ) ;
} catch ( Exception e) {
e.printStackTrace( ) ;
} finally {
readWriteLock.readLock( ) .unlock( ) ;
}
}
}
阻塞队列:
写入时如果队列满了,就必须阻塞等待
取时如果队列是空的,必须阻塞等待生产
四组API:
方式 抛出异常 有返回值,不抛出异常 阻塞 等待 超时等待 添加 add offer() put() offer(,) 移除 remove poll() take() poll(,) 检测队首元素 element peek … …
public class Test {
public static void main( String[ ] args) throws InterruptedException {
// 方式一
// test1( ) ;
// 方式二
// test2( ) ;
// 方式三
// test3( ) ;
// 方式四
test4( ) ;
}
// 添加超出范围:java.lang.IllegalStateException: Queue full
// 移除超出范围:java.util.NoSuchElementException
public static void test1 ( ) {
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue( 3 ) ;
System.out.println( arrayBlockingQueue.add( "A" )) ;
System.out.println( arrayBlockingQueue.add( "B" )) ;
System.out.println( arrayBlockingQueue.add( "C" )) ;
// System.out.println( arrayBlockingQueue.add( "D" )) ;
System.out.println( arrayBlockingQueue.remove( )) ;
System.out.println( arrayBlockingQueue.remove( )) ;
System.out.println( arrayBlockingQueue.remove( )) ;
// System.out.println( arrayBlockingQueue.remove( )) ;
}
// 添加超出范围:返回false
// 移除超出范围:返回null
public static void test2 ( ) {
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue( 3 ) ;
System.out.println( arrayBlockingQueue.offer( "A" )) ;
System.out.println( arrayBlockingQueue.offer( "B" )) ;
System.out.println( arrayBlockingQueue.offer( "C" )) ;
// System.out.println( arrayBlockingQueue.offer( "D" )) ;
System.out.println( arrayBlockingQueue.poll( )) ;
System.out.println( arrayBlockingQueue.poll( )) ;
System.out.println( arrayBlockingQueue.poll( )) ;
// System.out.println( arrayBlockingQueue.poll( )) ;
}
// 添加超出范围:阻塞,一直等待
// 移除超出范围:阻塞,一直等待
public static void test3( ) throws InterruptedException {
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue( 3 ) ;
arrayBlockingQueue.put( "A" ) ;
arrayBlockingQueue.put( "B" ) ;
arrayBlockingQueue.put( "C" ) ;
// arrayBlockingQueue.put( "D" ) ;
System.out.println( arrayBlockingQueue.take( )) ;
System.out.println( arrayBlockingQueue.take( )) ;
System.out.println( arrayBlockingQueue.take( )) ;
// System.out.println( arrayBlockingQueue.take( )) ;
}
// 添加超出范围:超时等待,如果没有位置则返回 false
// 移除超出范围:超时等待,如果没有移除东西则返回 null
public static void test4( ) throws InterruptedException {
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue( 3 ) ;
System.out.println( arrayBlockingQueue.offer( "A" )) ;
System.out.println( arrayBlockingQueue.offer( "B" )) ;
System.out.println( arrayBlockingQueue.offer( "C" )) ;
// System.out.println( arrayBlockingQueue.offer( "D" , 2 , TimeUnit.SECONDS )) ;
System.out.println( arrayBlockingQueue.poll( )) ;
System.out.println( arrayBlockingQueue.poll( )) ;
System.out.println( arrayBlockingQueue.poll( )) ;
// System.out.println( arrayBlockingQueue.poll( 2 , TimeUnit.SECONDS )) ;
}
}
// 只能存在一个值,移除后才能加入值
public class SynchronousQueueDeom {
public static void main( String[ ] args) {
// 同步队列
BlockingQueue blockingQueue = new SynchronousQueue( ) ;
new Thread(( ) - > {
try {
System.out.println( Thread.currentThread( ) .getName( ) + " put 1 ") ;
blockingQueue.put( "1 ") ;
System.out.println( Thread.currentThread( ) .getName( ) + " put 2 ") ;
blockingQueue.put( "2 ") ;
System.out.println( Thread.currentThread( ) .getName( ) + " put 3 ") ;
blockingQueue.put( "3 ") ;
}catch ( Exception e) {
e.printStackTrace( ) ;
}
}, "T1") .start( ) ;
new Thread(( ) - > {
try {
TimeUnit.SECONDS.sleep( 3 ) ;
System.out.println( Thread.currentThread( ) .getName( ) + " - > " + blockingQueue.take( )) ;
TimeUnit.SECONDS .sleep( 3 ) ;
System.out.println( Thread.currentThread( ) .getName( ) + " -> " + blockingQueue.take( )) ;
TimeUnit.SECONDS .sleep( 3 ) ;
System.out.println( Thread.currentThread( ) .getName( ) + " -> " + blockingQueue.take( )) ;
} catch ( Exception e) {
e.printStackTrace( ) ;
}
} , "T2" ) .start( ) ;
}
}
线程池(重点):
// Executors 工具类 三大方法
public class Demo01 {
public static void main( String[ ] args) {
ExecutorService threadPool = Executors.newSingleThreadExecutor( ) ;
// ExecutorService threadPool = Executors.newFixedThreadPool( 5 ) ;
// ExecutorService threadPool = Executors.newCachedThreadPool( ) ;
try {
for ( int i = 0 ; i < 100 ; i++) {
threadPool.execute(( ) -> {
System.out.println( Thread.currentThread( ) .getName( ) + " OK" ) ;
} ) ;
}
} catch ( Exception e) {
e.printStackTrace( ) ;
} finally {
// 线程池用完,程序结束,关闭线程池
threadPool.shutdown( ) ;
}
}
}
1 . corePoolSize(线程池基本大小):当向线程池提交一个任务时,若线程池已创建的线程数小于 corePoolSize,即便此时存在空闲线程,也会通过创建一个新线程来执行该任务,直到已创建的线程数大于 或等于corePoolSize时,(除了利用提交新任务来创建和启动线程(按需构造),也可以通过 prestartCoreThread( ) 或 prestartAllCoreThreads( ) 方法来提前启动线程池中的基本线程。)
2 . maximumPoolSize(线程池最大大小):线程池所允许的最大线程个数。当队列满了,且已创建的线程数小于 maximumPoolSize,则线程池会创建新的线程来执行任务。另外,对于无界队列,可忽略该参数。
3 . keepAliveTime(线程存活保持时间)当线程池中线程数大于核心线程数时,线程的空闲时间如果超过线程存 活时间,那么这个线程就会被销毁,直到线程池中的线程数小于等于核心线程数。
4 . unit:TimeUnit. 设置时间的单位( 为keepAliveTime服务的单位) 。
5 . workQueue(任务队列):用于传输和保存等待执行任务的阻塞队列。
6 . threadFactory(线程工厂):用于创建新线程。threadFactory创建的线程也是采用new Thread( ) 方式 threadFactory创建的线程名都具有统一的风格:pool-m-thread-n(m为线程池的编号,n为线程池内的线 程编号)。
7 . handler(线程饱和策略):当线程池和队列都满了,再加入线程会执行此策略。
public class Demo02 {
public static void main( String[ ] args) {
// 自定义线程池
ExecutorService threadPool = new ThreadPoolExecutor(
2 ,
5 ,
3 ,
TimeUnit.SECONDS ,
new LinkedBlockingQueue<> ( 3 ) ,
Executors.defaultThreadFactory( ) ,
new ThreadPoolExecutor.AbortPolicy( )
) ;
try{
//
for ( int i = 1 ; i < = 9 ; i++) {
// 使用线程池来创建线程
threadPool.execute(( ) -> {
System.out.println( Thread.currentThread( ) .getName( ) + " OK" ) ;
} ) ;
}
} catch ( Exception e) {
e.printStackTrace( ) ;
} finally {
//
threadPool.shutdown( ) ;
}
}
}
1 . new ThreadPoolExecutor.AbortPolicy( ) : 队列和线程池满了还有线程进入会直接抛出异常
2 . new ThreadPoolExecutor.CallerRunsPolicy( ) : main线程会去处理
3 . new ThreadPoolExecutor.DiscardPolicy( ) 队列和线程池满了还有线程进入不抛出异常
4 . new ThreadPoolExecutor.DiscardOldestPolicy( ) 队列满了,尝试和最早的竞争,失败不抛出异常
CPU密集型任务:尽量使用较小的线程池,一般为CPU核心数+1。 因为CPU密集型任务使得CPU使用率很高,若开过 多的线程数,会造成CPU过度切换。
IO密集型任务:可以使用稍大的线程池,一般为2*CPU核心数。 IO密集型任务CPU使用率并不高,因此可以让CPU 在等待IO的时候有其他线程去处理别的任务,充分利用CPU时间。
混合型任务:可以将任务分成IO密集型和CPU密集型任务,然后分别用不同的线程池去处理。 只要分完之后两个任务 的执行时间相差不大,那么就会比串行执行来的高效。
四大函数式接口:
public class Demo01 {
public static void main( String[ ] args) {
// Function< String, String> function = new Function< String, String> ( ) {
// @Override
// public String apply( String s) {
// return s;
// }
// } ;
Function< String, String> function = str -> {
return str;
} ;
System.out.println( function.apply( "33" )) ;
}
}
public class Demo02 {
public static void main( String[ ] args) {
// 判断字符串是否为空
// Predicate< String> predicate = new Predicate< String> ( ) {
@Override
public boolean test( String str) {
return str.isEmpty( ) ;
}
} ;
Predicate< String> predicate = str -> {
return str.isEmpty( ) ;
} ;
System.out.println( predicate.test( "" )) ;
}
}
public class Demo03 {
public static void main( String[ ] args) {
// Consumer< String> consumer = new Consumer< String> ( ) {
// @Override
// public void accept( String str) {
// System.out.println( str) ;
// }
// } ;
Consumer< String> consumer = ( str) -> {
System.out.println( str) ;
} ;
consumer.accept( "消费型接口!" ) ;
}
}
public class Demo04 {
public static void main( String[ ] args) {
// Supplier< Integer> supplier = new Supplier< Integer> ( ) {
// @Override
// public Integer get ( ) {
// return 1 ;
// }
// } ;
Supplier< Integer> supplier = ( ) -> { return 1 ; } ;
System.out.println( supplier.get( )) ;
}
}
Stream流式计算:
// 例如下:
// 1 . ID 必须是偶数
// 2 . 年龄大于22岁
// 3 . 用户名转为大写
// 4 . 用户名字母倒序
// 5 . 只输出一个用户!
public class Test01 {
public static void main( String[ ] args) {
User u1 = new User( 1 , "a" , 21 ) ;
User u2 = new User( 2 , "b" , 22 ) ;
User u3 = new User( 3 , "c" , 23 ) ;
User u4 = new User( 4 , "d" , 24 ) ;
User u5 = new User( 5 , "e" , 25 ) ;
User u6 = new User( 6 , "f" , 26 ) ;
//
List< User> list = Arrays.asList( u1, u2, u3, u4, u5, u6) ;
// 计算交给 Stream 流
//
list.stream( )
.filter( u -> u.getId( ) %2 == 0 )
.filter( u -> u.getAge( ) > 22 )
.map( u -> u.getName( ) .toUpperCase( ))
.sorted(( uu1, uu2) -> { return uu2.compareTo( uu1) ; } )
.limit( 1 )
.forEach( System.out :: println) ;
}
}
@Data
@AllArgsConstructor // 全参构造
@NoArgsConstructor // 无参构造
class User{
private int id ;
private String name;
private int age;
}
ForkJoin:
1 . ForkJoinPool 的每个工作线程都维护着一个工作队列(WorkQueue),这是一个双端队列(Deque),里面存放的对象是任务(ForkJoinTask)。
2 . 每个工作线程在运行中产生新的任务(通常是因为调用了 fork( ) )时,会放入工作队列的队首,并且工作线程在处理自己的工作队列时,使用的是 FIFO 方式,也就是说每次从队首取出任务来执行。
3 . 每个工作线程在处理自己的工作队列同时,会尝试窃取一个任务(或是来自于刚刚提交到 pool 的任务,或是来自于其他工作线程的工作队列),窃取的任务位于其他线程的工作队列的队尾,也就是说工作线程在窃取其他工作线程的任务时,使用的是 LIFO 方式。
4 . 在遇到 join( ) 时,如果需要 join 的任务尚未完成,则会先处理其他任务,并等待其完成。
5 . 在既没有自己的任务,也没有可以窃取的任务时,进入休眠。
// 求和:1 至 10_0000_0000
public class ForkJoinDemo1 {
public static void main( String[ ] args) throws ExecutionException, InterruptedException {
test1( ) ; // 3112
test2( ) ;
test3( ) ; // 284
}
public static void test1 ( ) {
long startTime = System.currentTimeMillis( ) ;
Calculator calculator = new Calculator( ) ;
System.out.println( calculator.test1( 1L, 10_0000_0000L)) ;
long endTime = System.currentTimeMillis( ) ;
System.out.println( "所用时间为:" + ( endTime-startTime)) ;
}
public static void test2( ) throws ExecutionException, InterruptedException {
long startTime = System.currentTimeMillis( ) ;
ForkJoinPool forkJoinPool = new ForkJoinPool( ) ;
ForkJoinTask< Long> forkJoinTask = new ForkJoinDemo( 1L, 10_0000_0000L) ;
ForkJoinTask< Long> submit = forkJoinPool.submit( forkJoinTask) ;
System.out.println( submit.get( )) ;
long endTime = System.currentTimeMillis( ) ;
System.out.println( "所用时间为:" + ( endTime-startTime)) ;
}
public static void test3 ( ) {
long startTime = System.currentTimeMillis( ) ;
// Stream并行流
Long sum = LongStream.rangeClosed( 0L, 10_0000_0000L) .parallel( ) .reduce( 0 , Long::sum) ;
System.out.println( sum) ;
long endTime = System.currentTimeMillis( ) ;
System.out.println( "所用时间为:" + ( endTime-startTime)) ;
}
}
class Calculator {
// 一般方法求和
public long test1( Long start, Long end) {
long total = 0 ;
for ( Long i = start; i < = end; i++) {
total += i;
}
return total;
}
}
class ForkJoinDemo extends RecursiveTask< Long> {
private long start;
private long end;
// 临界值
private long temp = 10000 ;
public ForkJoinDemo( long start, long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute ( ) {
if(( end - start) < temp) {
long sum = 0 ;
for ( long i = start; i < = end ; i++) {
sum += i;
}
return sum ;
} else {
long middle = ( start + end) / 2 ; // 中间值
ForkJoinDemo task1 = new ForkJoinDemo( start, middle) ;
task1.fork( ) ;
ForkJoinDemo task2 = new ForkJoinDemo( middle + 1 , end) ;
task2.fork( ) ;
return task1.join( ) + task2.join( ) ;
}
}
}
异步回调:
// 异步调用
public class Demo01 {
public static void main( String[ ] args) throws ExecutionException, InterruptedException {
// 发起一个请求
// CompletableFuture< Void> completableFuture = CompletableFuture.runAsync(( ) - > {
/ / try {
/ / TimeUnit.SECONDS.sleep( 2 ) ;
/ / } catch ( InterruptedException e) {
/ / e.printStackTrace( ) ;
/ / }
/ / System.out.println( Thread.currentThread( ) .getName( ) + "runAsync = > Void") ;
/ / }) ;
/ / System.out.println( "11111 ") ;
/ / / / 获取阻塞执行结果
/ / completableFuture.get( ) ;
CompletableFuture< Integer> completableFuture = CompletableFuture.supplyAsync(( ) - > {
System.out.println( Thread.currentThread( ) .getName( ) + " supplyAsync = > Integer") ;
/ / int i = 10 / 0 ;
return 1024 ;
}) ;
System.out.println( completableFuture.whenComplete(( t, u) - > {
System.out.println( "t = > " + t) ;
System.out.println( "u = > " + u) ;
}) .exceptionally(( e) - > {
System.out.println( e.getMessage( )) ;
return 301 ;
} ) .get( )) ;
}
}
JMM:
1 . 线程解锁前,必须把共享变量立刻刷回主存
2 . 线程加锁前,必须读取主存中的最新值到工作内存中!
3 . 加锁和解锁是同一把锁
public class JMMDemo {
private static int num = 0 ;
public static void main( String[ ] args) {
new Thread(( ) -> {
while ( num == 0 ) {
System.out.println( Thread.currentThread( ) .getName( ) + " : " + num) ;
}
} , "Demo" ) .start( ) ;
try{
TimeUnit.SECONDS .sleep( 1 ) ;
} catch ( Exception e) {
e.printStackTrace( ) ;
}
num = 1 ;
System.out.println( num) ;
}
}
Volatile:
1 . 保证可见性
2 . 不保证原子性
3 . 禁止指令重排
public class JMMDemo {
// 加 volatile 可以保证可见性
private volatile static int num = 0 ;
public static void main( String[ ] args) {
new Thread(( ) - > {
while ( num == 0 ) {
}
}, "Demo") .start( ) ;
try{
TimeUnit.SECONDS.sleep( 1 ) ;
}catch ( Exception e) {
e.printStackTrace( ) ;
}
num = 1 ;
System.out.println( num) ;
}
}
# 2. 不保证原子性
/ / # 一般回答:加 Lock 和 synchronized 可以解决
public class VDemo02 {
private volatile static int num = 0 ;
public static void add( ) {
/ / num++ 不是原子性操作
num++ ;
}
public static void main( String[] args) {
for ( int i = 0 ; i < 20 ; i++ ) {
new Thread(( ) - > {
for ( int j = 0 ; j < 1000 ; j++ ) {
add( ) ;
}
}, String.valueOf( i)) .start( ) ;
}
while ( Thread.activeCount( ) > 2 ) { // main gc
// 礼让,让出CPU调度时间
Thread.yield( ) ;
}
System.out.println( Thread.currentThread( ) .getName( ) + " " + num) ;
}
}
//
//
public class VDemo02 {
// 原子类的 Integer
private static AtomicInteger num = new AtomicInteger( ) ;
public static void add ( ) {
// AtomicInteger + 1 方法,底层CAS实现
num.getAndIncrement( ) ;
}
public static void main( String[ ] args) {
for ( int i = 0 ; i < 20 ; i++) {
new Thread(( ) - > {
for ( int j = 0 ; j < 1000 ; j++ ) {
add( ) ;
}
}, String.valueOf( i)) .start( ) ;
}
while ( Thread.activeCount( ) > 2 ) { // main gc
// 礼让,让出CPU调度时间
Thread.yield( ) ;
}
System.out.println( Thread.currentThread( ) .getName( ) + " " + num) ;
}
}
1 . 保证特定的操作的执行顺序!
2 . 可以保证某些变量的内存可见性( 利用这些特性volatile实现了可见性)
单例模式:
// 饿汉式单例
public class Hungry {
// 浪费空间
private byte[ ] data1 = new byte[ 1024 *1024] ;
private byte[ ] data2 = new byte[ 1024 *1024] ;
private byte[ ] data3 = new byte[ 1024 *1024] ;
private Hungry ( ) {
}
private final static Hungry HUNGRY = new Hungry( ) ;
public static Hungry getInstance ( ) {
return HUNGRY;
}
}
//
public class LazyMan {
private LazyMan ( ) {
System.out.println( Thread.currentThread( ) .getName( ) + " OK" ) ;
}
private static LazyMan lazyMan;
public static LazyMan getInstance ( ) {
if( lazyMan == null) {
lazyMan = new LazyMan( ) ;
}
return lazyMan;
}
// 多线程并发
public static void main( String[ ] args) {
for ( int i = 0 ; i < 10 ; i++) {
new Thread(( ) - > {
LazyMan.getInstance( ) ;
}, String.valueOf( i)) .start( ) ;
}
}
}
public static LazyMan getInstance ( ) {
if( lazyMan == null) {
synchronized ( LazyMan.class) {
if( lazyMan == null) {
lazyMan = new LazyMan( ) ;
}
}
}
return lazyMan;
}
private volatile static LazyMan lazyMan;
public static LazyMan getInstance ( ) {
if( lazyMan == null) {
synchronized ( LazyMan.class) {
if( lazyMan == null) {
lazyMan = new LazyMan( ) ; // 不是原子性操作
/**
* 创建对象后进行的操作:
* 1 . 分配内存空间
* 2 . 执行构造方法, 初始化对象
* 3 . 把这个对象指向这个空间
* 在这里会发生指令重排现象, 所以需要加 volatile 关键字
*/
}
}
}
return lazyMan;
}
public class LazyMan {
private LazyMan ( ) {
synchronized ( LazyMan.class) {
if( lazyMan != null) {
throw new RuntimeException( "不要试图通过反射破坏单例" ) ;
}
}
System.out.println( Thread.currentThread( ) .getName( ) + " OK" ) ;
}
private volatile static LazyMan lazyMan;
public static LazyMan getInstance ( ) {
if( lazyMan == null) {
synchronized ( LazyMan.class) {
if( lazyMan == null) {
lazyMan = new LazyMan( ) ;
}
}
}
return lazyMan;
}
// 多线程并发
public static void main( String[ ] args) throws Exception{
LazyMan instance = LazyMan.getInstance( ) ;
Constructor< LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor( null) ;
declaredConstructor.setAccessible( true) ;
LazyMan instance2 = declaredConstructor.newInstance( ) ;
System.out.println( instance) ;
System.out.println( instance2) ;
}
}
public static void main( String[ ] args) throws Exception{
//LazyMan instance = LazyMan.getInstance( ) ;
Constructor< LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor( null) ;
declaredConstructor.setAccessible( true) ;
LazyMan instance = declaredConstructor.newInstance( ) ;
LazyMan instance2 = declaredConstructor.newInstance( ) ;
System.out.println( instance) ;
System.out.println( instance2) ;
}
private static boolean bzw = false ;
private LazyMan ( ) {
synchronized ( LazyMan.class) {
if( bzw == false ) {
bzw = true ;
} else {
throw new RuntimeException( "不要试图通过反射破坏单例" ) ;
}
}
System.out.println( Thread.currentThread( ) .getName( ) + " OK" ) ;
}
// 枚举本身是一个 CLASS 类
// 枚举类 底层有一个 ( String, int) 的构造方法, 通过反编译工具可以看出
public enum EnumSingle {
INSTANCE ;
public EnumSingle getInstance ( ) {
return INSTANCE ;
}
}
class Test{
public static void main( String[ ] args) throws Exception {
EnumSingle instance1 = EnumSingle.INSTANCE ;
Constructor< EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor( String.class, int.class) ;
declaredConstructor.setAccessible( true) ;
EnumSingle instance2 = declaredConstructor.newInstance( ) ;
System.out.println( instance1) ;
System.out.println( instance2) ;
}
}
// 静态内部类
public class Holder {
private Holder ( ) {
}
public static Holder getInstance ( ) {
return InnerClass.HOLDER;
}
public static class InnerClass{
private static final Holder HOLDER = new Holder( ) ;
}
}
深入理解CAS:
1 . 循环会耗时( 底层有自旋锁)
2 . 一次性只能保证一个共享变量的原子性
3 . 存在ABA问题
public class CASDemo {
//
// CAS 是CPU的并发原语
public static void main( String[ ] args) {
AtomicInteger atomicInteger = new AtomicInteger( 2020 ) ;
System.out.println( atomicInteger.compareAndSet( 2020 , 2021 )) ;
System.out.println( atomicInteger.get( )) ;
// Java无法操作内存, 但是可以通过调用C++操作内存( native方法)
// 可以通过 Unsafe 类操作
// getAndIncrement( ) 底层有自旋锁
atomicInteger.getAndIncrement( ) ;
System.out.println( atomicInteger.compareAndSet( 2020 , 2021 )) ;
System.out.println( atomicInteger.get( )) ;
}
}
原子引用:
AtomicReference的作用是对”对象”进行原子操作。
提供了一种读和写都是原子性的对象引用变量。原子意味着多个线程试图改变同一个AtomicReference( 例如比较和交换操作) 将不会使得AtomicReference处于不一致的状态。
AtomicReference和AtomicInteger非常类似,不同之处就在于AtomicInteger是对整数的封装,底层采用的是compareAndSwapInt实现CAS,比较的是数值是否相等,而AtomicReference则对应普通的对象引用,底层使用的是compareAndSwapObject实现CAS,比较的是两个对象的地址是否相等。也就是它可以保证你在修改对象引用时的线程安全性。
public static void main( String[ ] args) {
AtomicInteger atomicInteger = new AtomicInteger( 2020 ) ;
System.out.println( atomicInteger.compareAndSet( 2020 , 2021 )) ;
System.out.println( atomicInteger.get( )) ;
System.out.println( atomicInteger.compareAndSet( 2021 , 2020 )) ;
System.out.println( atomicInteger.get( )) ;
System.out.println( atomicInteger.compareAndSet( 2020 , 6666 )) ;
System.out.println( atomicInteger.get( )) ;
}
// 通过加版本号解决:乐观锁思想
public class CASDemo02 {
static AtomicStampedReference< Integer> atomicStampedReference = new AtomicStampedReference<> ( 1 , 1 ) ;
public static void main( String[ ] args) {
new Thread(( ) - > {
/ / 获得版本号
int stamp = atomicStampedReference.getStamp( ) ;
System.out.println( "a1 = > " + stamp) ;
try {
TimeUnit.SECONDS.sleep( 1 ) ;
}catch ( Exception e) {
e.printStackTrace( ) ;
}
System.out.println( "a2 = > " + atomicStampedReference.compareAndSet( 1 , 2 ,
atomicStampedReference.getStamp( ) , atomicStampedReference.getStamp( ) + 1 )) ;
System.out.println( "a2 => " + atomicStampedReference.getStamp( )) ;
System.out.println( "a3 => " + atomicStampedReference.compareAndSet( 2 , 1 ,
atomicStampedReference.getStamp( ) , atomicStampedReference.getStamp( ) + 1 )) ;
System.out.println( "a3 => " + atomicStampedReference.getStamp( )) ;
} , "a" ) .start( ) ;
new Thread(( ) - > {
/ / 获得版本号
int stamp = atomicStampedReference.getStamp( ) ;
System.out.println( "b1 = > " + stamp) ;
try {
TimeUnit.SECONDS.sleep( 2 ) ;
}catch ( Exception e) {
e.printStackTrace( ) ;
}
System.out.println( "b2 = > " + atomicStampedReference.compareAndSet( 1 , 3 ,
stamp, stamp + 1 )) ;
System.out.println( "b2 => " + atomicStampedReference.getStamp( )) ;
} , "b" ) .start( ) ;
}
}
可重入锁:
多个线程按照申请锁的顺序去获得锁,线程会直接进入队列去排队,永远都是队列的第一位才能得到锁。
优点:所有的线程都能得到资源,不会饿死在队列中。
缺点:吞吐量会下降很多,队列里面除了第一个线程,其他的线程都会阻塞,cpu唤醒阻塞线程的开销会很大。
多个线程去获取锁的时候,会直接去尝试获取,获取不到,再去进入等待队列,如果能获取到,就直接获取到锁。
优点:可以减少CPU唤醒线程的开销,整体的吞吐效率会高点,CPU也不必取唤醒所有线程,会减少唤起线程的数 量。
缺点:这样可能导致队列中间的线程一直获取不到锁或者长时间获取不到锁,导致饿死。
//
public class Demo01 {
public static void main( String[ ] args) {
Phone phone = new Phone( ) ;
new Thread(( ) -> {
phone.sms( ) ;
} , "A" ) .start( ) ;
new Thread(( ) -> {
phone.sms( ) ;
} , "B" ) .start( ) ;
}
}
class Phone{
public synchronized void sms ( ) {
System.out.println( Thread.currentThread( ) .getName( ) + " sms" ) ;
call( ) ;
}
public synchronized void call ( ) {
System.out.println( Thread.currentThread( ) .getName( ) + " call" ) ;
}
}
//
public class Demo01 {
public static void main( String[ ] args) {
Phone phone = new Phone( ) ;
new Thread(( ) -> {
phone.sms( ) ;
} , "A" ) .start( ) ;
new Thread(( ) -> {
phone.sms( ) ;
} , "B" ) .start( ) ;
}
}
class Phone{
Lock lock = new ReentrantLock( ) ;
public void sms ( ) {
lock.lock( ) ;
try {
System.out.println( Thread.currentThread( ) .getName( ) + " sms" ) ;
call( ) ;
} catch ( Exception e) {
e.printStackTrace( ) ;
} finally {
lock.unlock( ) ;
}
}
public void call ( ) {
lock.lock( ) ;
try {
System.out.println( Thread.currentThread( ) .getName( ) + " call" ) ;
} catch ( Exception e) {
e.printStackTrace( ) ;
} finally {
lock.unlock( ) ;
}
}
}
自旋锁:
是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。
缺点:获取锁的线程一直处于活跃状态,但是并没有执行任何有效的任务,使用这种锁会造成busy-waiting。
//
public class SpinlockDemo {
AtomicReference< Thread> atomicReference = new AtomicReference( ) ;
// 加锁
public void myLock ( ) {
Thread thread = Thread.currentThread( ) ;
System.out.println( Thread.currentThread( ) .getName( ) + " ==> myLock" ) ;
// 自旋锁
while ( ! atomicReference.compareAndSet( null, thread)) {
}
}
// 解锁
public void myUnlock ( ) {
Thread thread = Thread.currentThread( ) ;
System.out.println( Thread.currentThread( ) .getName( ) + " ==> myUnlock" ) ;
atomicReference.compareAndSet( thread, null) ;
}
}
public class TestSpinLock {
public static void main( String[ ] args) throws InterruptedException {
// 底层使用自旋锁, 通过CAS实现
SpinlockDemo lock = new SpinlockDemo( ) ;
new Thread(( ) -> {
lock.myLock( ) ;
try{
TimeUnit.SECONDS .sleep( 5 ) ;
} catch ( Exception e) {
e.printStackTrace( ) ;
} finally {
lock.myUnlock( ) ;
}
} , "T1" ) .start( ) ;
TimeUnit.SECONDS .sleep( 1 ) ;
new Thread(( ) -> {
lock.myLock( ) ;
try{
} catch ( Exception e) {
e.printStackTrace( ) ;
} finally {
lock.myUnlock( ) ;
}
} , "T2" ) .start( ) ;
}
}
死锁排查:
public class DeadLockDemo {
public static void main( String[ ] args) {
String lockA = "lockA" ;
String lockB = "lockB" ;
new Thread( new MyThread( lockA, lockB) , "T1" ) .start( ) ;
new Thread( new MyThread( lockB, lockA) , "T2" ) .start( ) ;
}
}
class MyThread implements Runnable {
private String lockA;
private String lockB;
public MyThread( String lockA, String lockB) {
this.lockA = lockA;
this.lockB = lockB;
}
@Override
public void run ( ) {
synchronized ( lockA) {
System.out.println( Thread.currentThread( ) .getName( ) + " lock: " + lockA + " => get " + lockB) ;
try {
TimeUnit.SECONDS .sleep( 2 ) ;
} catch ( Exception e) {
e.printStackTrace( ) ;
}
synchronized ( lockB) {
System.out.println( Thread.currentThread( ) .getName( ) + " lock: " + lockB + " => get " + lockA) ;
}
}
}
}
1 . 使用 jps 定位进程号: jps -l
2 . jstack 进程号( 查看进程的堆栈信息)
1 . 看日志
2 . 查看堆栈信息