从不同步的代码块中调用了对象同步方法。_java面试官请说出synchronized的使用方法,十万并发项目必备技能...

介绍

synchronized 是Java多线程中的同步锁机制可以对方法、对象或代码块进行加锁,保证在同一时间只有一个线程操作对应的资源,避免多线程同时访问相同的资源发生冲突。简单来说,synchronized 就是用来控制线程同步的,被加锁的代码块,不能被多个线程同时执行。

用法

synchronized修饰方法

synchronized修饰一个静态的方法

synchronized修饰代码块

修饰一个类

synchronized修饰普通方法

锁是当前实例对象, 进入同步代码前要获得当前实例的锁;

如果多个线程访问同一个对象的实例变量,可能出现非线程安全问题。

例子:a线程set Mydata对象的数据后b线程是否可以同时set

Mydata类

61ed151c90c574edbd800ef190629d1a.png

a线程:

f64ca6b63affd97c001d0831a0768dd2.png

b线程:

c3dfd448d8a79a80543fae07f6f28b8b.png

TestDemo:测试

86c23215a4ccb4c3a8a3180106c3ed45.png

打印:

执行当前方法线程的名字threadB
jeig执行当前方法线程的名字threadA
threadA线程执行完毕 
threadAnum=5
threadB线程执行完毕 
threadBnum=5

结果:a线程set后b线程可以乘机set,造成数据不同步,这样我们称它为线程不安全。

这时我们使用synchronized修饰SetNum方法:

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

synchronized具有锁重入功能,也就是说一个线程获得锁,再次请求是可以再次得到对象的锁的

什么是可重入锁呢?

当一个线程获取了某个对象锁以后,还可以再次获得该对象锁。

什么时候我们会用到可重入锁呢?

看下面的demo,改一下MyData

f5124771f042dd0755bf989d5ef8d56f.png

运行:

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
8aaffce081436c75265b957cecd8bf58.png
747ceb4ac1c875340fc39e22590584dd.png
0ea195ad418d468def0fec86a1b6d103.png

打印:

执行当前方法线程的名字threadB
执行当前方法线程的名字threadA
threadBnum=7
threadAnum=5
threadA线程执行完毕 
threadB线程执行完毕

现在我们给SetNum加上Static修饰

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
a1a2fa803905c81a394af9d5039aa3d7.png
241702708a08277889f159cb6fe4e2e9.png
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
950e3ce5f2eb88b7c00ce289fe3189a2.png

使用synchronized代码块的好处

在Thread多线程的实现内synchronized的锁对象需要时各个线程都可以访问得到的对象,如类的static变量。如果synchronized使用的锁是对象自己所拥有的,则起不到互斥的效果。

af11c37fdd10752d61a7bbe71c239c3d.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值