一、同步锁lock、及其一些常用实现类
同步锁lock,注意(与synchronized不同点):
1)使用lock无论如何都要手动释放锁(否则会发生死锁),所以将其放在finally块中,而synchronized离开块后会自动释放锁,一般不会发生死锁
2)Lock可以让等待锁的线程响应中断,在持有锁的线程长时间不释放锁时,可以中断等待锁的线程去干别的事(通过lock.lockInterruptibly()方法),避免死锁。而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
lock.lockInterruptibly()方法:当两个线程同时通过lock.lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁,而线程B只有再等待,那么此时能够中断线程B的等待过程。(当一个线程获取了锁之后,是不会被中断的,只能中断阻塞过程中的线程。)
3)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。lock.tryLock();(获取到锁返回true,否则返回false,也可以设置等待时间,在等待时间内获取到返回true,否则返回false)
4)Lock可以提高多个线程进行读操作的效率(ReadWriteLock)。
1、可重入锁
可重入锁( ReentrantLock):ReentrantLock是基于AQS实现,重入这里指的是在某线程已经获取锁之后(获取到锁执行期间),该线程可以再次获取锁,进入同步代码块。这里需要强调一下重入的概念中所指的线程是已经获得锁的的线程,这与线程安全不冲突,因为只有一个线程可以获取锁,也就是说始终都是同一个线程(获取锁的线程)在运行同步代码,相当于单线程运行,当然是线程安全的。synchronized关键字也是支持重入的,例如synchronized可以在递归调用中使用。
ReentrantLock提供了公平锁与非公平锁(默认为非公平锁)。
公平锁:线程按照申请锁的等待队列顺序获得锁。(指定为公平锁:Lock lock = new ReentrantLock(true);)
非公平锁:线程随机获得锁,不按照申请锁的等待队列。
而synchronized的实现是非公平锁:线程随机获取到锁。
ReentrantLock提供了显式加解锁操作。提供了lock(),unlock()方法进行加解锁的操作,而synchronized是隐式进行加锁与解锁操作(依赖于编译器将其编译为moniterenter与moniterexit)。ReentrantLock对锁的等待可以中断,在持有锁的线程长时间不释放锁时,等待锁的线程可以选择放弃等待,这样就避免了synchronized可能带来的死锁问题。ReentrantLock.tryLock()可以设置等待时间。
eg:
while(true){
lock.lock();
try {
if(tickets>0){
System.out.println(Thread.currentThread().getName()+"成功售票:"+tickets);
Thread.sleep(20);
tickets--;
}else{
break;
}
} finally {
lock.unlock();
}
}
使用lock在进行线程通信时需要使用Condition的await、signal、signalAll方法,而不是Object类中的wait、notify、notifyAll
Condition condition1 = lock.newCondition();
注意:
Object的notify()方法能够唤醒一个正在等待该对象的锁的线程,当有多个线程都在等待该对象的锁的话,则只能唤醒其中一个线程,具体唤醒哪个线程则不得而知。使用wait、notify、notifyAll方法必须在synchronized代码块或者synchronized方法中
调用Condition的await()和signal(),signalAll()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用Conditon中的await()对应Object的wait(); Condition中的signal()对应Object的notify(),但是使用signal可以指定唤醒的线程; Condition中的signalAll()对应Object的notifyAll()
2、读写锁
当前有线程在占用读锁时,需要进行写操作,需要等待读锁释放后才可以获取写锁
当前有线程在占用写锁时,需要进行读操作,需要等待写锁释放后才可以获取读锁
举个例子:当有多个线程读写文件时,读操作和写操作会发生冲突现象,写操作和写操作会发生冲突现象,但是读操作和读操作不会发生冲突现象。
但是采用synchronized关键字来实现同步的话,就会导致一个问题:
如果多个线程都只是进行读操作,所以当一个线程在进行读操作时,其他线程只能等待无法进行读操作。
因此就需要一种机制来使得多个线程都只是进行读操作时,线程之间不会发生冲突,通过Lock就可以办到。
另外,通过Lock可以知道线程有没有成功获取到锁。这个是synchronized无法办到的
/**
* 读写锁:
* 写写、读写互斥,需要等待释放锁
* 读读不互斥,不需要释放锁
* @author JACK
*
*/
public class TestReadWriteLock {
public static void main(String[] args) {
ReadAndLockDemo demo = new ReadAndLockDemo();
new Thread(new Runnable() {
@Override
public void run() {
demo.set((int)(Math.random()*100));
}
},"write").start();
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
demo.get();
}
}).start();
}
}
}
class ReadAndLockDemo{
private int number=0;
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
//读
public void get(){
readWriteLock.readLock().lock();//上读锁
try {
System.out.println(Thread.currentThread().getName()+":"+number);
} finally {
readWriteLock.readLock().unlock();//释放读锁
}
}
//写
public void set(int number){
readWriteLock.writeLock().lock();//上写锁
try {
System.out.println(Thread.currentThread().getName()+number);
this.number=number;
} finally {
readWriteLock.writeLock().unlock();//释放写锁
}
}
}
3、闭锁
/**
* CountDownLatch:闭锁,在进行计算时,只有在其他线程计算全部完成,当前运算才执行
* @author JACK
*
*/
public class TestCountDownLatch {
public static void main(String[] args) {
CountDownLatch latch = new CountDownLatch(5);//参数表示?个线程完成后,主线程才运算
LatchDemo latchDemo = new LatchDemo(latch);
long currentTimeMillis = System.currentTimeMillis();
for(int i=0;i<5;i++){
new Thread(latchDemo).start();
}
try {
latch.await();//等待其他线程上的运算完成,当前线程才可以进行运算
} catch (InterruptedException e) {
e.printStackTrace();
}
long currentTimeMillis2 = System.currentTimeMillis();
System.out.println("耗费时间:"+(currentTimeMillis2-currentTimeMillis));
}
}
class LatchDemo implements Runnable{
private CountDownLatch latch;
public LatchDemo(CountDownLatch latch) {
this.latch=latch;
}
@Override
public void run() {
System.out.println("线程");
try{
for(int i=1;i<10000;i++){
System.out.println(i);
}
}finally{
latch.countDown();//线程数减1
}
}
}