相比synchronized方式进行线程同步和互斥
Java5 提供了另外一种线程同步或互斥的机制,即使用Lock和Condition。
1. 普通锁ReentrantLock ,不区分读写,和synchronized效果一致
public class LockTest {
Outputer outputer = new Outputer();
public static void main(String[] args) {
new LockTest().init();
}
private void init() {
new Thread(runnable).start();
new Thread(runnable2).start();
}
Runnable runnable = new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
outputer.output("aaaaaaaaaaaaaaaaaaaaa");
}
}
};
Runnable runnable2 = new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
outputer.output("bbbbbbbbbbbbbbbbbbbbbbbb");
}
}
};
static class Outputer {
// 锁对象
private Lock lock = new ReentrantLock();
public void output(String name) {
// 加锁,用来互斥方法块
lock.lock();
try {
System.out.println("be ready to output data!");
Thread.sleep(new Random().nextInt(10000));
System.out.println("start to output data! " + name);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 解锁,使用try{}保证,一定会解锁
lock.unlock();
}
}
}
}
看下输出结果
be ready to output data!
start to output data! bbbbbbbbbbbbbbbbbbbbbbbb
be ready to output data!
start to output data! aaaaaaaaaaaaaaaaaaaaa
be ready to output data!
start to output data! bbbbbbbbbbbbbbbbbbbbbbbb
be ready to output data!
start to output data! aaaaaaaaaaaaaaaaaaaaa
be ready to output data!
下面看下注释掉Lock代码后输出的结果
be ready to output data!
be ready to output data!
start to output data! aaaaaaaaaaaaaaaaaaaaa
be ready to output data!
start to output data! bbbbbbbbbbbbbbbbbbbbbbbb
be ready to output data!
start to output data! aaaaaaaaaaaaaaaaaaaaa
be ready to output data!
start to output data! aaaaaaaaaaaaaaaaaaaaa
be ready to output data!
2. 读写锁ReentrantReadWriteLock, 分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,写锁与写锁互斥,这是由JVM控制的,我们只需要上好相应的锁即可。一般情况下,读写锁的性能都会比排它锁要好,因为大多数场景读是多于写的。在读多于写的情况下,读写锁能够提供比排它锁更好的并发性和吞吐量。
public class ReadWriteLockTest {
public static void main(String[] args) {
final MyQueue queue = new MyQueue();
for (int i = 0; i < 3; i++) {
new Thread() {
public void run() {
while (true) {
queue.getData();
}
}
}.start();
new Thread() {
public void run() {
while (true) {
queue.setData(new Random().nextInt(10000) + "");
}
}
}.start();
}
}
static class MyQueue {
private String str = "";
private ReadWriteLock rwLock = new ReentrantReadWriteLock();
private void getData() {
rwLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName()
+ " be ready to read data!");
Thread.sleep((long) (Math.random() * 1000));
System.out.println(Thread.currentThread().getName()
+ " have read data :" + str);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
rwLock.readLock().unlock();
}
}
private void setData(String data) {
rwLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()
+ " be ready to write data!");
Thread.sleep((long) (Math.random() * 1000));
this.str = data;
System.out.println(Thread.currentThread().getName()
+ " have write data: " + data);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
rwLock.writeLock().unlock();
}
}
}
}
看下Log输出
// 读线程可以同时访问
Thread-0 be ready to read data!
Thread-2 be ready to read data!
Thread-2 have read data :
Thread-0 have read data :
Thread-1 be ready to write data!
Thread-1 have write data: 3504
Thread-1 be ready to write data!
Thread-1 have write data: 1910
Thread-1 be ready to write data!
Thread-1 have write data: 6865
// 写线程不可以同时访问
Thread-1 be ready to write data!
Thread-1 have write data: 7689
Thread-3 be ready to write data!
Thread-3 have write data: 1052
Thread-4 be ready to read data!
Thread-4 have read data :1052
Thread-5 be ready to write data!
3. 锁降级
锁降级指的是写锁降级成为读锁。如果当前线程拥有写锁,然后将其释放,最后再获取读锁,这种分段完成的过程不能称之为锁降级。锁降级是指把持住(当前拥有的)写锁,再获取到读锁,随后释放(先前拥有的)写锁的过程。
接下来看一个锁降级的示例:因为数据不常变化,所以多个线程可以并发的进行数据处理,当数据变更后,当前线程如果感知到数据变化,则进行数据的准备工作,同时其他处理线程被阻塞,直到当前线程完成数据的准备工作。
public void processData() {
readLock.lock();
if (!update) {
// 必须先释放读锁
readLock.unlock();
// 锁降级从写锁获取到开始
writeLock.lock();
try {
if (!update) {
// 准备数据的流程(略)
update = true;
}
readLock.lock();
} finally {
writeLock.unlock();
}
// 锁降级完成,写锁降级为读锁
}
try {
// 使用数据的流程(略)
} finally {
readLock.unlock();
}
}
上述示例中,当数据发生变更后,update变量(布尔类型且Volatile修饰)被设置为false,此时所有访问processData() 方法的线程都能够感知到变化,但只有一个线程能够获取到写锁,而其他线程会被阻塞在读锁和写锁的lock()方法上。当前程获取写锁完成数据准备之后,再获取读锁,随后释放写锁,完成锁降级。
锁降级中读锁的获取是否必要呢?答案是必要的。主要原因是保证数据的可见性,如果当前线程不获取读锁而是直接释放写锁,假设此刻另一个线程(记作 线程T)获取了写锁并修改了数据,则当前线程无法感知线程T的数据更新。如果当前线程获取读锁,即遵循锁降级的步骤,则线程T将会被阻塞,直到当前线程使 用数据并释放读锁之后,线程T才能获取写锁进行数据更新。