很多编码者都会说,Lock类和synchronized关键字用在代码块的并发性和内存上时语义是一样的,都是保持代码块同时只有
一个线程具有执行权。这样的说法只对了一半,我们以一个任务提交给多个线程运行为例,来看看使用显示锁(Lock类)和
内部锁(synchronized关键字)有什么不同。首先定义一个任务:
import java.util.Calendar;
public class Task {
public void doSomething(){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
StringBuffer sb = new StringBuffer();
sb.append("线程名称:"+Thread.currentThread().getName());
sb.append(",执行时间:"+Calendar.getInstance().get(13)+"s");
System.out.println(sb);
}
}
显示锁
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class TaskWithLock extends Task implements Runnable{
private final Lock lock = new ReentrantLock();
@Override
public void run() {
// TODO Auto-generated method stub
try{
lock.lock();
doSomething();
}finally{
lock.unlock();
}
}
}
内部锁任务
class TaskWithSync extends Task implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
synchronized("A"){
doSomething();
}
}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class TestThread {
/**
* @param args
*/
public static void main(String[] args) throws Exception{
// TODO Auto-generated method stub
runTasks(TaskWithLock.class);
runTasks(TaskWithSync.class);
}
public static void runTasks(Class<? extends Runnable> clz) throws Exception{
ExecutorService es = Executors.newCachedThreadPool();
System.out.println("***开始执行"+clz.getSimpleName()+"任务****");
for(int i=0;i<3;i++){
es.submit(clz.newInstance());
}
TimeUnit.SECONDS.sleep(10);
System.out.println("------"+clz.getSimpleName()+"任务执行完毕-----\n");
es.shutdown();
}
}
开始执行TaskWithLock任务*
线程名称:pool-1-thread-1,执行时间:10s
线程名称:pool-1-thread-3,执行时间:10s
线程名称:pool-1-thread-2,执行时间:10s
------TaskWithLock任务执行完毕-----
开始执行TaskWithSync任务*
线程名称:pool-2-thread-1,执行时间:20s
线程名称:pool-2-thread-3,执行时间:22s
线程名称:pool-2-thread-2,执行时间:24s
------TaskWithSync任务执行完毕-----
注意看运行时间戳,显示锁是同时运行的,很显然在pool-1-thread-1线程执行到sleep时,其他两个线程也会运行到这里,
一起等待,然后一起输出,这还具有线程互斥的概念吗?
而内部锁的输出则是我们预期结果:pool-2-thread-1线程在运行时其他线程处于等待状态,pool-2-thread-1执行完毕后,
JVM从等待线程池中随机获得一个线程pool-2-thread-3执行,最后再执行pool-2-thread-2,这正是我们希望的。
Lock锁为什么不出现互斥情况呢?
这是因为对于同步资源来说(示例中是代码块),显示锁是对象级别的锁,而内部锁是类级别的锁,也就是说Lock锁是跟随
对象的,synchronized锁是跟随 类的,更简单地说把Lock定义为多线程的私有属性是起不到资源互斥作用的,除非把Lock定
义为所有线程的共享变量。
public static void main(String[] args) throws Exception{
// TODO Auto-generated method stub
final Lock lock = new ReentrantLock();
for(int i=0;i<3;i++){
new Thread(new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
try {
lock.lock();
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
lock.unlock();
}
}
}).start();
}
}
线程名称Thread-0、Thread-1、Thread-2会逐渐输出,也就是一个线程在执行时,其他线程就处于等待状态。这里三个线程
运行的实例对象是同一个类(都是Client$1类的实例)。
(1)Lock支持更细粒度的锁控制
假设读写锁分离,写操作时不允许有读写操作存在,而读操作时读写操作可以并发执行,这一点内部锁就很难实现。显示锁
士例代码如下:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class Foo {
//可重入的读写锁
private final ReentrantReadWriteLock rw1 = new ReentrantReadWriteLock();
//读锁
private final Lock r = rw1.readLock();
//写锁
private final Lock w = rw1.writeLock();
//读操作,可并发执行
public void read(){
try {
r.lock();
Thread.sleep(1000);
System.out.println("read......");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
r.unlock();
}
}
//写操作,同时只允许一个写操作
public void write(Object _obj){
try {
w.lock();
Thread.sleep(1000);
System.out.println("writing......");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
w.unlock();
}
}
}
(2)Lock是无阻塞锁,synchronized是阻塞锁
当线程A持有锁时,线程B也期望获得锁,此时,如果程序中使用的是显示锁,则B线程为等待状态(在通常的描述中,也认为
此线程被阻塞了),若使用的是内部锁则为阻塞状态。
(3)Lock可实现公平锁,synchronized只能是非公平锁
(4)Lock是代码级的,synchronized是JVM级的
Lock是通过编码实现的,synchronized是在运行期由JVM解释的,相对来说synchronized的优化可能性更高,毕竟是在最核心
部分支持的,Lock的优化则需要用户自行考虑。
显示锁和内部锁的功能各不相同,在性能上也稍有差别,但随着JDK的不断推进,相对来说,显式锁使用起来更加便利和强大
,在实际开发中选择哪种类型的锁就需要根据实际情况考虑了:灵活、强大则选择Lock,-快捷、安全则选择synchronized。