目录
- Synchronized可以作用在哪里? 分别通过对象锁和类锁进行举例
- Synchronized本质上是通过什么保证线程安全的? 分三个方面回答:加锁和释放锁的原理,可重入原理,保证可见性原理
- Synchronized有什么样的缺陷? Java Lock是怎么弥补这些缺陷的
- Synchronized和Lock的对比,和选择?
- Synchronized在使用时有何注意事项
- Synchronized修饰的方法在抛出异常时,会释放锁吗
- 多个线程等待同一个Synchronized锁的时候,JVM如何选择下一个获取锁的线程?
- Synchronized使得同时只有一个线程可以执行,性能比较差,有什么提升的方法?
Synchronized可以作用在哪里? 分别通过对象锁和类锁进行举例
在应用Sychronized关键字时需要把握如下注意点:
- 一把锁只能同时被一个线程获取,没有获得锁的线程只能等待;
- 每个实例都对应有自己的一把锁(this),不同实例之间互不影响;例外:锁对象是*.class以及synchronized修饰的是static方法的时候,所有对象公用同一把锁
- synchronized修饰的方法,无论方法正常执行完毕还是抛出异常,都会释放锁
对象锁
包括方法锁(默认锁对象为this,当前实例对象)和同步代码块锁(自己指定锁对象)
代码块形式:手动指定锁定对象,也可是是this,也可以是自定义的锁
同步代码块形式——锁为this,两个线程使用的锁是一样的,线程1必须要等到线程0释放了该锁后,才能执行
public class SynchronizedObjectLock implements Runnable {
static SynchronizedObjectLock instance = new SynchronizedObjectLock();
@Override
public void run() {
// 同步代码块形式——锁为this,两个线程使用的锁是一样的,线程1必须要等到线程0释放了该锁后,才能执行
synchronized (this) {
System.out.println("我是线程" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "结束");
}
}
public static void main(String[] args) {
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
}
}
输出:
我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束
public class SynchronizedObjectLock implements Runnable {
static SynchronizedObjectLock instance = new SynchronizedObjectLock();
// 创建2把锁
Object block1 = new Object();
Object block2 = new Object();
@Override
public void run() {
// 这个代码块使用的是第一把锁,当他释放后,后面的代码块由于使用的是第二把锁,因此可以马上执行
synchronized (block1) {
System.out.println("block1锁,我是线程" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("block1锁,"+Thread.currentThread().getName() + "结束");
}
synchronized (block2) {
System.out.println("block2锁,我是线程" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("block2锁,"+Thread.currentThread().getName() + "结束");
}
}
public static void main(String[] args) {
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
}
}
输出:
block1锁,我是线程Thread-0
block1锁,Thread-0结束
block2锁,我是线程Thread-0 // 可以看到当第一个线程在执行完第一段同步代码块之后,第二个同步代码块可以马上得到执行,因为他们使用的锁不是同一把
block1锁,我是线程Thread-1
block2锁,Thread-0结束
block1锁,Thread-1结束
block2锁,我是线程Thread-1
block2锁,Thread-1结束
方法锁形式:synchronized修饰普通方法,锁对象默认为this
public class SynchronizedObjectLock implements Runnable {
static SynchronizedObjectLock instance = new SynchronizedObjectLock();
@Override
public void run() {
method();
}
public synchronized void method() {
System.out.println("我是线程" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "结束");
}
public static void main(String[] args) {
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();
t2.start();
}
}
输出:
我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束
类锁
指synchronize修饰静态的方法或指定锁对象为Class对象
synchronize修饰静态方法
public class SynchronizedObjectLock implements Runnable {
static SynchronizedObjectLock instance1 = new SynchronizedObjectLock();
static SynchronizedObjectLock instance2 = new SynchronizedObjectLock();
@Override
public void run() {
method();
}
// synchronized用在普通方法上,默认的锁就是this,当前实例
public synchronized void method() {
System.out.println("我是线程" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "结束");
}
public static void main(String[] args) {
// t1和t2对应的this是两个不同的实例,所以代码不会串行
Thread t1 = new Thread(instance1);
Thread t2 = new Thread(instance2);
t1.start();
t2.start();
}
}
输出:
我是线程Thread-0
我是线程Thread-1
Thread-1结束
Thread-0结束
public class SynchronizedObjectLock implements Runnable {
static SynchronizedObjectLock instance1 = new SynchronizedObjectLock();
static SynchronizedObjectLock instance2 = new SynchronizedObjectLock();
@Override
public void run() {
method();
}
// synchronized用在静态方法上,默认的锁就是当前所在的Class类,所以无论是哪个线程访问它,需要的锁都只有一把
public static synchronized void method() {
System.out.println("我是线程" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "结束");
}
public static void main(String[] args) {
Thread t1 = new Thread(instance1);
Thread t2 = new Thread(instance2);
t1.start();
t2.start();
}
}
输出:
我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束
synchronized指定锁对象为Class对象
public class SynchronizedObjectLock implements Runnable {
static SynchronizedObjectLock instance1 = new SynchronizedObjectLock();
static SynchronizedObjectLock instance2 = new SynchronizedObjectLock();
@Override
public void run() {
// 所有线程需要的锁都是同一把
synchronized(SynchronizedObjectLock.class){
System.out.println("我是线程" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "结束");
}
}
public static void main(String[] args) {
Thread t1 = new Thread(instance1);
Thread t2 = new Thread(instance2);
t1.start();
t2.start();
}
}
输出:
我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束
Synchronized本质上是通过什么保证线程安全的? 分三个方面回答:加锁和释放锁的原理,可重入原理,保证可见性原理
详见Synchronized原理分析:https://pdai.tech/md/java/thread/java-thread-x-key-synchronized.html
Synchronized有什么样的缺陷? Java Lock是怎么弥补这些缺陷的
synchronized的缺陷
-
效率低
:锁的释放情况少,只有代码执行完毕或者异常结束才会释放锁;试图获取锁的时候不能设定超时,不能中断一个正在使用锁的线程,相对而言,Lock可以中断和设置超时 -
不够灵活
:加锁和释放的时机单一,每个锁仅有一个单一的条件(某个对象),相对而言,读写锁更加灵活 -
无法知道是否成功获得锁
,相对而言,Lock可以拿到状态,如果成功获取锁,....,如果获取失败,.....
Lock解决相应问题
lock()
: 加锁unlock()
: 解锁tryLock()
: 尝试获取锁,返回一个boolean值tryLock(long,TimeUtil)
: 尝试获取锁,可以设置超时
Synchronized加锁只与一个条件(是否获取锁)相关联,不灵活,后来Condition与Lock的结合
解决了这个问题。
多线程竞争一个锁时,其余未得到锁的线程只能不停的尝试获得锁,而不能中断。高并发的情况下会导致性能下降。ReentrantLock的lockInterruptibly()方法可以优先考虑响应中断。 一个线程等待时间过长,它可以中断自己,然后ReentrantLock响应这个中断,不再让这个线程继续等待。有了这个机制,使用ReentrantLock时就不会像synchronized那样产生死锁了。
Synchronized和Lock的对比,和选择?
synchronized用于方法和代码块,不指定锁表示this,也可以指定对象;
Lock只能使用在方法内显示指定lock unlock,并且需要手动释放锁,一般在finally中释放锁,防止发生死锁;
synchronized无需手动释放锁,Lock需要手动释放锁;
synchronized不可中断,Lock可以通过调用lockInterruptibly中断锁;
synchronized获取不到锁一直阻塞,Lock可以通过tryLock方式尝试获取锁,获取不到锁返回false;
Synchronized在使用时有何注意事项
- 锁对象不能为空,因为锁的信息都保存在对象头里
- 作用域不宜过大,影响程序执行的速度,控制范围过大,编写代码也容易出错
- 避免死锁
- 在能选择的情况下,既不要用Lock也不要用synchronized关键字,用java.util.concurrent包中的各种各样的类,如果不用该包下的类,在满足业务的情况下,可以使用synchronized关键,因为代码量少,避免出错
Synchronized修饰的方法在抛出异常时,会释放锁吗
会释放锁
多个线程等待同一个Synchronized锁的时候,JVM如何选择下一个获取锁的线程?
一个持有锁的线程,在程序运行结束或者出现异常的时候,就会释放这把锁。这个时候除了刚才已经在等待中的线程之外,其他线程也会来竞争这把锁,而竞争结果是由JVM来操作的,获取最终的结果是不公平的(随机的),有可能是等待时间最长的线程获取这把锁,也有可能是刚生成的线程去获取这把锁
Synchronized使得同时只有一个线程可以执行,性能比较差,有什么提升的方法?
优化使用范围
使用其它锁