在JDK5的并发包中有一个子包为java.concurrent.locks,它下面定义了三个接口Lock,ReadWriteLock,Condition,分别为重入锁,读写锁,锁条件判断
LOCK:
Lock与java关键字(synchronized)具有相同的功能,但它更加灵活。ReentrantLock作为Lock接口的实现类,被作为Java语言中synchronized功能的替代,它们具有相同的内存意义,相同的锁定,但在争用条件下却有更好的性能,此为它还有synchronized没有提供的其它特性。但就实际应用来说,由于内部锁是内置于Java虚拟机中的,它能够进行优化,因此未来的性能改进可能更倾向于内部锁,而不是重入锁。综上所述,除非你的应用程序需要发在Java 5.0上,或者需要使用重入锁的可伸缩性,否则就应该选择内部锁。
总之,ReentrantLock锁与Java内在锁相比有下面的特点:
1)ReentrantLock必须在 finally 块中释放锁,而使用synchronized同步,JVM 将确保锁会获得自动释放。
2)与目前的 synchronized 实现相比,争用下的 ReentrantLock 实现更具可伸缩性。
3)对于ReentrantLock ,可以有不止一个条件变量与它关联。(Condition后面会说到)
4)允许选择想要一个公平锁,还是一个不公平锁。(new ReentrantLock(false)非公平锁,非公平锁执行效率会更高)
5)除非你对 Lock 的某个高级特性有明确的需要,或者有明确的证据表明在特定情况下,同步已经成为可伸缩性的瓶颈,否则还是应当继续使用synchronized。
6)Lock 类只是普通的类,JVM 不知道具体哪个线程拥有 Lock 对象。而且,几乎每个开发人员都熟悉 synchronized,它可以在 JVM 的所有版本中工作。
Condition:
类似于java中原来线程交互所用的wait,notify和notifyAll方法在新的并发包中基于重入锁机制引入了Condition接口,Condition
将 Object
监视器方法(wait
、notify
和 notifyAll
)分解成截然不同的对象,以便通过将这些对象与任意 Lock
实现组合使用,为每个对象提供多个等待 set(wait-set),就是多路等待。Condition 的方法与 wait 、notify 和 notifyAll 方法类似,分别命名为 await 、 signal和singalAll因为它们不能覆盖Object上的对应方法。
下面是一个多路条件等待按顺序执行的例子,给定一个0,按1,2,3,这样的顺序依次加一次,总共加10次,并输出每次加后的结果,模拟让多线程来做:
1 import java.util.concurrent.locks.Condition; 2 import java.util.concurrent.locks.Lock; 3 import java.util.concurrent.locks.ReentrantLock; 4 5 public class ConditionTest { 6 7 /** 8 * @param args 9 */ 10 public static void main(String[] args) { 11 for (int i = 0; i < 10; i++) { 12 new Thread(new Runnable() { 13 14 @Override 15 public void run() { 16 new Test1().sub1(); 17 18 } 19 }).start(); 20 21 new Thread(new Runnable() { 22 23 @Override 24 public void run() { 25 new Test1().sub2(); 26 27 } 28 }).start(); 29 30 new Thread(new Runnable() { 31 32 @Override 33 public void run() { 34 new Test1().sub3(); 35 36 } 37 }).start(); 38 } 39 } 40 41 } 42 43 class Test1 { 44 public static int i = 0; 45 public static int conNum = 1; 46 //此处注意锁和condition也要定义成静态的,不然就有多把锁和condition了,相当于没加一样。 47 public static Lock lock = new ReentrantLock(false); 48 public static Condition con1 = lock.newCondition(); 49 public static Condition con2 = lock.newCondition(); 50 public static Condition con3 = lock.newCondition(); 51 52 // 方法1对i进行加1 53 public void sub1() { 54 lock.lock(); 55 try { 56 while (conNum != 1) { 57 try { 58 con1.await(); 59 } catch (InterruptedException e) { 60 e.printStackTrace(); 61 } 62 } 63 i++; 64 System.out.print(i + ","); 65 conNum = 2; 66 con2.signal(); 67 } finally { 68 lock.unlock(); 69 } 70 71 } 72 73 // 方法2对i加2 74 public void sub2() { 75 lock.lock(); 76 77 try { 78 while (conNum != 2) { 79 try { 80 con2.await(); 81 } catch (InterruptedException e) { 82 e.printStackTrace(); 83 } 84 } 85 i = i + 2; 86 System.out.print(i + ","); 87 conNum = 3; 88 con3.signal(); 89 } finally { 90 lock.unlock(); 91 } 92 93 } 94 95 // 方法3对i加3; 96 public void sub3() { 97 lock.lock(); 98 try { 99 while (conNum != 3) { 100 try { 101 con3.await(); 102 } catch (InterruptedException e) { 103 e.printStackTrace(); 104 } 105 } 106 i = i + 3; 107 System.out.print(i + ","); 108 conNum = 1; 109 con1.signal(); 110 } finally { 111 lock.unlock(); 112 } 113 114 } 115 116 }
ReadWriteLock:
ReadWriteLock 维护了一对相关的锁,一个用于只读操作,另一个用于写入操作。只要没有 writer,读取锁可以由多个 reader 线程同时保持。写入锁是独占的。从理论上说,使用读写锁所允许的并发性增强将带来更大的性能提高。与互斥锁相比,使用读-写锁能否提升性能则取决于读写操作期间读取数据相对于修改数据的频率,以及数据的争用——即在同一时间试图对该数据执行读取或写入操作的线程数。
下面一个示例,模拟各有10个线程对数据源数据进行读写,从输出结果可以看出实现了有序的读写
1 public class LockTest { 2 /** 3 * @param args 4 */ 5 public static void main(String[] args){ 6 //10个读线程 7 for(int i=0;i<10;i++){ 8 new Thread(new Runnable() { 9 @Override 10 public void run() { 11 System.out.println(Thread.currentThread().getName()+"读取到的数据为:"+new Data().get()); 12 } 13 }).start(); 14 } 15 //10个写线程 16 for(int i=0;i<10;i++){ 17 final int num = i; 18 new Thread(new Runnable() { 19 @Override 20 public void run() { 21 new Data().set("world"+num); 22 } 23 }).start(); 24 } 25 } 26 27 28 29 } 30 //数据源 31 class Data{ 32 private static String name="hello"; 33 ReadWriteLock rwl = new ReentrantReadWriteLock(); 34 public String get(){ 35 rwl.readLock().lock(); 36 System.out.println(Thread.currentThread().getName()+"--读取数据前:"); 37 38 try{try { 39 Thread.sleep(new Random().nextInt(3000)); 40 } catch (InterruptedException e) { 41 e.printStackTrace(); 42 } 43 return name; 44 }finally{ 45 System.out.println(Thread.currentThread().getName()+"--已经读取完"); 46 rwl.readLock().unlock(); 47 } 48 49 } 50 51 public void set(String name){ 52 rwl.writeLock().lock(); 53 try{ 54 System.out.println(Thread.currentThread().getName()+">写入数据前"); 55 try { 56 Thread.sleep(new Random().nextInt(3000)); 57 } catch (InterruptedException e) { 58 e.printStackTrace(); 59 } 60 this.name = name; 61 System.out.println(Thread.currentThread().getName()+">写完数据"); 62 }finally{ 63 rwl.writeLock().unlock(); 64 } 65 66 } 67 }