介绍
synchronized 是Java多线程中的同步锁机制可以对方法、对象或代码块进行加锁,保证在同一时间只有一个线程操作对应的资源,避免多线程同时访问相同的资源发生冲突。简单来说,synchronized 就是用来控制线程同步的,被加锁的代码块,不能被多个线程同时执行。
用法
synchronized修饰方法
synchronized修饰一个静态的方法
synchronized修饰代码块
修饰一个类
synchronized修饰普通方法
锁是当前实例对象, 进入同步代码前要获得当前实例的锁;
如果多个线程访问同一个对象的实例变量,可能出现非线程安全问题。
例子:a线程set Mydata对象的数据后b线程是否可以同时set
Mydata类
![61ed151c90c574edbd800ef190629d1a.png](https://img-blog.csdnimg.cn/img_convert/61ed151c90c574edbd800ef190629d1a.png)
a线程:
![f64ca6b63affd97c001d0831a0768dd2.png](https://img-blog.csdnimg.cn/img_convert/f64ca6b63affd97c001d0831a0768dd2.png)
b线程:
![c3dfd448d8a79a80543fae07f6f28b8b.png](https://img-blog.csdnimg.cn/img_convert/c3dfd448d8a79a80543fae07f6f28b8b.png)
TestDemo:测试
![86c23215a4ccb4c3a8a3180106c3ed45.png](https://img-blog.csdnimg.cn/img_convert/86c23215a4ccb4c3a8a3180106c3ed45.png)
打印:
执行当前方法线程的名字threadB
jeig执行当前方法线程的名字threadA
threadA线程执行完毕
threadAnum=5
threadB线程执行完毕
threadBnum=5
结果:a线程set后b线程可以乘机set,造成数据不同步,这样我们称它为线程不安全。
这时我们使用synchronized修饰SetNum方法:
![1dcb02d6dc6e2808e276c713d4760da6.png](https://img-blog.csdnimg.cn/img_convert/1dcb02d6dc6e2808e276c713d4760da6.png)
我们再运行一会
打印:
执行当前方法线程的名字threadA
threadAnum=5
threadA线程执行完毕
执行当前方法线程的名字threadB
threadBnum=6
threadB线程执行完毕
结果:后启动的线程必须要等待第一个线程执行完SetNun方法后才可以执行。说明MyData的SetNum方法是线程安全的。
注意: 当synchronized修饰实例方法时,同一个类的不同对象实例,具有不同的锁,同一个类的同一个实例具有相同的锁,此时的锁是锁住的对象实例。当一个线程正在访问一个对象的 synchronized 实例方法,那么其他线程不能访问该对象的其他 synchronized 方法,毕竟一个对象实例只有一把锁,当一个线程获取了该对象实例的锁之后,其他线程无法获取该对象实例的锁,所以无法访问该对象实例的其他synchronized实例方法,但是其他线程还是可以访问该实例对象的其他非synchronized方法,当然如果是一个线程 A 需要访问对象实例 object1 的 synchronized 方法 f1(当前对象锁是object1 ),另一个线程 B 需要访问实例对象 object2的 synchronized 方法 f1(当前对象锁是object2),这样是允许的,因为两个实例对象锁并不同相同,如果A线程持有x对象的锁,B线程不可调用synchronized修饰的方法,但是可以异步调用没有被synchronized修饰的方法
![3a8544d6df948d49e7e6aa1b9f76a10e.png](https://img-blog.csdnimg.cn/img_convert/3a8544d6df948d49e7e6aa1b9f76a10e.png)
synchronized具有锁重入功能,也就是说一个线程获得锁,再次请求是可以再次得到对象的锁的
什么是可重入锁呢?
当一个线程获取了某个对象锁以后,还可以再次获得该对象锁。
什么时候我们会用到可重入锁呢?
看下面的demo,改一下MyData
![f5124771f042dd0755bf989d5ef8d56f.png](https://img-blog.csdnimg.cn/img_convert/f5124771f042dd0755bf989d5ef8d56f.png)
运行:
![424731078459145032deccdcc476abdc.png](https://img-blog.csdnimg.cn/img_convert/424731078459145032deccdcc476abdc.png)
运行结果:
执行当前方法线程的名字threadA
threadAnum=5
threadA线程执行完毕
执行当前方法线程的名字threadA
threadAnum=6
threadA线程执行完毕
执行当前方法线程的名字threadB
threadBnum=7
threadB线程执行完毕
执行当前方法线程的名字threadB
threadBnum=8
threadB线程执行完毕
SetNum1()和SetNum2()都是同步方法,当线程进入SetNum1()会获得该类的对象锁, 在SetNum1()对方法SetNum2()做了调用,但是SetNum2()也是同步的,因此该线程需要继续获得该对象锁。其他线程是无法获该对象锁的。这就是可冲入锁,可重入锁的作用就是为了避免死锁。
修饰静态的方法
例子,
![df440500569982ac4d3a17435d68d31b.png](https://img-blog.csdnimg.cn/img_convert/df440500569982ac4d3a17435d68d31b.png)
![8aaffce081436c75265b957cecd8bf58.png](https://img-blog.csdnimg.cn/img_convert/8aaffce081436c75265b957cecd8bf58.png)
![747ceb4ac1c875340fc39e22590584dd.png](https://img-blog.csdnimg.cn/img_convert/747ceb4ac1c875340fc39e22590584dd.png)
![0ea195ad418d468def0fec86a1b6d103.png](https://img-blog.csdnimg.cn/img_convert/0ea195ad418d468def0fec86a1b6d103.png)
打印:
执行当前方法线程的名字threadB
执行当前方法线程的名字threadA
threadBnum=7
threadAnum=5
threadA线程执行完毕
threadB线程执行完毕
现在我们给SetNum加上Static修饰
![8984b3593374bb95b819c4fea1c26044.png](https://img-blog.csdnimg.cn/img_convert/8984b3593374bb95b819c4fea1c26044.png)
打印:
执行当前方法线程的名字threadA
threadAnum=5
threadA线程执行完毕
执行当前方法线程的名字threadB
threadBnum=7
threadB线程执行完毕
结果:
使用synchronized标记实例方法时(非静态方法),只有获得该方法对应类实例的锁才能执行,否则所属线程将被阻塞,方法一旦执行,就独占该锁,直到该方法执行完毕将锁释放,被阻塞的线程才能获得锁从而执行。这种机制确保了同一时刻该类同一个实例,所有声明为synchronized的函数中只有一个方法处于可执行状态,从而有效避免了类实例成员变量访问冲突,静态方法是属于类的,普通方法是属于对象本身的,所以一个是对象锁,一个是class锁。
由于静态成员不专属于任何一个实例对象(这句话非常重要,真是因为静态成员本质上并不属于任何一个实例,因此对象锁对于它们是没有意义的,针对静态成员的并发事实上也就是多个线程之间访问同一个静态资源的并发问题),是类成员,因此通过class对象锁可以控制静态 成员的并发操作。需要注意的是如果一个线程A调用一个实例对象的非static synchronized方法,而线程B需要调用这个实例对象所属类的静态 synchronized方法,是允许的,不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的class对象,而访问非静态 synchronized 方法占用的锁是当前实例对象锁.
synchronized修饰代码块
例子
![1f6b4cd7f3e039c6df3aa28071cdecde.png](https://img-blog.csdnimg.cn/img_convert/1f6b4cd7f3e039c6df3aa28071cdecde.png)
![a1a2fa803905c81a394af9d5039aa3d7.png](https://img-blog.csdnimg.cn/img_convert/a1a2fa803905c81a394af9d5039aa3d7.png)
![241702708a08277889f159cb6fe4e2e9.png](https://img-blog.csdnimg.cn/img_convert/241702708a08277889f159cb6fe4e2e9.png)
![bc856007da8594b2e34c838f99004359.png](https://img-blog.csdnimg.cn/img_convert/bc856007da8594b2e34c838f99004359.png)
打印:
线程名字=threadA
线程名字=threadB
i=1
i=1
线程名字=threadA
线程名字=threadB
i=2
i=2
线程名字=threadB
i=3
线程名字=threadB
i=4
线程名字=threadB
i=5
线程名字=threadB
线程名字=threadA
i=3
线程名字=threadA
i=4
线程名字=threadA
i=5
线程名字=threadA
i=6
i=6
线程名字=threadB
线程名字=threadA
i=7
线程名字=threadB
i=8
线程名字=threadB
i=9
线程名字=threadB
i=10
i=7
线程名字=threadA
i=8
线程名字=threadA
i=9
线程名字=threadA
i=10
线程名字=threadA
j=1
线程名字=threadA
j=2
线程名字=threadA
j=3
线程名字=threadA
j=4
线程名字=threadA
j=5
线程名字=threadA
j=6
线程名字=threadA
j=7
线程名字=threadA
j=8
线程名字=threadA
j=9
线程名字=threadA
j=10
线程名字=threadB
j=1
线程名字=threadB
j=2
线程名字=threadB
j=3
线程名字=threadB
j=4
线程名字=threadB
j=5
线程名字=threadB
j=6
线程名字=threadB
j=7
线程名字=threadB
j=8
线程名字=threadB
j=9
线程名字=threadB
j=10
结果:
我们可以看到在执行非synchronized 代码快内的代码时是异步执行的,在synchronized代码块中则是同步执行的。
同步代码块(细粒度锁):
synchronized ( obj ) {...},同步代码块可以指定获取哪个对象上的锁。
synchronized(syncObject) { //访问syncObject的代码}
同步代码块使用的锁是任意对象Object。
同步方法使用的锁是this。
使用静态修饰的同步函数使用的是该类所在的字节码文件对象,格式为类名.class。
同步方法默认用this或者当前类class对象作为锁; 同步代码块可以选择以什么来加锁,比同步方法要更细颗粒度,我们可以选择只同步会发生同步问题的部分代码而不是整个方法;
同步方法默认用this或者当前类class对象作为锁; 同步代码块可以选择以什么来加锁,比同步方法要更细颗粒度,我们可以选择只同步会发生同步问题的部分代码而不是整个方法; 同步方法使用关键字 synchronized修饰方法,而同步代码块主要是修饰需要进行同步的代码,用 synchronized(object){代码内容}进行修饰;
可以用随意一个类对象作为锁
![49306f5edd17f52ecc9c0e1113f0c0b4.png](https://img-blog.csdnimg.cn/img_convert/49306f5edd17f52ecc9c0e1113f0c0b4.png)
![950e3ce5f2eb88b7c00ce289fe3189a2.png](https://img-blog.csdnimg.cn/img_convert/950e3ce5f2eb88b7c00ce289fe3189a2.png)
使用synchronized代码块的好处
在Thread多线程的实现内synchronized的锁对象需要时各个线程都可以访问得到的对象,如类的static变量。如果synchronized使用的锁是对象自己所拥有的,则起不到互斥的效果。
![af11c37fdd10752d61a7bbe71c239c3d.png](https://img-blog.csdnimg.cn/img_convert/af11c37fdd10752d61a7bbe71c239c3d.png)