Lock简单使用
在java多线程中,锁的机制可以通过synchronized来实现,但在jdk1.5中新增加了ReentrantLock类也能达到同样的效果。下面通过一段代码来展示。
package demo_20;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockRun {
public static void main(String[] args) {
TestMethod testMethod = new TestMethod();
MyThread myThread1 = new MyThread(testMethod);
MyThread myThread2 = new MyThread(testMethod);
Thread thread1 = new Thread(myThread1);
Thread thread2 = new Thread(myThread2);
thread1.start();
thread2.start();
}
}
class MyThread implements Runnable{
private TestMethod testMethod;
public MyThread(TestMethod testMethod) {
this.testMethod = testMethod;
}
@Override
public void run() {
testMethod.method();
}
}
class TestMethod {
Lock lock = new ReentrantLock();
public void method(){
lock.lock();
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+ "("+ i + ")");
}
lock.unlock();
}
}
Condition线程间的等待通知
类ReentrantLock也可实现线程间的通信,但是需要借助Condition对象来实现。
Condition可以实现多路通知功能,也就是在一个Lock里面创建多个Condition实例,线程对象可以注册在指定的Condition中,从而可以选择性的实现线程通知。
如下代码通过Condition实现的生产者/消费者
同时实现选择通知。
package demo_20;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Lock的生产者消费者实现
*/
public class LockProducersConsumers {
public static void main(String[] args) {
ProducersAndConsumers producersAndConsumers = new ProducersAndConsumers();
Thread[] lockProducers = new Thread[10];
Thread[] lockConsumers = new Thread[10];
for (int i=0;i<10;i++){
lockProducers[i] = new Thread(new LockProducers(producersAndConsumers));
lockConsumers[i] = new Thread(new LockConsumers(producersAndConsumers));
lockConsumers[i].start();
lockProducers[i].start();
}
}
}
/**
* 生产者
*/
class LockProducers implements Runnable{
private ProducersAndConsumers producersAndConsumers;
public LockProducers(ProducersAndConsumers producersAndConsumers) {
this.producersAndConsumers = producersAndConsumers;
}
@Override
public void run() {
while (true){
producersAndConsumers.set();
}
}
}
/**
* 消费者
*/
class LockConsumers implements Runnable{
private ProducersAndConsumers producersAndConsumers;
public LockConsumers(ProducersAndConsumers producersAndConsumers) {
this.producersAndConsumers = producersAndConsumers;
}
@Override
public void run() {
while (true){
producersAndConsumers.get();
}
}
}
class ProducersAndConsumers{
private Lock lock = new ReentrantLock();
/**
* 使用两个Condition可使生产者与消费者分组,从而避免唤醒同类阻塞线程造成线程阻塞
*/
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private boolean flag = false;
public void set(){
try {
lock.lock();
while (flag){
condition1.await();
}
System.out.println("生产者: "+Thread.currentThread().getName());
flag = true;
condition2.signal();
}catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void get(){
try {
lock.lock();
while (!flag){
condition2.await();
}
System.out.println("消费者: "+Thread.currentThread().getName());
flag = false;
condition1.signal();
}catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
同wait一样,在使用condition.await()之前也需要先获得同步监视器。Lock.lock();
signal唤醒同类线程造成死锁
package demo_20;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockSignalAll {
public static void main(String[] args) {
ProducersAndConsumers producersAndConsumers = new ProducersAndConsumers();
Thread[] lockProducers = new Thread[10];
Thread[] lockConsumers = new Thread[10];
for (int i=0;i<10;i++){
lockProducers[i] = new Thread(new LockProducers(producersAndConsumers));
lockConsumers[i] = new Thread(new LockConsumers(producersAndConsumers));
lockConsumers[i].start();
lockProducers[i].start();
}
}
}
/**
* 生产者
*/
class LockProducers implements Runnable{
private ProducersAndConsumers producersAndConsumers;
public LockProducers(ProducersAndConsumers producersAndConsumers) {
this.producersAndConsumers = producersAndConsumers;
}
@Override
public void run() {
while (true){
producersAndConsumers.set();
}
}
}
/**
* 消费者
*/
class LockConsumers implements Runnable{
private ProducersAndConsumers producersAndConsumers;
public LockConsumers(ProducersAndConsumers producersAndConsumers) {
this.producersAndConsumers = producersAndConsumers;
}
@Override
public void run() {
while (true){
producersAndConsumers.get();
}
}
}
class ProducersAndConsumers{
private Lock lock = new ReentrantLock();
/**
* 使用两个Condition可使生产者与消费者分组,从而避免唤醒同类阻塞线程造成线程阻塞
*/
private Condition condition1 = lock.newCondition();
private boolean flag = false;
public void set(){
try {
lock.lock();
while (flag){
condition1.await();
}
System.out.println("生产者: "+Thread.currentThread().getName());
flag = true;
condition1.signal();
}catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void get(){
try {
lock.lock();
while (!flag){
condition1.await();
}
System.out.println("消费者: "+Thread.currentThread().getName());
flag = false;
condition1.signal();
}catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
解决方法两种:
- 将condition.signal()换成condition.signalAll()
- 使用Condition的分组功能,唤醒指定的线程。
公平锁/非公平锁
锁Lock分为公平锁/非公平锁
公平锁:表示线程获取锁的顺序是按照线程加锁的顺序来进行的,即先来先得(但也不是必须,只是大多数线程都会按照此规律依次的进行)。
非公平锁:获取锁的原则是随机的。先来不一定先得,有可能造成某线程一直得不到锁
Lock lock = new ReentrantLock(true);公平锁 false为非公平锁。
关于锁的常用方法
int getHoldCount():查询当前线程保持此锁的的个数,即调用lock()方法的次数。
int getQueueLength()
返回正等待获取此锁定的线程估计数
比如有五个线程,其中一个线程先获得了锁,此时调用此方法,则返回值为4.即有四个线程正在等待lock释放。
int getWaitQueueLength(Condition condition)
反回等待与此锁定相关给定条件的Condition的线程估计数。
如有五个线程,每个线程都执行了await方法,则返回的int值为 5
boolean hasQueuedThread(Thread thread)
查询指定
线程是否在等待获取此锁定。
boolean hasQueuedThreads()
查询是否有线程正在等待获取此锁定。
boolean hasWaiters(Condition condition)
查询是否有线程正在等待与此锁定有关的Condition条件。
boolean isFair()
判断是不是公平锁 是返回true 默认为false
boolean isHeldByCurrentThread()
查询当前线程是否保持此锁定
boolean isLocked()
查询是否有线程保持此锁定
void lockInterruptibly()
如果当前线程未被中断,则获取锁,否则抛出异常。
注意先判断,后获得锁
boolean tryLock()
在调用时,若锁未被另一个线程获得保持,则获取此锁。
即先判断锁有无被占用的情况。
boolean try(long time,TimeUnit unit)
如果锁定在一定时间内没有被另外的一个线程保持,且未被中断,则获得锁。
awaitUninterruptibly()
相比于await();前者在处于WAITING状态时被中断则抛出异常。awaitUninterruptibly()则不会。
ReentrantReadWriteLock类
ReentrantLock具有完全互斥排他性,同一时间只能有一个线程执行lock()方法后面的任务。效率低下。
ReentrantReadWriteLock可以实现读写锁互斥。
- 读读不互斥
- 读写互斥
- 写写互斥
只要调用了Lock的write方法,Lock锁就为互斥锁。
lock与synchronized的区别
-
Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现,synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定。
但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将 unLock()放到finally{} 中; -
synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;
而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁; -
Lock可以让等待锁的线程响应中断,线程可以中断去干别的事务,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断
-
通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
-
Lock可以提高多个线程进行读操作的效率。
- 在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。
- 举个例子:当有多个线程读写文件时,读操作和写操作会发生冲突现象,写操作和写操作会发生冲突现象,但是读操作和读操作不会发生冲突现象。
- 但是采用synchronized关键字来实现同步的话,就会导致一个问题:
如果多个线程都只是进行读操作,所以当一个线程在进行读操作时,其他线程只能等待无法进行读操作。
因此就需要一种机制来使得多个线程都只是进行读操作时,线程之间不会发生冲突,通过Lock就可以办到。
另外,通过Lock可以知道线程有没有成功获取到锁。这个是synchronized无法办到的