ReentrantLock使用详解(1)之lock/unlock

在大多数实际的多线程应用中,两个或两个以上的线程需要共享对同一数据的存取。如果两个线程存取相同的对象,并且每一个线程都调用了一个修改该对象状态的方法,那么线程彼此踩了对方的脚,根据各线程访问数据的次序,可能会产生讹误的对象。这样的一种现象称之为竞争条件。

当然多个线程共享一个变量在实际的应用中有时难以避免,但是我们可以通过java提供的一些技术来避免线程彼此踩脚的行为发生。

java提供了锁机制来对多个线程共享一个变量进行同步,在Java SE 5.0引入了显式锁ReentrantLock类,以及从1.0版本开始的对象内部锁 可以通过synchronized关键字声明某个线程持有这个对象内部锁,我们这小结先学习ReentrantLock锁的使用,下一节学习synchronized关键字的使用。


(1) ReentrantLock锁的使用结构

ReentrantLock 是java.unti.concurrent包下的一个类,它的一般使用结构如下所示:

  1. public void lockMethod() {  
  2.     ReentrantLock myLock = new ReentrantLock();  
  3.     myLock.lock();  
  4.     try{  
  5.         // 受保护的代码段  
  6.         //critical section  
  7.     } finally {  
  8.         // 可以保证发生异常 锁可以得到释放 避免死锁的发生  
  9.         myLock.unlock();  
  10.     }  
  11. }  
public void lockMethod() {
	ReentrantLock myLock = new ReentrantLock();
	myLock.lock();
	try{
		// 受保护的代码段
		//critical section
	} finally {
		// 可以保证发生异常 锁可以得到释放 避免死锁的发生
		myLock.unlock();
	}
}

把解锁操作括在finally字句之内是至关重要的,如果受保护的代码抛出异常,锁可以得到释放,这样可以避免死锁的发生

我们执行下面代码:

  1. public class ReentrantLockTest1 {  
  2.       
  3.     private int num = 10;  
  4.     private ReentrantLock myLock = new ReentrantLock();  
  5.     public void writeNumMethod() {    
  6.         //myLock.lock();  
  7.         try{  
  8.             // 受保护的代码段  
  9.             int index =10;  
  10.             while(index > 0) {  
  11.                 System.out.println(Thread.currentThread().getName() + " : "+ num);  
  12.                 num-=10;  
  13.                 long beginTime = System.currentTimeMillis();  
  14.                 while(System.currentTimeMillis() - beginTime < 10){}  
  15.                 num+=10;  
  16.                 System.out.println(Thread.currentThread().getName() + " : "+ num);  
  17.                 index--;  
  18.             }  
  19.   
  20.     } finally {  
  21.             // 可以保证发生异常 锁可以得到释放 避免死锁的发生  
  22.             //myLock.unlock();  
  23.         }  
  24.     }  
  25.       
  26.     public void readNumMethod() {     
  27.         //myLock.lock();  
  28.         try{  
  29.             int index = 10;  
  30.             // 受保护的代码段  
  31.             while(index > 0) {  
  32.                 System.out.println(Thread.currentThread().getName() + " : "+ num);  
  33.                 index--;  
  34.             }  
  35.   
  36.     } finally {  
  37.             // 可以保证发生异常 锁可以得到释放 避免死锁的发生  
  38.             //myLock.unlock();  
  39.         }  
  40.     }  
  41.       
  42.       
  43.     public static void main(String [] args) {  
  44.         final ReentrantLockTest1 myLockTest = new ReentrantLockTest1();  
  45.           
  46.         Thread t1 = new Thread(new Runnable() {  
  47.             public void run() {  
  48.                 myLockTest.writeNumMethod();  
  49.             }  
  50.         },"A");  
  51.           
  52.         Thread t2 = new Thread(new Runnable() {  
  53.             public void run() {  
  54.                 myLockTest.readNumMethod();  
  55.             }  
  56.         },"B");  
  57.           
  58.         t1.start();  
  59.         t2.start();  
  60.     }  
  61. }  
public class ReentrantLockTest1 {
	
	private int num = 10;
    private ReentrantLock myLock = new ReentrantLock();
	public void writeNumMethod() {	
		//myLock.lock();
		try{
			// 受保护的代码段
			int index =10;
			while(index > 0) {
				System.out.println(Thread.currentThread().getName() + " : "+ num);
				num-=10;
				long beginTime = System.currentTimeMillis();
				while(System.currentTimeMillis() - beginTime < 10){}
				num+=10;
				System.out.println(Thread.currentThread().getName() + " : "+ num);
				index--;
			}

	} finally {
			// 可以保证发生异常 锁可以得到释放 避免死锁的发生
			//myLock.unlock();
		}
	}
	
	public void readNumMethod() {	
		//myLock.lock();
		try{
			int index = 10;
			// 受保护的代码段
			while(index > 0) {
				System.out.println(Thread.currentThread().getName() + " : "+ num);
				index--;
			}

	} finally {
			// 可以保证发生异常 锁可以得到释放 避免死锁的发生
			//myLock.unlock();
		}
	}
	
	
	public static void main(String [] args) {
		final ReentrantLockTest1 myLockTest = new ReentrantLockTest1();
		
		Thread t1 = new Thread(new Runnable() {
			public void run() {
				myLockTest.writeNumMethod();
			}
		},"A");
		
		Thread t2 = new Thread(new Runnable() {
			public void run() {
				myLockTest.readNumMethod();
			}
		},"B");
		
		t1.start();
		t2.start();
	}
}


上面示例中 writeNumMethod方法 对num变量进行先减10 然后再加10,readNumMethod方法读取num的值,线程A执行writeNumMethod方法,线程B执行readNumMethos方法,我们期望两个线程的 输出的结果都是10,但是由于我们没有对这个共享变量num 进行同步,假如此时线程A执行到num-=10这句代码时,线程的cpu时间片到时了,这时操作系统就会去调度线程B这样线程B就会输出0值。我们看一输出结果。

输出结果:

  1. A : 10  
  2. B : 10  
  3. B : 0  
  4. B : 0  
  5. B : 0  
  6. B : 0  
  7. B : 0  
  8. B : 0  
  9. B : 0  
  10. B : 0  
  11. B : 0  
  12. A : 10  
  13. A : 10  
  14. A : 10  
  15. A : 10  
  16. A : 10  
  17. A : 10  
  18. A : 10  
  19. A : 10  
  20. A : 10  
  21. A : 10  
  22. A : 10  
  23. A : 10  
  24. A : 10  
  25. A : 10  
  26. A : 10  
  27. A : 10  
  28. A : 10  
  29. A : 10  
  30. A : 10  
A : 10
B : 10
B : 0
B : 0
B : 0
B : 0
B : 0
B : 0
B : 0
B : 0
B : 0
A : 10
A : 10
A : 10
A : 10
A : 10
A : 10
A : 10
A : 10
A : 10
A : 10
A : 10
A : 10
A : 10
A : 10
A : 10
A : 10
A : 10
A : 10
A : 10


果然共享变量出现了讹误的现象。

那么如果我们取消代码中显式锁前的注释,再次运行代码。

  1. A : 10  
  2. A : 10  
  3. A : 10  
  4. A : 10  
  5. A : 10  
  6. A : 10  
  7. A : 10  
  8. A : 10  
  9. A : 10  
  10. A : 10  
  11. A : 10  
  12. A : 10  
  13. A : 10  
  14. A : 10  
  15. A : 10  
  16. A : 10  
  17. A : 10  
  18. A : 10  
  19. A : 10  
  20. A : 10  
  21. B : 10  
  22. B : 10  
  23. B : 10  
  24. B : 10  
  25. B : 10  
  26. B : 10  
  27. B : 10  
  28. B : 10  
  29. B : 10  
  30. B : 10  
A : 10
A : 10
A : 10
A : 10
A : 10
A : 10
A : 10
A : 10
A : 10
A : 10
A : 10
A : 10
A : 10
A : 10
A : 10
A : 10
A : 10
A : 10
A : 10
A : 10
B : 10
B : 10
B : 10
B : 10
B : 10
B : 10
B : 10
B : 10
B : 10
B : 10

输出结果是显式锁很好的保护了 writeNumMethod 的操作,假设线程A在执行结束前被剥夺了运行权,这是线程B调用readNumMethod方法去读取num的值,但是由于线程B不能获得锁,线程B必须等待线程A释放锁才能执行readNumMethod方法。这样就保证了writeNumMethod方法的原子性,也就保证了共享变量num不会出现讹误的现象。

(2)ReentrantLock是可重入锁

ReentrantLock持有一个所计数器,当已持有所的线程再次获得该锁时计数器值加1,每调用一次lock.unlock()时所计数器值减一,直到所计数器值为0,此时线程释放锁。

我们测试以下代码:

  1. public class ReentrantLockTest2 {  
  2.       
  3.     private ReentrantLock lock = new ReentrantLock();  
  4.       
  5.     public void testReentrantLock() {  
  6.         // 线程获得锁  
  7.         lock.lock();  
  8.         try {  
  9.             System.out.println(Thread.currentThread().getName() + " get lock");  
  10.             long beginTime = System.currentTimeMillis();  
  11.             while(System.currentTimeMillis() - beginTime < 100){}  
  12.             lock.lock();  
  13.             try {  
  14.                 System.out.println(Thread.currentThread().getName() + " get lock again");  
  15.                 long beginTime2 = System.currentTimeMillis();  
  16.                 while(System.currentTimeMillis() - beginTime2 < 100){}  
  17.             }finally {  
  18.                 // 线程释放锁  
  19.                 lock.unlock();  
  20.                 System.out.println(Thread.currentThread().getName() + " release lock");  
  21.             }  
  22.         } finally {  
  23.             // 线程释放锁  
  24.             lock.unlock();  
  25.             System.out.println(Thread.currentThread().getName() + " release lock again");  
  26.         }  
  27.     }  
  28.     public static void main(String[] args) {  
  29.         // TODO Auto-generated method stub  
  30.         final ReentrantLockTest2 test2 = new ReentrantLockTest2();  
  31.         Thread thread = new Thread(new Runnable(){  
  32.             public void run() {  
  33.                 test2.testReentrantLock();  
  34.             }  
  35.         },"A");  
  36.         thread.start();  
  37.     }  
  38.   
  39. }  
  40.   
  41. 输出结果:  
  42. A get lock  
  43. A get lock again  
  44. A release lock  
  45. A release lock again  
public class ReentrantLockTest2 {
	
	private ReentrantLock lock = new ReentrantLock();
	
	public void testReentrantLock() {
		// 线程获得锁
		lock.lock();
		try {
			System.out.println(Thread.currentThread().getName() + " get lock");
			long beginTime = System.currentTimeMillis();
			while(System.currentTimeMillis() - beginTime < 100){}
			lock.lock();
			try {
				System.out.println(Thread.currentThread().getName() + " get lock again");
				long beginTime2 = System.currentTimeMillis();
				while(System.currentTimeMillis() - beginTime2 < 100){}
			}finally {
				// 线程释放锁
				lock.unlock();
				System.out.println(Thread.currentThread().getName() + " release lock");
			}
		} finally {
			// 线程释放锁
			lock.unlock();
			System.out.println(Thread.currentThread().getName() + " release lock again");
		}
	}
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		final ReentrantLockTest2 test2 = new ReentrantLockTest2();
		Thread thread = new Thread(new Runnable(){
			public void run() {
				test2.testReentrantLock();
			}
		},"A");
		thread.start();
	}

}

输出结果:
A get lock
A get lock again
A release lock
A release lock again

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值