Lock、synchronized
Synchronized
如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况:
1)获取锁的线程执行完了该代码块,然后线程释放对锁的占有;
2)线程执行发生异常,此时JVM会让线程自动释放锁。
那么如果这个获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法),获取锁的线程被阻塞了,但是又没有释放锁,其他线程便只能干巴巴地等待,这非常影响程序的执行效率。因此就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断),通过Lock就可以办到。
Lock
1)Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个接口,通过这个接口可以实现同步访问;
2)Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。
3)在Lock接口中,lock()、tryLock()、tryLock(long time, TimeUnit unit)和lockInterruptibly()是用来获取锁的。unLock()方法是用来释放锁的。
4)通过Lock的tryLock()可以知道有没有成功获取锁,而synchronized却无法办到。
5)Lock可以提高多个线程进行读操作的效率。(ReentrantReadWriteLock)
6)Lock可以让等待锁的线程响应中断(lockInterruptibly())
- tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,
- 当通过lockInterruptibly()方法获取某个锁时,如果不能获取到,只有进行等待的情况下,是可以响应中断的。而用synchronized修饰的话,当一个线程处于等待某个锁的状态,是无法被中断的,只有一直等待下去。
lock( )方法,如果锁已被其他线程获取,则会等待。tryLock( )方法,拿不到锁时不会一直在那等待。lockInterruptibly( )方法能够响应中断。
可重入锁、可中断锁、公平锁、读写锁。
可重入锁
如果锁具备可重入性,则称作为可重入锁。像synchronized和ReentrantLock都是可重入锁,可重入性实际上表明了锁的分配机制:基于线程的分配,而不是基于方法调用的分配。举个简单的例子,当一个线程执行到某个synchronized方法时,比如说method1,而在method1中会调用另外一个synchronized方法method2,此时线程不必重新去申请锁,而是可以直接执行方法method2。
####可中断锁
顾名思义,就是可以相应中断的锁。在Java中,synchronized就不是可中断锁,而Lock是可中断锁。如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁。在前面的lockInterruptibly()的用法时已经体现了Lock的可中断性。
公平锁
尽量以请取锁的顺序来获取锁。比如同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该所,这种就是公平锁。非公平锁即无法保证锁的获取是按照请求锁的顺序进行的。这样就可能导致某个或者一些线程永远获取不到锁。在Java中,synchronized就是非公平锁,它无法保证等待的线程获取锁的顺序。而对于ReentrantLock和ReentrantReadWriteLock,它默认情况下是非公平锁,但是可以设置为公平锁。
/*** 如果参数为true表示为公平锁,为fasle为非公平锁。* 默认情况下,如果使用无参构造器,则是非公平锁。
**/ReentrantLock lock = new ReentrantLock(true);
读写锁
对一个资源(比如文件)的访问分成了2个锁,一个读锁和一个写锁。正因为有了读写锁,才使得多个线程之间的读操作不会发生冲突。ReadWriteLock就是读写锁,它是一个接口,ReentrantReadWriteLock实现了这个接口。可以通过readLock()获取读锁,通过writeLock()获取写锁。
CountDownLatch、CyclicBarrier 、join()、线程池
CyclicBarrier
CyclicBarrier 的字面意思是可循环(Cyclic)使用的屏障(Barrier),又叫同步屏障。它可以让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。线程到达屏障的控制通过CyclicBarrier 的 await() 方法实现。
CyclicBarrier 的构造方法有 CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用 await 方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。
CyclicBarrier 还提供一个构造函数 CyclicBarrier(int parties, Runnable barrierAction) ,用于在线程都到达屏障时,优先执行barrierAction 这个 Runnable 对象,然后都到达屏障的线程继续执行。
实现原理
在CyclicBarrier的内部定义了一个ReentrantLock对象,每当一个线程调用 CyclicBarrier的await方法时,将剩余拦截的线程数减1,然后判断剩余拦截数是否为0,如果不是,进入 Lock 对象的条件队列等待。如果是则执行barrierAction 对象的 run 方法,然后将锁的条件队列中的所有线程放入锁等待队列中,这些线程会依次的获取锁、释放锁,接着先从await 方法返回,再从CyclicBarrier的 await方法中返回。
CountDownLatch
Java 的 util.concurrent 包里面的 CountDownLatch 其实可以把它看作一个计数器(倒计时锁),只不过这个计数器的操作是原子操作,同时只能有一个线程去操作这个计数器,也就是同时只能有一个线程去减这个计数器里面的值。
你可以向 CountDownLatch 对象设置一个初始的数字作为计数值,任何调用这个对象上的 await() 方法都会阻塞,直到这个计数器的计数值被其他的线程减为 0 为止。
join
join() 是 Thread 类的一个方法,join() 方法的作用是等待当前线程结束,也即让“主线程”等待“子线程”结束之后才能继续运行。t.join() 方法阻塞调用此方法的线程 (calling thread),直到线程 t完成,此线程再继续(看起来和同步调用类似);通常用于在 main 主线程内,等待其它线程完成后再继续执行 main 主线程。
join 实现原理
Join 方法实现是通过 wait(Object 提供的方法)。 看源代码知会进入while(isAlive()) 循环;即只要子线程是活的,主线程就不停的等待。
CyclicBarrier 和 CountDownLatch 比较
CountDownLatch 的作用是允许1或N个线程等待其他线程完成执行;而 CyclicBarrier 则是允许N个线程相互等待。CountDownLatch 的计数器无法被重置;CyclicBarrier 的计数器可以被重置后使用,因此它被称为是循环的 barrier。
CountDownLatch 与 join 比较
调用thread.join() 方法必须等thread 执行完毕,当前线程才能继续往下执行,
而CountDownLatch通过计数器提供了更灵活的控制,只要检测到计数器为0当前线程就可以往下执行而不用管相应的thread是否执行完毕。
线程池
当线程池的线程全部执行完毕后再执行主线程, executorService.shutdown(),当线程池里面的线程结束之后,会主动关闭线程池,executorService.isTerminated(),是判断是否结束。
public class ExecuteOrderPractice {public void orderPractice(){ExecutorService executorService = Executors.newFixedThreadPool(3);for(int i = 0; i < 5; i++){executorService.execute(new Runnable() {@Overridepublic void run() {try{Thread.sleep(1000);System.out.println(Thread.currentThread().getName() + " do something");}catch (InterruptedException e){e.printStackTrace();}}});}executorService.shutdown();while(true){if(executorService.isTerminated()){System.out.println("Finally do something ");break;}try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args){new ExecuteOrderPractice().orderPractice();}
}
condition?wait/notify