Java并发编程:Lock

一、synchronized的缺陷

synchronized是Java的关键字,也就是Java语言内置的特性。那么为什么会出现Lock呢?

线程等待

如果一个代码块被synchronized修饰,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里拿着锁的线程只会有两种情况释放锁:

1.获得锁的线程执行完了该代码块,然后线程释放对锁的占有。

2.线程执行发生了异常,JVM会让线程自动释放锁。

如果这个获取锁的线程由于要等待IO或者其他原因(比如sleep)被阻塞了,但是又没有释放锁,其他线程便只能干巴巴地等待,会非常影响效率。而使用lock就可以改变这种局面。

读写问题

在例如,多个线程读写文件,读写操作会发生冲突现象,写写操作会发生冲突现象,但读读不会冲突。但是使用synchronized的话,无论如何都会冲突,因为一个线程拿到锁时,其他的都得等待。而使用lock可以改变这种局面、

总而言之,Lock提供了比synchronized更多的功能。区别如下:

1.Lock不是Java语言内置的,由concurrent提供,Lock是一个接口。  synchronized是Java语言的关键字,是Java内置的。

2。Lock和synchronized有一个很大的区别,synchronized不需要用户手动释放锁,当synchronized方法或代码块执行完后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果未主动释放锁,可能会死锁。

二、java.util.comcurrent.locks包下常用的类

Lock

public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}

下面一次看下每个方法的使用lock()  tryLock()  tryLock(long time,TimeUnit unit)  lockInterruptibly()是用来获取锁的。unLock()方法是用来获取锁的。 newCondition先放一下。

上面四个获取锁的方法有何区别?

lock()

首先lock()是用的最多的方法啊,用来获取锁,如果锁已被其他线程获取,则进行等待。

lock必须主动释放锁,并且发生异常时,不会自动释放锁。因此,一般放到try catch块中,用finally来释放锁,以防死锁发生。通常使用下面的写法:

Lock lock = new ReentrantLock();
		lock.lock();
		try {
			//执行任务
		} catch (Exception e) {
			// TODO: handle exception
		}finally {
			lock.unlock();   //释放锁
		}

tryLock()

tryLock()方法是有返回值的,true表示成功获取锁,false表示获取失败。无论有没有获取到锁,这个方法都会立即返回,不会一直等待。一般写法:

Lock lock = new ReentrantLock();
		if(lock.tryLock()){
			try {
				//执行任务
			} catch (Exception e) {
				// TODO: handle exception
			}finally {
				lock.unlock(); //释放锁
			}
		}else{
			//拿不到锁的废物,做点别的吧
		}

lockInterruptibly()

lockInteruptibly方法比较特殊,当通过这个方法获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。也就是说,当两个线程同时执行lock.lockInteruptibly()想获取某个锁时,假若此时线程A获得了锁,而线程B只有在等待,此时如果调用线程B的threadB.interrupt()方法能够中断线程B的等待过程。

由于lockInterruptibly的声明中抛出了异常,所以lock.lockInterruptibly必须放在try块中或者在调用lockinterruptibly()的方法外声明抛出InterrruptedException。lockInteruptibly一般的使用形式如下:

public void method() throws InterruptedException {
    lock.lockInterruptibly();
    try {  
     //.....
    }
    finally {
        lock.unlock();
    }  
}

当一个线程获取锁之后,是不会被interrupt()方法中断的。因为interrupt方法只能中断阻塞的线程,不能中断正在运行的线程。因此当通过lockInterruptibly()方法获取某个锁时,如果获取不到,只有在等待情况下,是可以响应中断的。

而synchronized修饰的话,当一个线程处于等待某个锁的状态,是无法被中断的,只有一种等下去。

ReentrantLock 可重入锁

ReentrantLock是唯一实现了Lock接口的类,并且ReentrantLock提供了更多的方法。下面看下具体如何使用ReentrantLock。例子1,lock()的正确使用方法

public class Test {
	private ArrayList<Integer> arrayList = new ArrayList<>();
	public static void main(String[] args) {
		final Test test = new Test();
		new Thread() {
			@Override
			public void run() {
				test.insert(Thread.currentThread()); //currentThread返回当前正在执行当前代码的线程引用
			}
		}.start();
		new Thread() {
			@Override
			public void run() {
				test.insert(Thread.currentThread()); //currentThread返回当前正在执行当前代码的线程引用
			}
		}.start();
	}
	
	public void insert(Thread thread){
		Lock lock = new ReentrantLock();
		lock.lock();
		try {
			System.out.println(thread.getName()+"得到了锁");
			for(int i=0;i<5;i++){
				arrayList.add(i);
			}
		} catch (Exception e) {
			
		}finally {
			System.out.println(thread.getName()+"释放了锁");
			lock.unlock();
		}
	}
}

执行结果:

为什么会出现这种结果呢,因为Lock lock = new ReentrantLock();写在了方法内部,这样每个线程执行时就会创建一个新的lock对象。  所以应该把这行代码,定义成成员变量。

public class Test {
	private ArrayList<Integer> arrayList = new ArrayList<>();
	Lock lock = new ReentrantLock();
	public static void main(String[] args) {
		final Test test = new Test();
		new Thread() {
			@Override
			public void run() {
				test.insert(Thread.currentThread()); //currentThread返回当前正在执行当前代码的线程引用
			}
		}.start();
		new Thread() {
			@Override
			public void run() {
				test.insert(Thread.currentThread()); //currentThread返回当前正在执行当前代码的线程引用
			}
		}.start();
	}
	
	public void insert(Thread thread){
		lock.lock();
		try {
			System.out.println(thread.getName()+"得到了锁");
			for(int i=0;i<5;i++){
				arrayList.add(i);
			}
		} catch (Exception e) {
			
		}finally {
			System.out.println(thread.getName()+"释放了锁");
			lock.unlock();
		}
	}
}

结果:

tryLock的使用功能方法:

public class Test {
	private ArrayList<Integer> arrayList = new ArrayList<>();
	Lock lock = new ReentrantLock();
	public static void main(String[] args) {
		final Test test = new Test();
		new Thread() {
			@Override
			public void run() {
				test.insert(Thread.currentThread()); //currentThread返回当前正在执行当前代码的线程引用
			}
		}.start();
		new Thread() {
			@Override
			public void run() {
				test.insert(Thread.currentThread()); //currentThread返回当前正在执行当前代码的线程引用
			}
		}.start();
	}
	
	public void insert(Thread thread){
		if(lock.tryLock()){
			try {
				System.out.println(thread.getName()+"得到了锁");
				for(int i=0;i<5;i++){
					arrayList.add(i);
				}
			} catch (Exception e) {
				
			}finally {
				System.out.println(thread.getName()+"释放了锁");
				lock.unlock();
			}
		}else{
			System.out.println(thread.getName()+"没拿到锁");
		}
	}
}

结果:

lockInterruptibly响应中断的使用方法:

public class Test {
	private ArrayList<Integer> arrayList = new ArrayList<>();
	Lock lock = new ReentrantLock();
	public static void main(String[] args) {
		final Test test = new Test();
		Thread t1 = new Thread() {
			@Override
			public void run() {
				try {
					test.insert(Thread.currentThread());
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					System.out.println(Thread.currentThread().getName()+"被中断");
				//	e.printStackTrace();
				} 
			}
		};
		Thread t2 = new Thread() {
			@Override
			public void run() {
				try {
					test.insert(Thread.currentThread());
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					System.out.println(Thread.currentThread().getName()+"被中断");
				//	e.printStackTrace();
				} 
			}
		};
		t1.start();
		t2.start();
		try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
		t2.interrupt();
	}
	
	public void insert(Thread thread) throws InterruptedException{
		   lock.lockInterruptibly();
			try {
				System.out.println(thread.getName()+"得到了锁");
				 long startTime = System.currentTimeMillis();
				 for(    ;     ;) {
		                if(System.currentTimeMillis() - startTime >= 10000)
		                    break;
		                //插入数据
		            }
			} catch (Exception e) {
				
			}finally {
				System.out.println(thread.getName()+"释放了锁");
				lock.unlock();
			}
	}
}

运行结果:等待的线程能够被中断

ReadWriteLock锁

readWrite锁是一个接口,它里面定义了两个方法:

public interface ReadWriteLock {
    
    Lock readLock();
 
    Lock writeLock();
}

一个用来获取读锁,一个用来获取写锁。也就是说将文件读写操作分开,分成2个锁来分配给线程,从而使得多个线程可以同时进行读操作,下面的ReentrantReadWriteLock实现了ReadWriteLock接口。

ReentrantReadWriteLock

ReentrantReadWriteLock提供了很多丰富的方法,不过最主要的就是readLock和writeLock用来获取读锁和写锁。

下面举例说明ReentrantReadWriteLock的具体用法:

当有多个线程同时读时,先看下synchronized达到的效果:

public class Test {
	 public static void main(String[] args) {
		 Test test = new Test();
		new Thread(){
			@Override
			public void run() {
				test.method(Thread.currentThread());
			}
		}.start();
		
		new Thread(){
			@Override
			public void run() {
				test.method(Thread.currentThread());
			}
		}.start();
		
	}
	private synchronized void method(Thread thread){
		
		long start = System.currentTimeMillis();
		while(System.currentTimeMillis() - start <= 1){
			System.out.println(thread.getName()+"正在读");
		}
		System.out.println(thread.getName()+"读完毕");
		
	}
}

结果:图太长了就光截个结果把,只有当0读完时,1才读。

使用reentrantReadWriteLock:

public class Test {
	ReadWriteLock lock = new ReentrantReadWriteLock();
	 public static void main(String[] args) {
		 Test test = new Test();
		new Thread(){
			@Override
			public void run() {
				test.method(Thread.currentThread());
			}
		}.start();
		
		new Thread(){
			@Override
			public void run() {
				test.method(Thread.currentThread());
			}
		}.start();
		
	}
	private void method(Thread thread){
		lock.readLock().lock();
		try {
			long start = System.currentTimeMillis();
			while(System.currentTimeMillis() - start <= 1){
				System.out.println(thread.getName()+"正在读");
			}
			System.out.println(thread.getName()+"读完毕");
		} finally {
			lock.readLock().unlock();
		}
		
	}
}

截一段结果:两个现在在共同执行

这样就大大提高了读操作的效率。不过当一个线程已经使用了读锁,则此时其他线程如果要申请写锁,申请写锁的线程会一直等待释放读锁。

如果一个线程已经占有了写锁,则此时其他线程如果申请写锁或者读锁,则申请的线程会一直等待释放写锁。

如何选择Lock和synchronized?

lock使用一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;

synchronized1在发生异常时,会自动释放线程占有的锁,因此不会导致死锁发生,而Lock在发生异常时,如果没有主动通过unLock去释放锁,很可能造成死锁现象,因此使用lOCK时需要在finally中释放锁。

Lock可以让等待的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等下去,不能够响应中断。

通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。

Lock可以提高多个线程进行读的效率。

性能上看,竞争不激烈,两者的性能差不多,当竞争非常激烈时,此时Lock的性能远远优于synchronized,具体情况具体选择。

锁的相关介绍:

可重入锁

如果锁具备可重入性,则成为可重入锁。像synchronized热ReentrantLock都是可重入锁。可重入性表明了锁的分配机制:

基于线程的分配,而不是基于方法调用的分配。举个栗子,当一个线程执行到某个synchronized方法时,比如method1,而在method1中会调用另外一个synchronized方法method2,此时线程不必重新申请锁,而是可以直接执行方法method2

class MyClass {
    public synchronized void method1() {
        method2();
    }
     
    public synchronized void method2() {
         
    }
}

上述代码中两个方法method1和method2都1用synchronized修饰了,某一时刻A执行到了method1,此时线程A获取了这个对象的锁,而由于method2也是synchronized方法,假如synchronized不具备可重入性,此时线程A需要重新申请锁。但是这就会造成一个问题,因为线程A已经持有了该对象的锁,而又在申请获取该对象的锁,这样线程A一直等待永远不会获取到锁。

由于synchronized和Lock都具备可重入性,所以不会发生上述情况。

可中断锁

Java中,synchronized就不是可中断的,而lOCK是可中断的锁。

如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,B不想等待了,想先处理其他事,我们可以让它中断自己获取再别的线程中中断它,这就是可中断锁。

前面的lockInterruptibly的用法已经体现了Lock的可中断性。

公平锁

公平锁尽量以请求锁的顺序来获取锁。比如同时有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该锁,这种就是公平锁。

非公平锁即无法保证锁的获取是按照请求的顺序进行的。这样就可能导致某个或者一些线程永远获取不到锁。

在Java中,synchronized就是非公平锁,他无法保证等待的线程获取锁的顺序。

而ReentrantLock和ReenrantReadWriteLock,它默认下是非公平锁,但是可以设置为公平锁。看下源码:

有两个静态内部类,一个是NotFairSync,一个是FairSync分别用来实现非公平锁和公平锁。

我们可以在创建ReentrantLock对象时,通过传入参数的方式来设置锁的公平性:

ReentrantLock lock = new ReentrantLock(true);

如果参数为true表示公平锁,不传或者穿false就是非公平锁,默认情况下是非公平锁。另外ReentrantLock类中定义了很多方法如:

isFair() //判断锁是否是公平锁

isLocked() //判断锁是否被任何线程获取了

isHeidByCurrentThread() //判断锁是否被当前线程获取了

在ReentrantReadWriteLock中也有类似方法,同样也可以设置为公平锁和非公平锁。不过要记住,ReentrantReadWriteLock并未实现Lock接口,他实现的是ReadWirteLock接口。

4.读写锁

读写锁将对一个资源的访问分层了两个锁,一个读锁一个写锁。

正因为有了读写锁,才使得多个线程之间的读操作不会发生冲突。

ReadWriteLock就是读写锁,他是一个接口,ReentrantReadWriteLock实现了这个接口。

可以通过readLock获取读锁,writeLock获取写锁。

 

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值