软件编程
位置:首页>> 软件编程>> java编程>> Java多线程 ReentrantLock互斥锁详解

Java多线程 ReentrantLock互斥锁详解

作者:慢慢来  发布时间:2022-07-23 21:21:06 

标签:java,多,线程,reentrantlock,互斥,锁

加锁和解锁

我们来看下ReentrantLock的基本用法

ThreadDomain35类


public class ThreadDomain35 {
 private Lock lock = new ReentrantLock();
 public void testMethod()
 {
   try
   {
     lock.lock();
     for (int i = 0; i < 2; i++)
     {
       System.out.println("ThreadName = " + Thread.currentThread().getName() + ", i = " + i);
     }
   }
   finally
   {
     lock.unlock();
   }
 }
}

线程和main方法


public class MyThread35 extends Thread {

private ThreadDomain35 td;

public MyThread35(ThreadDomain35 td)
 {
   this.td = td;
 }

public void run()
 {
   td.testMethod();
 }

public static void main(String[] args)
 {
   ThreadDomain35 td = new ThreadDomain35();
   MyThread35 mt0 = new MyThread35(td);
   MyThread35 mt1 = new MyThread35(td);
   MyThread35 mt2 = new MyThread35(td);
   mt0.start();
   mt1.start();
   mt2.start();
 }
}

输出结果


ThreadName = Thread-2, i = 0
ThreadName = Thread-2, i = 1
ThreadName = Thread-0, i = 0
ThreadName = Thread-0, i = 1
ThreadName = Thread-1, i = 0
ThreadName = Thread-1, i = 1

一个线程必须执行完才能执行下一个线程,说明ReentrantLock可以加锁。

ReentrantLock持有的对象监视器和synchronized不同

ThreadDomain37类,methodB用synchronized修饰


public class ThreadDomain37 {
 private Lock lock = new ReentrantLock();
 public void methodA()
 {
   try
   {
     lock.lock();
     System.out.println("MethodA begin ThreadName = " + Thread.currentThread().getName());
     Thread.sleep(5000);
     System.out.println("MethodA end ThreadName = " + Thread.currentThread().getName());
   }
   catch (InterruptedException e)
   {
     e.printStackTrace();
   }
   finally
   {
     lock.unlock();
   }
 }
 public synchronized void methodB()
 {
   System.out.println("MethodB begin ThreadName = " + Thread.currentThread().getName());
   System.out.println("MethodB begin ThreadName = " + Thread.currentThread().getName());
 }
}

MyThread37_0类


public class MyThread37_0 extends Thread {
 private ThreadDomain37 td;
 public MyThread37_0(ThreadDomain37 td)
 {
   this.td = td;
 }
 public void run()
 {
   td.methodA();
 }
}

MyThread37_1类


public class MyThread37_1 extends Thread {
 private ThreadDomain37 td;
 public MyThread37_1(ThreadDomain37 td)
 {
   this.td = td;
 }

public void run()
 {
   td.methodB();
 }
}

MyThread37_main方法


public class MyThread37_main {
 public static void main(String[] args)
 {
   ThreadDomain37 td = new ThreadDomain37();
   MyThread37_0 mt0 = new MyThread37_0(td);
   MyThread37_1 mt1 = new MyThread37_1(td);
   mt0.start();
   mt1.start();
 }
}

运行结果如下


MethodA begin ThreadName = Thread-0
MethodB begin ThreadName = Thread-1
MethodB begin ThreadName = Thread-1
MethodA end ThreadName = Thread-0

加了synchronized依然是异步执行,说明ReentrantLock和synchronized持有的对象监视器不同。ReentrantLock需要手动加锁和释放锁。

Condition

基本用法

synchronized与wait()和nitofy()/notifyAll()方法可以实现等待/唤醒模型,ReentrantLock同样可以,需要借助Condition的await()和signal/signalAll(),await()释放锁。

ThreadDomain38类


public class ThreadDomain38 {
 private Lock lock = new ReentrantLock();
 private Condition condition = lock.newCondition();
 public void await()
 {
   try
   {
     lock.lock();
     System.out.println("await时间为:" + System.currentTimeMillis());
     condition.await();
     System.out.println("await等待结束");
   }
   catch (InterruptedException e)
   {
     e.printStackTrace();
   }
   finally
   {
     lock.unlock();
   }
 }
 public void signal()
 {
   try
   {
     lock.lock();
     System.out.println("signal时间为:" + System.currentTimeMillis());
     condition.signal();
     System.out.println("signal等待结束");
   }
   finally
   {
     lock.unlock();
   }
 }
}

MyThread38类,线程和main方法


public class MyThread38 extends Thread
{
 private ThreadDomain38 td;

public MyThread38(ThreadDomain38 td)
 {
   this.td = td;
 }
 public void run()
 {
   td.await();
 }
 public static void main(String[] args) throws Exception
 {
   ThreadDomain38 td = new ThreadDomain38();
   MyThread38 mt = new MyThread38(td);
   mt.start();
   Thread.sleep(3000);
   td.signal();
 }
}


运行结果如下


await时间为:1563505465346
signal时间为:1563505468345
signal等待结束
await等待结束

可以看到,ReentrantLock和Condition实现了等待/通知模型。

一个Lock可以创建多个Condition;

notify()唤醒的线程是随机的,signal()可以有选择性地唤醒。

Condition选择 唤醒/等待

现在看一个利用Condition选择等待和唤醒的例子

ThreadDomain47类,定义add和sub方法


public class ThreadDomain47 {
 private final Lock lock = new ReentrantLock();

private final Condition addCondition = lock.newCondition();

private final Condition subCondition = lock.newCondition();

private static int num = 0;
 private List<String> lists = new LinkedList<String>();

public void add() {
   lock.lock();

try {
     while(lists.size() == 10) {//当集合已满,则"添加"线程等待
       addCondition.await();
     }

num++;
     lists.add("add Banana" + num);
     System.out.println("The Lists Size is " + lists.size());
     System.out.println("The Current Thread is " + "增加线程");
     System.out.println("==============================");
     this.subCondition.signal();

} catch (InterruptedException e) {
     e.printStackTrace();
   } finally {//释放锁
     lock.unlock();
   }
 }
 public void sub() {
   lock.lock();

try {
     while(lists.size() == 0) {//当集合为空时,"减少"线程等待
       subCondition.await();
     }

String str = lists.get(0);
     lists.remove(0);
     System.out.println("The Token Banana is [" + str + "]");
     System.out.println("The Current Thread is " + "减少线程");
     System.out.println("==============================");
     num--;
     addCondition.signal();

} catch (InterruptedException e) {
     e.printStackTrace();
   } finally {
     lock.unlock();
   }
 }
}

MyThread40_0类,增加线程


public class MyThread40_0 implements Runnable {
 private ThreadDomain47 task;
 public MyThread40_0(ThreadDomain47 task) {
   this.task = task;
 }
 @Override
 public void run() {
   task.add();
 }
}

MyThread40_1类,减少线程


public class MyThread40_1 implements Runnable {
 private ThreadDomain47 task;

public MyThread40_1(ThreadDomain47 task) {
   this.task = task;
 }
 @Override
 public void run() {
   task.sub();
 }
}

main方法,启动线程


public class MyThread40_main {
 public static void main(String[] args) {
   ThreadDomain47 task = new ThreadDomain47();
   Thread t1=new Thread(new MyThread40_0(task));
   Thread t3=new Thread(new MyThread40_0(task));
   Thread t7=new Thread(new MyThread40_0(task));
   Thread t8=new Thread(new MyThread40_0(task));
   Thread t2 = new Thread(new MyThread40_1(task));
   Thread t4 = new Thread(new MyThread40_1(task));
   Thread t5 = new Thread(new MyThread40_1(task));
   Thread t6 = new Thread(new MyThread40_1(task));
   t1.start();
   t2.start();
   t3.start();
   t4.start();
   t5.start();
   t6.start();
   t7.start();
   t8.start();
 }
}

输出结果如下


The Lists Size is 1
The Current Thread is 增加线程
==============================
The Lists Size is 2
The Current Thread is 增加线程
==============================
The Token Banana is [add Banana1]
The Current Thread is 减少线程
==============================
The Token Banana is [add Banana2]
The Current Thread is 减少线程
==============================
The Lists Size is 1
The Current Thread is 增加线程
==============================
The Token Banana is [add Banana1]
The Current Thread is 减少线程
==============================
The Lists Size is 1
The Current Thread is 增加线程
==============================
The Token Banana is [add Banana1]
The Current Thread is 减少线程
==============================

可以看到,lists的数量不会增加太多,也不会减少太多。当集合满,使增加线程等待,唤醒减少线程;当集合空,使减少线程等待,唤醒增加线程。我们用wait()/notify()机制无法实现该效果,这里体现了Condition的强大之处。

ReentrantLock中的方法

公平锁和非公平锁

ReentrantLock可以指定公平锁和非公平锁,公平锁根据线程运行的顺序获取锁,非公平锁则通过抢占获得锁,不按线程运行顺序。synchronized是非公平锁。在ReentrantLock(boolean fair)构造函数传入true/false来指定公平锁/非公平锁。
看个例子

ThreadDomain39类和main方法


public class ThreadDomain39 {
 private Lock lock = new ReentrantLock(true);

public void testMethod()
 {
   try
   {
     lock.lock();
     System.out.println("ThreadName" + Thread.currentThread().getName() + "获得锁");
   }
   finally
   {
     lock.unlock();
   }
 }

public static void main(String[] args) throws Exception
 {
   final ThreadDomain39 td = new ThreadDomain39();
   Runnable runnable = new Runnable()
   {
     public void run()
     {
       System.out.println("线程" + Thread.currentThread().getName() + "运行了");
       td.testMethod();
     }
   };
   Thread[] threads = new Thread[5];
   for (int i = 0; i < 5; i++)
     threads[i] = new Thread(runnable);
   for (int i = 0; i < 5; i++)
     threads[i].start();
 }
}

输出结果如下


线程Thread-0运行了
ThreadNameThread-0获得锁
线程Thread-1运行了
线程Thread-2运行了
ThreadNameThread-1获得锁
线程Thread-3运行了
线程Thread-4运行了
ThreadNameThread-2获得锁
ThreadNameThread-3获得锁
ThreadNameThread-4获得锁

可以看到公平锁获得锁的顺序和线程运行的顺序相同。公平锁尽可能地让线程获取锁的顺序和线程运行顺序保持一致,再执行几次,可能不一致。

ReentrantLock构造函数传入false,输出结果如下:


线程Thread-0运行了
线程Thread-2运行了
线程Thread-4运行了
线程Thread-3运行了
ThreadNameThread-0获得锁
线程Thread-1运行了
ThreadNameThread-1获得锁
ThreadNameThread-2获得锁
ThreadNameThread-4获得锁
ThreadNameThread-3获得锁

非公平锁获得锁的顺序和线程运行的顺序不同

getHoldCount()

获取当前线程调用lock()的次数,一般debug使用。

看个例子


public class ThreadDomain40 {
 private ReentrantLock lock = new ReentrantLock();
 public void testMethod1()
 {
   try
   {
     lock.lock();
     System.out.println("testMethod1 getHoldCount = " + lock.getHoldCount());
     testMethod2();
   }
   finally
   {
     lock.unlock();
   }
 }
 public void testMethod2()
 {
   try
   {
     lock.lock();
     System.out.println("testMethod2 getHoldCount = " + lock.getHoldCount());
   }
   finally
   {
     lock.unlock();
   }
 }
 public static void main(String[] args)
 {
   ThreadDomain40 td = new ThreadDomain40();
   td.testMethod1();
 }
}

输出结果如下


testMethod1 getHoldCount = 1
testMethod2 getHoldCount = 2

可以看到,testMethod1()被调用了一次,testMethod2()被调用了两次,ReentrantLock和synchronized一样,锁都是可重入的。

getQueueLength()和isFair()

getQueueLength()获取等待的线程数量,isFair()判断是否是公平锁。

ThreadDomain41类和main方法,Thread.sleep(2000)使第一个线程之后的线程都来不及启动,Thread.sleep(Integer.MAX_VALUE)使线程无法unlock()。


public class ThreadDomain41 {
 public ReentrantLock lock = new ReentrantLock();

public void testMethod()
 {
   try
   {
     lock.lock();
     System.out.println("ThreadName = " + Thread.currentThread().getName() + "进入方法!");
     System.out.println("是否公平锁?" + lock.isFair());
     Thread.sleep(Integer.MAX_VALUE);
   }
   catch (InterruptedException e)
   {
     e.printStackTrace();
   }
   finally
   {
     lock.unlock();
   }
 }

public static void main(String[] args) throws InterruptedException
 {
   final ThreadDomain41 td = new ThreadDomain41();
   Runnable runnable = new Runnable()
   {
     public void run()
     {
       td.testMethod();
     }
   };
   Thread[] threads = new Thread[10];
   for (int i = 0; i < 10; i++)
     threads[i] = new Thread(runnable);
   for (int i = 0; i < 10; i++)
     threads[i].start();
   Thread.sleep(2000);
   System.out.println("有" + td.lock.getQueueLength() + "个线程正在等待!");
 }
}

输出结果如下


ThreadName = Thread-1进入方法!
是否公平锁?false
有9个线程正在等待!

ReentrantLock默认是非公平锁,只有一个线程lock(),9个线程在等待。

hasQueuedThread()和hasQueuedThreads()

hasQueuedThread(Thread thread)查询指定线程是否在等待锁,hasQueuedThreads()查询是否有线程在等待锁。
看个例子

ThreadDomain41类和main方法,和上面例子类似,Thread.sleep(Integer.MAX_VALUE); 让线程不释放锁,Thread.sleep(2000);让第一个线程之后的线程都无法启动。


public class ThreadDomain42 extends ReentrantLock {
 public void waitMethod()
 {
   try
   {
     lock();
     Thread.sleep(Integer.MAX_VALUE);
   }
   catch (InterruptedException e)
   {
     e.printStackTrace();
   }
   finally
   {
     unlock();
   }
 }

public static void main(String[] args) throws InterruptedException
 {
   final ThreadDomain42 td = new ThreadDomain42();
   Runnable runnable = new Runnable()
   {
     public void run()
     {
       td.waitMethod();
     }
   };
   Thread t0 = new Thread(runnable);
   t0.start();
   Thread.sleep(500);
   Thread t1 = new Thread(runnable);
   t1.start();
   Thread.sleep(500);
   Thread t2 = new Thread(runnable);
   t2.start();
   Thread.sleep(500);
   System.out.println("t0 is waiting?" + td.hasQueuedThread(t0));
   System.out.println("t1 is waiting?" + td.hasQueuedThread(t1));
   System.out.println("t2 is waiting?" + td.hasQueuedThread(t2));
   System.out.println("Is any thread waiting?" + td.hasQueuedThreads());
 }
}

输出结果如下


t0 is waiting?false
t1 is waiting?true
t2 is waiting?true
Is any thread waiting?true

t0线程获得了锁,t0没有释放锁,导致t1,t2等待锁。

isHeldByCurrentThread()和isLocked()

isHeldByCurrentThread()判断锁是否由当前线程持有,isLocked()判断锁是否由任意线程持有。
请看示例

ThreadDomain43类和main方法


public class ThreadDomain43 extends ReentrantLock {
 public void testMethod()
 {
   try
   {
     lock();
     System.out.println(Thread.currentThread().getName() + "线程持有了锁!");
     System.out.println(Thread.currentThread().getName() + "线程是否持有锁?" +
         isHeldByCurrentThread());
     System.out.println("是否任意线程持有了锁?" + isLocked());
   } finally
   {
     unlock();
   }
 }
 public void testHoldLock()
 {
   System.out.println(Thread.currentThread().getName() + "线程是否持有锁?" +
       isHeldByCurrentThread());
   System.out.println("是否任意线程持有了锁?" + isLocked());
 }

public static void main(String[] args)
 {
   final ThreadDomain43 td = new ThreadDomain43();
   Runnable runnable0 = new Runnable()
   {
     public void run()
     {
       td.testMethod();
     }
   };
   Runnable runnable1 = new Runnable()
   {
     public void run()
     {
       td.testHoldLock();
     }
   };
   Thread t0 = new Thread(runnable0);
   Thread t1 = new Thread(runnable1);
   t0.start();
   t1.start();
 }
}

输出结果如下


Thread-0线程持有了锁!
Thread-1线程是否持有锁?false
Thread-0线程是否持有锁?true
是否任意线程持有了锁?true
是否任意线程持有了锁?true

Thread-0线程testMethod方法持有锁,Thread-1线程testHoldLock方法没有lock操作,所以不持有锁。

tryLock()和tryLock(long timeout, TimeUnit unit)

tryLock()有加锁的功能,获得了锁且锁没有被另外一个线程持有,此时返回true,否则返回false,可以有效避免死锁。tryLock(long timeout, TimeUnit unit)表示在给定的时间内获得了锁,锁没有被其他线程持有,且不处于中断状态。返回true,否则返回false;

看个例子


public class MyThread39 {
 public static void main(String[] args) {

System.out.println("开始");
   final Lock lock = new ReentrantLock();
   new Thread() {
     @Override
     public void run() {
       String tName = Thread.currentThread().getName();
       if (lock.tryLock()) {
         System.out.println(tName + "获取到锁!");
       } else {
         System.out.println(tName + "获取不到锁!");
         return;
       }
       try {
         for (int i = 0; i < 5; i++) {
           System.out.println(tName + ":" + i);
         }
         Thread.sleep(5000);
       } catch (Exception e) {
         System.out.println(tName + "出错了!");
       } finally {
         System.out.println(tName + "释放锁!");
         lock.unlock();
       }

}
   }.start();

new Thread() {
     @Override
     public void run() {
       String tName = Thread.currentThread().getName();

try {
         if (lock.tryLock(1,TimeUnit.SECONDS)) {
           System.out.println(tName + "获取到锁!");
         } else {
           System.out.println(tName + "获取不到锁!");
           return;
         }
       } catch (InterruptedException e) {
         e.printStackTrace();
       }

try {
         for (int i = 0; i < 5; i++) {
           System.out.println(tName + ":" + i);
         }

} catch (Exception e) {
         System.out.println(tName + "出错");
       } finally {
         System.out.println(tName + "释放锁!");
         lock.unlock();
       }
     }
   }.start();

System.out.println("结束");
 }
}

输出结果如下


开始
Thread-0获取到锁!
Thread-0:0
Thread-0:1
Thread-0:2
Thread-0:3
Thread-0:4
结束
Thread-1获取不到锁!
Thread-0释放锁!

Thread-0先获得了锁,且sleep了5秒,导致Thread-1获取不到锁,我们给Thread-1的tryLock设置1秒,一秒内获取不到锁就会返回false。

如果Thread.sleep(0),那么Thread-0和Thread-1都可以获得锁,园友可以自己试下。

synchronized和ReentrantLock的比较

1.synchronized关键字是语法层面的实现,ReentrantLock要手动lock()和unlock();

2.synchronized是不公平锁,ReentrantLock可以指定是公平锁还是非公平锁;

3.synchronized等待/唤醒机制是随机的,ReentrantLock借助Condition的等待/唤醒机制可以自行选择等待/唤醒;

来源:https://www.cnblogs.com/Java-Starter/p/11212079.html

0
投稿

猜你喜欢

手机版 软件编程 asp之家 www.aspxhome.com