文章目录
在Java中,要实现线程间的同步,有两种方式,一种是使用Java的 关键字Synchronized,另一种是使用 Lock接口的实现类,那么两种方式究竟有哪些异同,在使用时应该如何选择呢,下文对两种方式的异同做一个简单的分析,帮助理解和使用时抉择。
synchronized与Lock的对比分析
为了便于分析,我们构建一个小程序,自己构造一个Lock,然后和synchronized进行比较,解析以注释的形式写在代码中
工程结构
App.java --用于测试的主类
CustomLock.java --自定义的一个Lock
CustomLockAnalysis.java --用于分析Lock的分析类
SynchronizedAnalysis.java --用于分析synchronized的分析类
App.java
public class App {
public static void main(String[] args) {
try {
String option = "lockTest4";
if ("sychronizedTest1".equals(option)) {
// synchronized缺点一:synchronized无法控制阻塞时长,后面的进程只能阻塞等待直到当前进程执行完毕释放锁
SynchronizedAnalysis analysis = new SynchronizedAnalysis();
new Thread(analysis::sychMMethod, "T1").start();
TimeUnit.MILLISECONDS.sleep(2);
new Thread(analysis::sychMMethod, "T2").start();
} else if ("sychronizedTest2".equals(option)) {
// synchronized缺点二:当一个线程因争抢资源而进入阻塞状态时,不能被打断,可以对其调用interrupt,但会在该线程进入running状态后才会生效,而不是我们期望的立即中断执行
SynchronizedAnalysis analysis = new SynchronizedAnalysis();
Thread t1 = new Thread(analysis::sychMMethod, "T1");
t1.start();
TimeUnit.MILLISECONDS.sleep(2);
Thread t2 = new Thread(analysis::sychMMethod, "T2");
t2.start();
TimeUnit.MILLISECONDS.sleep(2);
t2.interrupt();
System.out.println(t2.isInterrupted());// 被打断的状态会立即改变,但打断不会立即生效
System.out.println(t2.getState());
} else if ("lockTest1".equals(option)) {
// 因为lockMethod并没有被synchronized修饰,所以可以被立即打断
CustomLockAnalysis test = new CustomLockAnalysis();
Thread t1 = new Thread(test::lockMethod, "T1");
t1.start();
TimeUnit.MICROSECONDS.sleep(10);
t1.interrupt();// 打断会立即生效
Thread t2 = new Thread(test::lockMethod, "T2");
t2.start();
} else if ("lockTest2".equals(option)) {
// 因为lockMethod并没有被synchronized修饰,所以可以被立即打断
CustomLockAnalysis test = new CustomLockAnalysis();
Thread t1 = new Thread(test::lockMillsMethod, "T1");
t1.start();
TimeUnit.MILLISECONDS.sleep(10);
Thread t2 = new Thread(test::lockMillsMethod, "T2");
t2.start();
} else if ("lockTest3".equals(option)) {
// tryLock方法,当无法获取资源锁时,线程会立即往下执行,然后根据返回的结果来判断是否进行同步操作
CustomLockAnalysis test = new CustomLockAnalysis();
Thread t1 = new Thread(test::tryLockMethod, "T1");
t1.start();
t1.interrupt();
TimeUnit.MILLISECONDS.sleep(10);
Thread t2 = new Thread(test::tryLockMethod, "T2");
t2.start();
Thread t3 = new Thread(test::tryLockMethod, "T3");
t3.start();
} else if ("lockTest4".equals(option)) {
// tryLock方法,当无法获取资源锁时,线程会立即往下执行,然后根据返回的结果来判断是否进行同步操作
CustomLockAnalysis test = new CustomLockAnalysis();
Thread t1 = new Thread(test::tryLockMillsMethod, "T1");
t1.start();
TimeUnit.MILLISECONDS.sleep(10);
Thread t2 = new Thread(test::tryLockMillsMethod, "T2");
t2.start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
CustomLock.java
public class CustomLock implements Lock {
private Thread currentThread;
private boolean locked = false;
private List<Thread> blockedList = new ArrayList<>();
public List<Thread> getBlockedThreads() {
return blockedList;
}
@Override
public void lock() {
synchronized (this) {
while (locked) {
blockedList.add(Thread.currentThread());
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
blockedList.remove(Thread.currentThread());
locked = true;
currentThread = Thread.currentThread();
}
}
/**
* 附带超时功能的lock
*
* @param timeout 超时时间,单位ms
* @throws InterruptedException
*/
public void lock(long timeout) throws InterruptedException {
synchronized (this) {
long remainMills = timeout;
long endMills = new Date().getTime() + timeout;
while (locked) {
if (remainMills <= 0) {
throw new InterruptedException(
Thread.currentThread() + " can not get the lock during " + timeout + " ms");
}
if (!blockedList.contains(Thread.currentThread())) {
blockedList.add(Thread.currentThread());
this.wait(timeout);// 如果当前资源锁已经被其它线程获取,则等待timeout时间后再次尝试
remainMills = endMills - new Date().getTime();
}
}
blockedList.remove(Thread.currentThread());
locked = true;
currentThread = Thread.currentThread();
}
}
@Override
public void lockInterruptibly() throws InterruptedException {
// TODO Auto-generated method stub
}
/**
* 当获取锁时发现该锁被其它线程占用,则立即返回false,否则占用该锁 即使无法获取资源锁,tryLock也不会将该线程放进wait
* set中,而是立即返回,从而避免线程的阻塞
*/
@Override
public boolean tryLock() {
synchronized (this) {
if (!locked) {
locked = true;
currentThread = Thread.currentThread();
return true;
}
return false;
}
}
/**
* 基本与lock(long timeout)一致,不同是当给定时间无法获取到资源锁时,不会抛出打断异常 而是返回false
*
* @throws InterruptedException
*/
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
synchronized (this) {
long timeout = TimeUnit.MILLISECONDS.convert(time, unit);
long remainMills = timeout;
long endMills = new Date().getTime() + timeout;
while (locked) {
if (remainMills <= 0) {
return false;
}
if (!blockedList.contains(Thread.currentThread())) {
blockedList.add(Thread.currentThread());
this.wait(timeout);// 如果当前资源锁已经被其它线程获取,则等待timeout时间后再次尝试
remainMills = endMills - new Date().getTime();
}
}
blockedList.remove(Thread.currentThread());
locked = true;
currentThread = Thread.currentThread();
return true;
}
}
@Override
public void unlock() {
// 注意,wait()、notify()、notifyAll()只能在某线程获得了该资源锁的情况下使用,即只能用于sychronized中
synchronized (this) {
// 判断解锁的线程是否是加锁的线程,只有加锁的线程才有资格解锁
if (currentThread == Thread.currentThread()) {
locked = false;// 将锁状态置为false
this.notifyAll();// 唤醒该资源的wait set中的所有线程
}
}
}
@Override
public Condition newCondition() {
// TODO Auto-generated method stub
return null;
}
}
CustomLockAnalysis.java
public class CustomLockAnalysis {
private final CustomLock lock = new CustomLock();
public void lockMethod() {
try {
lock.lock();// 加锁
System.out.println(Thread.currentThread() + ": get the lock");
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void lockMillsMethod() {
try {
lock.lock(2000);// 加锁
System.out.println(Thread.currentThread() + ": get the lock");
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void tryLockMethod() {
try {
boolean isGetLock = lock.tryLock();
if (isGetLock) {
System.out.println(Thread.currentThread() + ": get the lock");
System.out.println("do something sychronized");
Thread.sleep(60000);
} else {
System.out.println(Thread.currentThread() + ": can't get the lock");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void tryLockMillsMethod() {
try {
boolean isGetLock = lock.tryLock(60000, TimeUnit.MILLISECONDS);
if (isGetLock) {
System.out.println(Thread.currentThread() + ": get the lock");
System.out.println("do something sychronized");
Thread.sleep(10000);
} else {
System.out.println(Thread.currentThread() + ": can't get the lock");
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
SynchronizedAnalysis.java
public class SynchronizedAnalysis {
public synchronized void sychMMethod() {
try {
System.out.println(Thread.currentThread().getName() + ": " + new Date().getTime());
TimeUnit.MINUTES.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
总结
synchronized在使用中有两个缺点:
- synchronized无法控制阻塞时长,后面的进程只能阻塞等待直到当前进程执行完毕释放锁;
- 当一个线程因争抢资源而进入阻塞状态时,不能被打断,可以对其调用interrupt,但会在该线程进入running状态后才会生效,而不是我们期望的立即中断执行;
所以Lock是在此基础上对其进行了改进,使用时可以控制阻塞时长,可以对其打断,也可以在获取不到锁时立即返回并继续执行。
在需求简单,性能要求不高时,可以选择synchronized关键字进行同步控制,毕竟synchronized的语义非常明确,便于维护,反之则可以考虑使用Lock。
这里的Lock只是为了方便分析对比而自己构造的,实际上jdk中提供了很多各式各样的Lock,可以具体了解后根据实际情况选用。
学习源码
https://gitee.com/imdongrui/study-repo.git
仓库中的lock工程