synchronized与Lock的简单对比分析


在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在使用中有两个缺点:

  1. synchronized无法控制阻塞时长,后面的进程只能阻塞等待直到当前进程执行完毕释放锁;
  2. 当一个线程因争抢资源而进入阻塞状态时,不能被打断,可以对其调用interrupt,但会在该线程进入running状态后才会生效,而不是我们期望的立即中断执行;

所以Lock是在此基础上对其进行了改进,使用时可以控制阻塞时长,可以对其打断,也可以在获取不到锁时立即返回并继续执行。

在需求简单,性能要求不高时,可以选择synchronized关键字进行同步控制,毕竟synchronized的语义非常明确,便于维护,反之则可以考虑使用Lock。

这里的Lock只是为了方便分析对比而自己构造的,实际上jdk中提供了很多各式各样的Lock,可以具体了解后根据实际情况选用。

学习源码

https://gitee.com/imdongrui/study-repo.git
仓库中的lock工程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值