syncronized关键字
syncronzied关键字
1、为什么需要syncronized关键字
在计算机中多线程在访问共享资源的时候,在对共享资源进行访问并修改的时候,会导致出现数据问题。
所以为了解决这种问题,引入了锁的概念。下面看看不使用锁的时候,然后再看看使用锁的时候
public class NoAddLock {
private static int money = 0;
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
money++;
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 20000; i++) {
money++;
}
});
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("对应的money的值是:"+money); // 对应的money的值是:24480
}
}
对应的加上锁的时候:
/**
* 加锁之后,对应的值永远是30000
* @author liguang
* @date 2022/3/8 15:47
*/
public class AddLock {
private static int money = 0;
public static void main(String[] args) {
Object object = new Object();
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
synchronized (object){
money++;
}
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 20000; i++) {
synchronized (object){
money++;
}
}
});
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("对应的money的值是:"+money); // 对应的money的值是:30000
}
}
2、syncronized的作用范围
三个作用范围
- 静态方法上
- 实例方法
- 代码块
对应着下面的三种方式:
作用在静态方法上
// 静态同步方法
public static synchronized void add(){}
作用在实例方法上
// 实例同步方法
public synchronized void add1(){}
作用在同步代码块中
public void add2(){
// 同步代码块
synchronized (this){
}
}
但是不可以忽略的是,syncronized锁是在作用在对象上的!这也就解释了为什么在Object类对象中会有wait方法和notify等这些方法。为了锁对象来进行服务的。
上面的三种使用方式上,syncronized分别作用于不同的对象
3、syncronized作用锁对象
静态同步 | 实例同步 | 同步块 |
---|---|---|
作用的是当前类的类对象上(xx.class对象) | 作用的是当前类的实例对象 | 自己可以指定锁对象(只要是对象即可) |
4、JVM如何保证线程安全问题
JVM保证使用syncronized关键字加锁,保证多线程竞争共享资源的线程安全问题
预备知识点一:
需要知道 Java 对象头,锁的类型和状态和对象头的Mark Word息息相关;
对象是在堆中存放的,主要分成了三个部分:对象头、对象实例对象、对齐填充组成的
- 对象头
对象头中存放的是对象hashCode、当前对象所属类信息以及数组的长度(这个只有数组对象才有)
- 对象实例
放入的是对象的属性
- 对齐填充
因为对象在内存中都是以8或者是8的倍数个字节存储的,当不足的时候,需要对其进行填充才能够满足是8的倍数
预备知识点二
需要了解 Monitor ,每个对象都有一个与之关联的Monitor 对象;Monitor对象属性如下所示( Hospot 1.7 代码) 。
ObjectMonitor() {
_header = NULL;
_count = 0; // 重入次数
_waiters = 0, // 等待线程数
_recursions = 0;
_object = NULL;
_owner = NULL; // 当前持有锁的线程
_WaitSet = NULL; // 调用了 wait 方法的线程被阻塞 放置在这里
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ;
FreeNext = NULL ;
_EntryList = NULL ; // 等待锁 处于block的线程 有资格成为候选资源的线程
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
}
对象关联的 ObjectMonitor 对象有一个线程内部竞争锁的机制,如下图所示:
在多线程下,会有多个线程来访问共享资源,那么如何保证在同一时刻只有一个线程拥有这个资源并操作这个资源,这个是设计的初衷。
首先多个线程来了之后,肯定会有锁去抢共享资源,那么如果其中有一个线程抢到了,而其他的线程没有抢到,那么只有等待抢到锁的线程完成对共享资源的操作之后,其他的线程才可以来继续抢占共享资源。
那么如何保证在其中一个线程抢到锁,而其他的锁在排队竞争的状态呢?应该是给锁有关,没锁,去抢;有锁,等待;
没有抢到锁的线程放入到容器中来,然后定期检查共享资源是不是没有锁了;如果没有锁,那么随机挑选一个线程,来抢占资源。
那么看一下上面的流程,在多线程竞争锁激烈的情况下,会将原来的Waitting Queue拆分为ContentionList和EntryList,在ContentionList中的是容纳来抢占锁的线程,在ContentionList集合中多个线程会进行大量的CAS来抢占锁,而JVM考虑的是把小部分的线程转移到EntryList集合中来,这个集合中的多线程成为了候选资源的线程,但是在同一个JVM中,在同一个时刻,只有一个线程能够进入OnDeck状态,只有这一个能够去获取得到对应的锁;
如果获取得到了锁,那么理解将锁对象关联的monitor中得owner设置成当前的线程,等到线程执行完成任务之后,会将owner进行重新设置为无线程占据,那么从OnDeck状态的线程来立刻来获取得到锁
那么将上面的叙述总结下:
1、多线程条件下,多个线程来抢占共享资源,其中一个线程抢到了,另外的线程因为没有抢到,那么就只能够在等待队列中等待;
2、在等待队列中的线程也不老实,会进行自旋操作;但是JVM为了考虑到对尾部元素的影响,将原来的等待队列进行拆分,扩充为ContentionList何EntryList,
在ContentionList集合中的线程并不一定都有条件成为候选资源的线程,而JVM操作的方式是通过将能够成为候选资源的线程放入到EntryList中去;
3、在某一个时刻,如果锁被释放了,那么处于OnDeck状态的线程和新来的线程进行抢占,然后回到了第一步,总有一个线程是没有抢到的;
4、抢到锁的线程开始来执行业务逻辑,没有抢到的继续排队。
5、如果抢到锁的线程在执行过程中进行了wait方法操作,而去释放锁,那么将会进入到阻塞队列中来,然后等待被唤醒,唤醒之后的线程又将会直接进入到EntryList集合中来;
5、syncronized是非公平锁
从流程图中的两个流程来进行回答:
1、多线程条件下,并非是一开始就直接进入到ContentionList集合中去,而是先是去尝试自旋状态获取得到锁,而此时contentionList集合中可能也有线程在等待锁,获取不到的时候才会进入到contentionList队列中来;
2、在自旋获取锁的线程可能会直接抢占Ondeck线程的资源;
6、经典案例
6.1、获取得到单例对象
public class DoubleCheckObj {
private static DoubleCheckObj doubleCheckObj = null;
public void show(){
System.out.println("show method.................");
}
/**
* 获取得到单例对象,对单例数据来进行操作
* @return
*/
public static DoubleCheckObj getSingleton() {
if (doubleCheckObj == null) {
synchronized (DoubleCheckObj.class) {
if (doubleCheckObj == null) {
doubleCheckObj = new DoubleCheckObj();
}
}
}
return doubleCheckObj;
}
public static void main(String[] args) {
DoubleCheckObj doubleCheckObj = new DoubleCheckObj();
for (int var = 0; var < 100; var++) {
new Thread(()->{
doubleCheckObj.show();
}).start();
}
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
6.2、死锁案例
public class TestDeadLock {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
public static class DeadLockSample implements Runnable {
Object[] locks;
public DeadLockSample(Object lock1, Object lock2) {
locks = new Object[2];
locks[0] = lock1;
locks[1] = lock2;
}
@Override
public void run() {
synchronized (locks[0]) {
try {
System.out.println("current thread name is " + Thread.currentThread().getName());
Thread.sleep(3000);
synchronized (locks[1]) {
System.out.println(String.format("%s come in...", Thread.currentThread().getName()));
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
Thread a = new Thread(new DeadLockSample(lock1, lock2));
Thread b = new Thread(new DeadLockSample(lock2, lock1));
a.start();
b.start();
}
}
6.3、生产者消费者(三个线程交替打印)
public class WaitNofityDemo1 {
private static /*volatile*/ int i = 0;
private static final Object obj = new Object();
public static class One implements Runnable {
@Override
public void run() {
// 这么来进行操作,那么会占满CPU的
synchronized (obj) {
while (!Thread.currentThread().isInterrupted()) {
if (i % 3 == 0) {
System.out.println("当前的i的值是:" + i);
i++;
obj.notifyAll();
}
try {
Thread.sleep(2000);
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public static class Two implements Runnable {
@Override
public void run() {
synchronized (obj) {
while (!Thread.currentThread().isInterrupted()) {
if (i % 3 == 2) {
System.out.println("当前的i的值是:" + i);
i++;
obj.notifyAll();
}
try {
Thread.sleep(2000);
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public static class Three implements Runnable {
@Override
public void run() {
synchronized (obj) {
while (!Thread.currentThread().isInterrupted()) {
if (i % 3 == 1) {
System.out.println("当前的i的值是:" + i);
i++;
obj.notifyAll();
}
try {
Thread.sleep(2000);
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public static void main(String[] args) {
Thread thread1 = new Thread(new One());
Thread thread2 = new Thread(new Two());
Thread thread3 = new Thread(new Three());
thread1.start();
thread2.start();
thread3.start();
}
}
参考地址:https://mp.weixin.qq.com/s?__biz=MzI3ODA0ODkwNA==&mid=2247483680&idx=1&sn=18a73ea417d299de1a09640d56bd2489&chksm=eb5db8c7dc2a31d1c99f09511325d9d5cd7cf82152df7160c36210f0a68f293200f2ac4733df&cur_album_id=1337196027396407297&scene=189#wechat_redirect
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?