Synchronized可以作用在哪里? 分别通过对象锁和类锁进行举例。
- 对象锁
- 作用在代码块(这里可以手动指定对象,可以使this,也开始是自定义的锁)
public class SynchronizedObjectLock implements Runnable {
static SynchronizedObjectLock instence = new SynchronizedObjectLock();
//自定义的锁
Object block1 = new Object();
Object block2 = new Object();
@Override
public void run() {
// 同步代码块形式——锁为this,两个线程使用的锁是一样的,线程1必须要等到线程0释放了该锁后,才能执行
// this 可以替换成 自定义的锁
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(instence);
Thread t2 = new Thread(instence);
t1.start();
t2.start();
}
}
- 方法锁形式 synchronized修饰普通方法锁对象默认是this
public class SynchronizedObjectLock implements Runnable {
static SynchronizedObjectLock instence = 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(instence);
Thread t2 = new Thread(instence);
t1.start();
t2.start();
}
}
- 类锁
- synchornized 修饰静态方法
著作权归https://pdai.tech所有。
链接:https://www.pdai.tech/md/java/thread/java-thread-x-key-synchronized.html
public class SynchronizedObjectLock implements Runnable {
static SynchronizedObjectLock instence1 = new SynchronizedObjectLock();
static SynchronizedObjectLock instence2 = 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(instence1);
Thread t2 = new Thread(instence2);
t1.start();
t2.start();
}
}
- synchornized修饰所对象是class对象
public class SynchronizedObjectLock implements Runnable {
static SynchronizedObjectLock instence1 = new SynchronizedObjectLock();
static SynchronizedObjectLock instence2 = 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(instence1);
Thread t2 = new Thread(instence2);
t1.start();
t2.start();
}
}
Synchronized本质上是通过什么保证线程安全的? 分三个方面回答:加锁和释放锁的原理,可重入原理,保证可见性原理。
synchornized加锁原理分析
加锁释放锁的原理
Monitorenter
和Monitorexit
指令,会让对象在执行,使其锁计数器加1或者减1
在一个对象被执行时,在同一时间只会与一个monitor(锁)相关联,而一个monitor在同一时间只会被一个线程获得,所以在一个对象尝试获得与这个对象
相关联的Monitor锁的所有权时 ,会有三种情况
1. monitor计数器为0 说明该对象还没有被获取,然后当前线程就会立即获得,然后monitor计数器+1,一旦+1 其他线程想要获得就必须得等待
2. 如果这个monitor已经拿到了该锁的所有权,又重入了这把锁,那么计数器就会反复+1
3. 这把锁已经被别的线程获取了,等待锁释放
monitorexit
指令:释放对于monitor的所有权,释放过程很简单,就是讲monitor的计数器减1,如果减完以后,计数器不是0,则代表刚才是重入进来的,当前线程还继续持有这把锁的所有权,如果计数器变成0,则代表当前线程不再拥有该monitor的所有权,即释放锁。
可重入的原理:加锁次数计数器
通俗来讲就是在同一个锁程中不需要重新获取锁
保证可见性的原理
内存模型,和haapens-before原则
Synchronized由什么样的缺陷?Java Lock是怎么弥补这些缺陷的。
- 效率低 : 锁的释放情况太少了,只有代码执行完毕,或者异常结束后才会释放锁,试图获取锁的进程不能设置超时,不能中断一个正在使用锁的线程,lock可以中断你和设置超时
- 不够灵活:加锁和释放的时机单一,每个锁仅有一个单一的条件,相对,读写锁更加灵活
- 无法知道是否成功获取锁:Lock可以拿到状态
lock(): 加锁
unlock(): 解锁
tryLock(): 尝试获取锁,返回一个boolean值
tryLock(long,TimeUtil): 尝试获取锁,可以设置超时
Synchronized和Lock的对比,和选择? Synchronized在使用时有何注意事项?
- 作用域不要太大
- 避免死锁
- 所对象不能为空,因为锁的信息都保存在信息头里面
- 在能选择的情况下,尽量不要用lock,也不要用synchronized关键字,尽量使用java.util.concurrent包中的各种各样的类
Synchronized修饰的方法在抛出异常时,会释放锁吗?
public class TestMain implements Runnable {
//格式化
static SimpleDateFormat sim = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Override
public void run() {
//让线程Thread-0执行同步方法1
if ("Thread-0".equals(Thread.currentThread().getName())) {
synchronizedMethod();
} else {
//让线程Thread-1执行同步方法2
synchronizedMethod2();
}
}
public synchronized void synchronizedMethod() {
Date satrtTime = new Date();
String time = sim.format(satrtTime);
System.out.println(time + ":【" + Thread.currentThread().getName() + "访问了同步方法1】");
try {
//睡眠3秒
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Date endTime = new Date();
String time2 = sim.format(endTime);
System.out.println(time2 + ":【" + Thread.currentThread().getName() + "准备抛出异常了】");
throw new RuntimeException();
}
public synchronized void synchronizedMethod2() {
Date satrtTime = new Date();
String time = sim.format(satrtTime);
System.out.println(time + ":【" + Thread.currentThread().getName() + "访问了同步方法2】");
try {
//睡眠3秒
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Date endTime = new Date();
String time2 = sim.format(endTime);
System.out.println(time2 + ":【" + Thread.currentThread().getName() + "准备退出这个同步方法了2】");
}
public static void main(String[] args) throws Exception {
//创建一个对象testMain1
TestMain testMain1 = new TestMain();
System.out.println("运行开始");
Thread thread1 = new Thread(testMain1);
Thread thread2 = new Thread(testMain1);
thread1.start();
thread2.start();
//让主线程做个等待,等线程一和线程二都执行完它才继续执行
thread1.join();
thread2.join();
System.out.println("运行结束");
}
}
所以可以看出在异常抛出后会释放锁
多个线程等待同一个snchronized锁的时候,JVM如何选择下一个获取锁的线程?
Synchronized使得同时只有一个线程可以执行,性能比较差,有什么提升的方法?
- 消除锁(判断该对象是否有多个线程调用,若无则消除该对象上的锁)
- 使用轻量级锁
- 锁粗化(在一个代码块代码总是释放锁又加锁的情况下,又是同一线程这时就会锁粗化把这一整块代码块加锁)
我想更加灵活地控制锁的释放和获取(现在释放锁和获取锁的时机都被规定死了),怎么办?
使用Condition与Lock的结合:ReentrantLock 进行锁操作
lock(): 加锁
unlock(): 解锁
tryLock(): 尝试获取锁,返回一个boolean值
tryLock(long,TimeUtil): 尝试获取锁,可以设置超时
什么是锁的升级和降级? 什么是JVM里的偏斜锁、轻量级锁、重量级锁? 不同的JDK中对Synchronized有何优化?
- 锁的升级与降级:,为了减少获得锁和释放锁带来的性能开销,引入了偏向锁、轻量级锁的概念。锁的状态根据竞争激烈的程度从低到高不断升级或者降级。(偏向锁-》轻量级锁-》重量级锁)
- 偏向锁:当一个线程访问加了同步锁的代码块时,会在对象头中存储当前线程的id,后续这个线程进入和退出这个代码块时,不需要在次加锁,和释放锁,而是直接比较对象头里面是否存储了指向当前线程的偏向锁,相等就表示偏向锁偏向当前线程,不需要尝试获得锁
- 轻量级锁:轻量级意味着相比于传统的锁,它使用的是系统互斥量来实现的,目的是在 没有多线程竞争的情况下,减少系统互斥量操作产生的性能消耗(轻量级锁,先进行cas操作,然后若是没获得锁就进行自旋的操作,在短暂时间里再次去获取锁并且大几率能够获得锁,这就是轻量级锁)
- 重量级锁:两个线程之间的竞争不能得到很好的交替(自旋之后获得不到锁),会上升为重量级锁,
- JDk1.6之后引入了偏向锁,和轻量级锁