Synchronized和Lock的区别
- Synchronized是关键字,Lock是Java的类。Synchronized可以用在方法和代码块上,而Lock需要在方法里显式的控制获取锁和释放锁
- 都是可重入锁,Lock可重入锁的实现是ReentrantLock。
- Synchronized锁可自动释放,在发生异常或者代码执行完后自动释放;而Lock锁需要显式的释放锁,如果未处理异常可能会导致死锁
- Synchronized是非公平锁,它不会保证等待时间最长的线程优先获得锁;Lock锁的实现ReentrantLock可以选择是否公平锁
- Lock锁提供了更多的功能,包括tryLock和Condition接口控制锁粒度
- 性能:Synchronized:在JDK 1.6及更高版本中,synchronized关键字进行了大量的优化,包括使用偏置锁、轻量级锁等技术,使得它的性能在某些情况下甚至优于Lock。Lock:通常被认为在性能上至少与synchronized相当,但在某些场景下可能更具优势,特别是在需要高级功能的时候。
- 选择:如果需要简单的同步,并且希望代码更加简洁,那么使用synchronized关键字可能更为合适。如果需要更细粒度的控制,例如尝试锁、超时等待、公平性选择等高级功能,那么Lock接口是更好的选择。
乐观锁和悲观锁
Synchronized和Lock锁都是悲观锁;
乐观锁:无锁,数据更新时判断。版本号机制、CAS算法,Java原子类就是通过CAS自旋实现的。
Synchronized对象锁和类锁
在普通方法上加Synchronized锁的是对象,在静态方法上加Synchronized锁的是类,代码块括号里的对象是锁定的指定对象。开发中能用对象锁就不用类锁,能锁代码块就不要锁方法,要控制锁的粒度。
为什么任何对象都可以是一把锁
Java 中的同步机制基于监视器锁的概念。每个对象都有一个内部监视器,这个监视器可以用来控制对对象状态的访问。当一个线程尝试进入一个同步的代码块或方法时,它必须首先获取该对象的监视器锁。如果已经有其他线程持有了这个锁,那么当前线程将会被阻塞,直到锁被释放。Synchronized锁重入也是这个监视器里的一个属性,记录锁的持有者和进入次数。
公平锁与非公平锁
Synchronized和ReentrantLock默认都是非公平锁,ReentrantLock构造函数传true时可以实现公平锁。
非公平锁,表示锁释放后所有线程随机获得锁;公平锁,表示锁释放后,先排队的线程优先获得锁。
为什么默认是非公平锁:非公平锁重在效率,减少了线程上下文切换。
可重入锁
Synchronized是可重入锁,持有该锁的线程可以再次获得锁。Lock锁的实现ReentrantLock也是可重入锁,使用显式锁时,要注意释放锁和获取锁的次数保持一致,防止死锁。
Synchronized锁原理
死锁及排查
死锁:两个或多个线程互相等待对方持有的资源而无法继续执行的情况。产生原因:
- 互斥条件:为了防止数据竞争条件(race conditions),资源必须在一个时刻只能被一个线程使用。
- 占有与等待:一个已经持有某种资源的线程在未释放已持有的资源前,可以请求新的资源。
- 非抢占条件:资源只能由持有它的线程自愿释放,而不能被其他线程抢占。
- 循环等待:存在一个进程(线程)的循环链,其中每个进程都在等待下一个进程持有的资源。
要避免死锁,可以采取以下几种措施:
- 破坏互斥条件:通常很难做到这一点,因为很多资源本质上是互斥使用的。
- 破坏占有与等待条件:如果一个线程已经持有了某个资源,那么在它请求更多资源之前,应该先释放已有的资源。
- 破坏非抢占条件:允许持有某些资源的线程在请求另一些资源时被抢占。
- 破坏循环等待条件:给资源分配一个全局顺序,规定所有线程按照顺序请求资源。
如何排查死锁:jps获取进程id,使用jstack pid获取运行信息
Lock锁的Condition,Condition接口有什么作用?
Condition接口提供了比传统的Object类中的wait()、notify()和notifyAll()方法更强大、更灵活的线程间通信机制。Condition通常与Lock接口一起使用,以提供更高级别的线程同步能力。
Condition接口允许线程以更可控的方式进行等待和唤醒,它支持以下功能:
- 线程等待:线程可以等待直到另一个线程发出信号或指定的等待时间到期。
- 线程唤醒:线程可以唤醒一个或多个等待在同一个Condition上的线程。
- 与锁关联:Condition总是与一个特定的锁(Lock)关联。这意味着只有在当前线程持有锁的情况下,才能调用Condition方法。
Condition接口的几大方法:
- await:会释放锁,使当前线程进入等待状态,直到它收到信号或已中断。会抛出中断异常。
- awaitUninterruptibly:会释放锁,使当前线程进入等待状态,直到它收到信号。不会抛出中断异常。
- await(long time, TimeUnit unit):,使当前线程进入等待状态,直到它收到信号或已中断或经过指定的等待时间。会抛出中断异常。
- signal:不会释放锁,唤醒一个等待中的线程,唤醒是否公平取决于锁是否是公平锁。
- signalAll:不会释放锁,唤醒所有等待中的线程,唤醒和未获取到锁的状态会由WAITING转为RUNNABLE。
ReentrantReadWriteLock读写锁,锁饥饿,锁降级
读写锁:适用于读多写少的场景,读读不互斥,可以提高读的并发量提升性能。
锁饥饿:如果读的场景非常多,导致程序一直有线程不断的持有读锁,而读写互斥,写锁一直被阻塞,从而出现写锁饥饿的场景,永远不会获取或者很难获取到写锁。
锁降级:在同一个线程里,获取写锁后可以再获取读锁,释放写锁后就持有了读锁,这种现象称为锁降级,这可以保证线程能读到最新修改的数据,其他线程读锁也能获取到这次更新,阻塞其他写操作来保证只读到当前写完的数据。锁无法升级(即重读锁转换为写锁)
StampedLock,比读写锁性能更好的读写锁
提供乐观读取锁的方法,在读取过程中不阻塞其他读写操作,乐观的认为每次读取都没有修改过,使用乐观读后要判断stamp是否被修改,如果被修改需要提供再次读取的方法,否则可能出现数据不一致问题。由于乐观读是不阻塞的,所以在读多写少(如果频繁写入,可能出现读操作一直自旋的情况,效率降低)的情况下,可以显著提示并发性能,并且可以在乐观读途中获取到写锁,对数据进行更新,可以解决读写锁的写锁饥饿问题。
缺点:StampedLock不可重入,是非公平锁,不支持Condition变量。
Condition变量:Condition变量是java.util.concurrent.locks包中的一个接口,它通常与Lock接口一起使用,提供比wait()和notify()更强大的条件等待和信号机制。
private final Condition queueNotEmpty = lock.newCondition();
// 如果队列为空,等待直到队列中有元素
while (queue.isEmpty()) {
System.out.println("Queue is empty, consumer is waiting...");
queueNotEmpty.await();
}
// 通知消费者队列不再为空
queueNotEmpty.signal();
4478

被折叠的 条评论
为什么被折叠?



