【Kill Thread】补充:Synchronized的作用
【Kill Thread】补充:Synchronized的作用
synchronize详解,锁升级
https://blog.csdn.net/lpf463061655/article/details/105149322
一、Synchronized简介
1、Synchronized的作用
通俗易懂:能够保证在同一时刻最多只有一个线程执行该段代码,以达到保证并发安全的效果。
2、Synchronized的地位
二、Synchronized的两种用法(对象锁和类锁)
1、对象锁
①方法锁(默认锁对象为this 当前实例对象)
/**
* 描述:对象锁示例1,代码块形式
*/
public class SynchronizedObjectCodeBlock2 implements Runnable {
private static SynchronizedObjectCodeBlock2 instance = new SynchronizedObjectCodeBlock2();
@Override
public synchronized void run() {
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 thread1 = new Thread(instance);
Thread thread2 = new Thread(instance);
thread1.start();
thread2.start();
while (thread1.isAlive() || thread2.isAlive()) {
}
System.out.println("finish");
}
}
运行结果
②同步代码块锁(自己制定锁对象)
/**
* 描述:对象锁示例1,代码块形式
*/
public class SynchronizedObjectCodeBlock2 implements Runnable {
private static SynchronizedObjectCodeBlock2 instance = new SynchronizedObjectCodeBlock2();
@Override
public void run() {
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 thread1 = new Thread(instance);
Thread thread2 = new Thread(instance);
thread1.start();
thread2.start();
while (thread1.isAlive() || thread2.isAlive()) {
}
System.out.println("finish");
}
}
- 确保了只有一个线程来运行这个代码块
③区别
- 代码块形式:手动指定锁对象
- 方法锁形式:synchronized修饰普通方法,锁对象默认为this
2、类锁
概念
- Java类可能有很多个对象,但是只有一个Class对象。
- 本质:所谓的类所,不过是Class对象的锁而已。
- 用法和效果:类锁只能在同一时刻被一个对象拥有。
①Synchronized修饰静态static的方法
/**
* 描述: 类锁的第一种形式,static形式
*/
public class SynchronizedClassStatic4 implements Runnable{
private static SynchronizedClassStatic4 instance1 = new SynchronizedClassStatic4();
private static SynchronizedClassStatic4 instance2 = new SynchronizedClassStatic4();
//static很关键
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() + "运行结束");
}
@Override
public void run() {
method();
}
public static void main(String[] args) {
Thread thread1 = new Thread(instance1);
Thread thread2 = new Thread(instance2);
thread1.start();
thread2.start();
while (thread1.isAlive() || thread2.isAlive()) {
}
System.out.println("finish");
}
}
- 如果不加static,根据两个对象创建出来的线程,默认的锁是当前对象的锁,而加上static之后,这个method代码块升级为类锁。
②synchronized(*.class)代码块
public class SynchronizedClassClass5 implements Runnable{
private static SynchronizedClassClass5 instance1 = new SynchronizedClassClass5();
private static SynchronizedClassClass5 instance2 = new SynchronizedClassClass5();
//static很关键
public void method() {
synchronized (SynchronizedClassClass5.class) {
System.out.println("我是对象锁的代码块方式,我叫:" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "运行结束");
}
}
@Override
public void run() {
method();
}
public static void main(String[] args) {
Thread thread1 = new Thread(instance1);
Thread thread2 = new Thread(instance2);
thread1.start();
thread2.start();
while (thread1.isAlive() || thread2.isAlive()) {
}
System.out.println("finish");
}
}
- 这里的锁为这个类的对象,只有一个。
三、多线程访问同步方法的7种情况
1、两个线程同时访问一个对象的同步方法
- 线程安全,一个线程执行完之后,另一个线程执行。
2、两个线程访问的是两个对象的同步方法
- 两个线程一起执行
3、两个线程访问的是synchronized的静态方法
- 类锁,先后执行
4、同时访问同步方法与非同步方法
- 同步方法最多只能被一个线程调用,非同步方法没有任何限制,谁调用都可以
5、访问同一个对象的不同的普通同步方法
- 先后执行,因为synchronized方法默认的锁,是当前这个对象,还是会互斥的
6、同时访问静态synchronized和非静态synchronized方法
- 两个程序可以同时运行,一个是类锁,一个是对象锁,是两个不同的锁,互不影响
7、方法抛异常后,会释放锁
四、Synchronized的性质
1、可重入
指的是同一个线程的外层函数获得锁之后,内层函数可以直接再次获取该锁。
好处:避免死锁、提升封装性
就比如说,排队去摇车牌号,轮到A人了,此时A获得了锁,A可以一直获取牌号,给A的三辆车弄三个车牌号,这是允许的,这就叫可重入。如果不允许这种操作,一次性只允许获取一个牌照,再次获取牌照需要重新排队,这就叫不可重入。
运行结果:
2、不可中断
五、深入原理
1、加锁和释放锁的原理:看字节码文件
- 获取和释放锁的设计:进入和退出同步代码块(包括抛出异常)
测试代码:
public class SynchronizedToLock {
Lock lock = new ReentrantLock();
private synchronized void method1() {
System.out.println("我是Synchronized形式");
}
private void method2() {
lock.lock();
try {
System.out.println("我是lock形式");
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
SynchronizedToLock synchronizedToLock = new SynchronizedToLock();
synchronizedToLock.method1();
synchronizedToLock.method2();
}
}
- synchronized可以自己释放锁,如果自己控制锁的话,需要手动的加锁和释放锁。
2、可重入原理:加锁次数计数器
测试代码
public class Decompilation {
private Object object = new Object();
public void insert(Thread thread) {
synchronized (object) {
}
}
}
编译生成class文件
monitorenter和monitorexit JVM指令
可重入原理:加锁次数计数器
- JVm会记录被加锁的次数
- 第一次加锁的时候,次数从0变为1,之后如果再次加锁,就从1变成2,以此类推
- 退出一层代码块的时候,计数减一,当计数为0的时候,代表锁释放
3、保证可见性的原理:内存模型
- 可见性:一个线程执行的结果,另外的线程不一定可见
- 线程1操作x = 5,之后线程2可能读取x = 3
- synchronized可以保证可见性
六、Synchronized的缺陷
1、效率低
锁的释放情况少,试图获得锁时不能设定超时、不能中断一个正在视图获得锁的线程。
2、不够灵活(读写锁更灵活)
加锁和释放的时机单一、每个锁仅有单一的条件(某个对象),可能是不够的
3、无法知道是否成功的获取了锁
七、常见面试问题
1、使用注意点
锁的范围不宜过大,避免锁的嵌套
2、如何选择Lock和Synchronized关键字
手动和自动的情况,如果synchronized可以满足需求,优先选择synchronized
3、多线程访问同步方法的各种具体情况
synchronized锁的升级
https://blog.csdn.net/lucky_love816/article/details/114633313
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现