在 Java 中线程的状态可以分为:新建(New),运行状态(Runnable)、阻塞状态(Blocked)、等待状态(Waiting)、结束状态(Terminated)。运行状态可以转为 阻塞状态或等待状态。
在接收完基本概念后,我们看看显示锁(Lock)和内部锁(synchronized)有什么不同。
- Lock 支持更细粒度的同步控制
- Lock是无阻塞锁,synchronized 是阻塞锁。当线程A持有锁S,如果线程B也期望获取锁S,如果是显示锁则线程B进入等待状态,如果是内部锁则线程B进入阻塞状态
- Lock可以实现公平锁(默认是非公平锁,即使对公平锁而言,可轮询的tryLock仍然会插队),synchronized 只能是非公平锁
- Lock 是代码级的,synchronized 是JVM级的。
ReentrantLock实现了Lock接口,并提供了与Synchronized相同的互斥性和可见性。在获取ReentrantLock时,有着与进入同步代码块相同的内存语义,在释放ReentrantLock时同样有着与退出同步代码块相同的含义。此外还提供了一些其他的功能,包括定时的锁等待,可中断的锁等待等。
与显示锁相比,内置锁容然具有很大的优势(性能在JAVA 6.0 后与显示锁旗鼓相当)。内置锁为大家所熟悉,代码简洁紧凑。所以在一些内置锁无法满足需求的情况下,ReentrantLock可以作为一种高级工具。当需要一些高级功能时才应该使用ReentrantLock,这些功能包括:可定时的、可轮询的与可中断的锁获取操作,公平队列以及非块锁。否则,还是优先考虑使用内置锁。因为 synchronized 是JVM的内置属性,在未来优化的可能性更高。
import java.util.Calendar;
import java.util.concurrent.TimeUnit;
/**
*
* @author zhangwei_david
* @version $Id: Task.java, v 0.1 2014年10月23日 下午10:26:05 zhangwei_david Exp $
*/
public class Task {
public void doSomething() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (Exception e) {
}
StringBuffer strBuffer = new StringBuffer();
strBuffer.append("线程名称:" + Thread.currentThread().getName());
strBuffer.append(", 执行时间 :" + Calendar.getInstance().get(13) + "s");
System.out.println(strBuffer.toString());
}
}
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
*
* @author zhangwei_david
* @version $Id: TaskWithLock.java, v 0.1 2014年10月23日 下午10:29:47 zhangwei_david Exp $
*/
public class TaskWithLock extends Task implements Runnable {
Lock lock = new ReentrantLock();
/**
* @see java.lang.Runnable#run()
*/
public void run() {
try {
lock.lock();
doSomething();
} finally {
lock.unlock();
}
}
}
/**
*
* @author zhangwei_david
* @version $Id: TaskWithSync.java, v 0.1 2014年10月23日 下午10:31:33 zhangwei_david Exp $
*/
public class TaskWithSync extends Task implements Runnable {
/**
* @see java.lang.Runnable#run()
*/
public void run() {
synchronized ("A") {
doSomething();
}
}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
*
* @author zhangwei_david
* @version $Id: TaskTest.java, v 0.1 2014年10月23日 下午10:32:07 zhangwei_david Exp $
*/
public class TaskTest {
public static void runTask(Class<? extends Runnable> clz) throws InterruptedException,
InstantiationException,
IllegalAccessException {
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\n");
es.shutdown();
}
/**
*
* @param args
* @throws Exception
* @throws
* @throws
*/
public static void main(String[] args) throws Exception {
runTask(TaskWithLock.class);
runTask(TaskWithSync.class);
}
}
运行的结果是:
*** 开始执行 TaskWithLock 任务****
线程名称:pool-1-thread-3, 执行时间 :24s
线程名称:pool-1-thread-2, 执行时间 :24s
线程名称:pool-1-thread-1, 执行时间 :24s
---------TaskWithLock-----------任务执行完毕!
*** 开始执行 TaskWithSync 任务****
线程名称:pool-2-thread-1, 执行时间 :34s
线程名称:pool-2-thread-3, 执行时间 :36s
线程名称:pool-2-thread-2, 执行时间 :38s
---------TaskWithSync-----------任务执行完毕!
从结果中可以发现Lock 没有生效,到底是什么呢?因为Lock 是代码级的锁,属于一个对象,定义一个私有的锁是不会起到同步的作用的。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
*
* @author zhangwei_david
* @version $Id: TaskWithLock.java, v 0.1 2014年10月23日 下午10:29:47 zhangwei_david Exp $
*/
public class TaskWithLock extends Task implements Runnable {
private static final TaskWithLock instance = new TaskWithLock();
/**
* Getter method for property <tt>instance</tt>.
*
* @return property value of instance
*/
public static TaskWithLock getInstance() {
return instance;
}
Lock lock = new ReentrantLock();
/**
* @see java.lang.Runnable#run()
*/
public void run() {
try {
lock.lock();
doSomething();
} finally {
lock.unlock();
}
}
}
/**
*
* @author zhangwei_david
* @version $Id: TaskWithSync.java, v 0.1 2014年10月23日 下午10:31:33 zhangwei_david Exp $
*/
public class TaskWithSync extends Task implements Runnable {
/**
* @see java.lang.Runnable#run()
*/
public void run() {
synchronized ("A") {
doSomething();
}
}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
*
* @author zhangwei_david
* @version $Id: TaskTest.java, v 0.1 2014年10月23日 下午10:32:07 zhangwei_david Exp $
*/
public class TaskTest {
public static void runTask(Runnable task) throws InterruptedException, InstantiationException,
IllegalAccessException {
ExecutorService es = Executors.newCachedThreadPool();
System.out.println("*** 开始执行 " + task.getClass().getSimpleName() + " 任务****");
for (int i = 0; i < 3; i++) {
es.submit(task);
}
TimeUnit.SECONDS.sleep(10);
System.out
.println("---------" + task.getClass().getSimpleName() + "-----------任务执行完毕!\n\n");
es.shutdown();
}
/**
*
* @param args
* @throws Exception
* @throws
* @throws
*/
public static void main(String[] args) throws Exception {
runTask(TaskWithLock.getInstance());
runTask(new TaskWithSync());
}
}
结果是:
*** 开始执行 TaskWithLock 任务****
线程名称:pool-1-thread-2, 执行时间 :41s
线程名称:pool-1-thread-1, 执行时间 :43s
线程名称:pool-1-thread-3, 执行时间 :45s
---------TaskWithLock-----------任务执行完毕!
*** 开始执行 TaskWithSync 任务****
线程名称:pool-2-thread-2, 执行时间 :51s
线程名称:pool-2-thread-3, 执行时间 :53s
线程名称:pool-2-thread-1, 执行时间 :55s
---------TaskWithSync-----------任务执行完毕!
可以发现只有锁本身是共享的时候才可以进行代码的同步控制。
如果将Lock改成这样:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
*
* @author zhangwei_david
* @version $Id: TaskWithLock.java, v 0.1 2014年10月23日 下午10:29:47 zhangwei_david Exp $
*/
public class TaskWithLock extends Task implements Runnable {
private static final TaskWithLock instance = new TaskWithLock();
/**
* Getter method for property <tt>instance</tt>.
*
* @return property value of instance
*/
public static TaskWithLock getInstance() {
return instance;
}
Lock lock = new ReentrantLock(true);
/**
* @see java.lang.Runnable#run()
*/
public void run() {
try {
lock.lock();
doSomething();
} finally {
lock.unlock();
}
}
}
运行的结果是:
*** 开始执行 TaskWithLock 任务****
线程名称:pool-1-thread-1, 执行时间 :34s
线程名称:pool-1-thread-2, 执行时间 :36s
线程名称:pool-1-thread-3, 执行时间 :38s
---------TaskWithLock-----------任务执行完毕!
*** 开始执行 TaskWithSync 任务****
线程名称:pool-2-thread-1, 执行时间 :44s
线程名称:pool-2-thread-3, 执行时间 :46s
线程名称:pool-2-thread-2, 执行时间 :48s
---------TaskWithSync-----------任务执行完毕!
我们可以发现Lock的锁是公平的,而synchronized 是非公平的。(公平是指JVM优先选择等待时间最长的线程持有锁,非公平是指随机选择)
在激烈竞争的情况下,非公平锁的性能远远高于公平锁,原因是:恢复一个被挂起的线程与该线程真正开始运行之间存在着验证的延迟。 如: 线程B请求线程A持有的锁,由于锁已经被A持有B被挂起,A释放锁,C请求锁;如果是非公平的锁,此时C可以持有锁,线程B被唤醒,B唤醒时C已经释放锁,B正好可以持有锁。公平锁,就必须等待B释放,C 才可以获取锁。
Synchronized 重入(Reentrant)
当某个线程请求一个由其他线程持有的锁时,发出的请求的线程就会阻塞。然而,由于内置锁(Intrinsic Lock)是可重入的,因此如果该线程试图获取一个已经由他持有锁,那么这个请求就会成功。重入意味着获取锁的操作粒度是一个线程而不是调用。
/**
*
* @author zhangwei_david
* @version $Id: Parent.java, v 0.1 2015年2月4日 下午5:28:43 zhangwei_david Exp $
*/
public class Parent {
public synchronized void doParent() {
System.out.println("父类获取锁,进入同步代码块");
}
}
/**
*
* @author zhangwei_david
* @version $Id: Child.java, v 0.1 2015年2月4日 下午5:30:11 zhangwei_david Exp $
*/
public class Child extends Parent {
public synchronized void doChild() {
System.out.println("子类获取锁,进入同步代码块");
super.doParent();
}
}
测试类:
/**
*
* @author zhangwei_david
* @version $Id: Client.java, v 0.1 2015年2月4日 下午5:31:31 zhangwei_david Exp $
*/
public class Client {
public static void main(String[] args) {
Child child = new Child();
child.doChild();
}
}
结果是:
子类获取锁,进入同步代码块
父类获取锁,进入同步代码块