首先要搞清他们是什么以及他们的特点是什么。
Synchronized
synchronized是Java内置的关键字,解决的是多个线程之间访问资源的同步性,synchronized关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。
另外,在 Java 早期版本中,synchronized属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的 Mutex Lock 来实现的,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的synchronized 效率低的原因。JDK1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。
因为synchronized加锁是无法主动释放锁的,所以synchronized关键字可以保证只有一个线程能够访问同步代码块。
Synchronized使用
synchronized修饰普通方法
synchronized修饰静态方法
synchronized(this)
synchronized(xxx.class)
区别:
synchronized修饰普通方法与synchronized(this)可以简单理解为变量锁,他们只能针对同个变量达到同步执行效果;如果是不同变量就会异步执行。
synchronized修饰静态方法与synchronized(xxx.class)可以简单理解为类锁,只要调用同个类的加锁方法,都能达到同步执行效果。
Volatile
Volatile保证了并发三特性中的可见性和有序性。
可见性:当某线程对 volatile 变量的修改会立即回写到主存中,并且导致其他线程的缓存行失效,强制其他线程再使用变量时,需要从主存中读取。
有序性:Java 内存模型具备一些先天的“有序性”,即不需要通过任何手段就能够得到保证的有序性,这个通常也称为 happens-before 原则。如果两个操作的执行次序无法从 happens-before 原则推导出来,那么它们就不能保证它们的有序性,虚拟机可以随意地对它们进行重排序。
- 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作;
- 锁定规则:一个 unLock 操作先行发生于后面对同一个锁的 lock 操作;
- volatile 变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作;
- 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C;
- 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作;
- 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生;
- 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行;
- 对象终结规则:一个对象的初始化完成先行发生于他的 finalize() 方法的开始;
对象在初始化的时候分三个步骤,用下面的伪代码表示:
memory = allocate(); //1. 分配对象的内存空间
ctorInstance(memory); //2. 初始化对象
instance = memory; //3. 设置 instance 指向对象的内存空间
Lock
常用API:Lock(), unLock(), tryLock()
ReentrantLock中主要定义了三个内部类,
(1)抽象类Sync实现了AQS的部分方法;
(2)NonfairSync实现了Sync,主要用于非公平锁的获取;
(3)FairSync实现了Sync,主要用于公平锁的获取。
Lock三部曲:
- new ReentantLock();
- 代码块加锁 Lock.lock();
- finally解锁
Synchronized与Lock的区别:
- Synchronized是内置的java关键字, Lock是一个Java类
- Synchronized无法判断获取锁的状态, Lock可以判断是否获取到了锁
- Synchronized不会自动释放锁, Lock手动释放。
- Synchronized线程不会争抢,线程1不释放,线程2 就一直等待, Lock不会一直等待。
- Synchronized可重入锁,不可以中断,非公平, Lock可重入,可判断,默认非公平的,但是可以自己设置。
- Synchronized适合锁少量的代码同步问题, Lock适合所大量的同步代码。
为什么ReentrantLock默认采用的是非公平模式?
答:因为非公平模式效率比较高。
为什么非公平模式效率比较高?
答:因为非公平模式会在一开始就尝试两次获取锁,如果当时正好state的值为0,它就会成功获取到锁,少了排队导致的阻塞/唤醒过程,并且减少了线程频繁的切换带来的性能损耗。
非公平模式有什么弊端?
答:非公平模式有可能会导致一开始排队的线程一直获取不到锁,导致线程饿死。
区别
使用synchronized加锁是无法主动释放锁的,这就会涉及到死锁的问题。
同步代码块简单来说就是将一段代码用一把锁给锁起来, 只有获得了这把锁的线程才访问, 并且同一时刻, 只有一个线程能持有这把锁, 这样就保证了同一时刻只有一个线程能执行被锁住的代码.
// 获取锁
void lock();
// 获取锁(可中断)
void lockInterruptibly() throws InterruptedException;
// 尝试获取锁,如果没获取到锁,就返回false
boolean tryLock();
// 尝试获取锁,如果没获取到锁,就等待一段时间,这段时间内还没获取到锁就返回false
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 释放锁
void unlock();
// 条件锁
Condition newCondition();