synchronized 锁改变人生(类的实例对象的锁、Class实例对象的锁、代码块锁)

来源:互联网 发布:淘宝标题关键词优化 编辑:程序博客网 时间:2024/06/05 21:17

0、要从0开始,哈哈,因为工作的关系,并发用的不算多,synchronized用的不多,但是也必须要深入理解啊,咱们今天就搞搞synchronized,一探究竟。


1、先说说what is 线程安全?

答:用我自己话讲就是,如果一个实例方法是线程安全的,那就意味着同一时刻只有一个线程可以执行该实例方法。打个比方,假设实例方法比喻成公共卫生间,如果是线程安全的,那么这个卫生间就是那种独立卫生间,可以锁门,反之线程不安全,就是那种咱们原来学校的那种厕所(屁股对着屁股在大横勾里,high爆)!!


2、再说说线程不安全?

答:上面也提到一点,其实线程不安全的实例方法、静态方法等,都是可以被多个 线程 异步操作的,几乎几根线程同时会调用同一个方法、或者代码块!!!!!



3、上例子,未启用synchronized 


类,Dog,有一个实例方法Barking,一个实例变量mBarkCount。

class Dog {private int mBarkCount = 0;public void Barking() {long threadId = Thread.currentThread().getId();System.err.println("线程  = " + threadId + " 正在调用……");mBarkCount++;  //每调用一次,就加一次System.out.println("狗狗已经叫了:" + mBarkCount + "次");System.err.println("线程 = " + threadId + "  调用完成……");}}


线程任务

public class MyWorkOne implements Runnable {@Overridepublic void run() {DriveMain.mDogInstance.Barking();}}


驱动类(利用了Junit4框架)

public class DriveMain {public static Dog mDogInstance;@BeforeClasspublic static void beforeGo(){mDogInstance = new Dog();}@Beforepublic void setUp(){}@Afterpublic void tearDown(){}@AfterClasspublic static void afterGo(){}@Testpublic void driveBark() {Thread[] threads = new Thread[5];Runnable runnable = new MyWorkOne();for(Thread thread: threads) {thread = new Thread(runnable);thread.start();}}}
new 5 根Thread实例对象,同时调用同一个Dog实例对象的实例方法Barking


输出结果:

线程  = 12 正在调用……线程  = 16 正在调用……线程 = 12  调用完成……线程 = 16  调用完成……线程  = 15 正在调用……线程 = 15  调用完成……线程  = 13 正在调用……线程  = 14 正在调用……线程 = 13  调用完成……线程 = 14  调用完成……狗狗已经叫了:1次狗狗已经叫了:2次狗狗已经叫了:3次狗狗已经叫了:4次狗狗已经叫了:5次

因为没有使用synchronized修饰实例方法barking(),看的出来一个线程还没有调用完成,另外的线程就也开始调用该实例方法了。


 4、我们为barking()实例方法加上关键字synchronized

public synchronized void Barking() {long threadId = Thread.currentThread().getId();System.err.println("线程  = " + threadId + " 正在调用……");mBarkCount++;  //每调用一次,就加一次System.out.println("狗狗已经叫了:" + mBarkCount + "次");System.err.println("线程 = " + threadId + "  调用完成……");}

再看下输出结果:

线程  = 12 正在调用……狗狗已经叫了:1次狗狗已经叫了:2次狗狗已经叫了:3次狗狗已经叫了:4次线程 = 12  调用完成……线程  = 16 正在调用……线程 = 16  调用完成……线程  = 14 正在调用……线程 = 14  调用完成……线程  = 15 正在调用……线程 = 15  调用完成……线程  = 13 正在调用……线程 = 13  调用完成……狗狗已经叫了:5次
是不是很清晰啊,线程 12 正在调用时,没有其它线程进来,等线程12调用结束,线程16又进来,线程16结束后,线程14才又进来…………………………


5、 因为调用的是实例方法, 所以线程之间获得就是类的实例对象锁,谁拿到锁,谁就去调用,用完了,再去释放锁,其它线程才有机会使用。


6、华丽丽的分割线…………………………………………………………………………………………………………………………………………………………


7、上面说完了实例方法,再让我们看看静态方法加上synchronized的快感

还是上面的例子, 老规矩,没加上锁的静态方法:


给Dog类加上该方法

public static void dogInHeat(){long threadId = Thread.currentThread().getId();System.err.println("线程: " + threadId + " 开始让狗狗发情了…………");System.err.println("线程: " + threadId + " 停止让狗狗发情了…………");}


线程工作类,也改为调用dogInHeat()

public class MyWorkOne implements Runnable {@Overridepublic void run() {Dog.dogInHeat();}}

驱动类
@Testpublic void driveBark() {Thread[] threads = new Thread[5];Runnable runnable = new MyWorkOne();for(Thread thread: threads) {thread = new Thread(runnable);thread.start();}}
输出结果:

线程: 13 开始让狗狗发情了…………线程: 16 开始让狗狗发情了…………线程: 14 开始让狗狗发情了…………线程: 14 停止让狗狗发情了…………线程: 15 开始让狗狗发情了…………线程: 15 停止让狗狗发情了…………线程: 12 开始让狗狗发情了…………线程: 16 停止让狗狗发情了…………线程: 13 停止让狗狗发情了…………线程: 12 停止让狗狗发情了…………
看到了吧,没有加同步锁,线程13还没发情完毕,线程16就进来了


8、为静态方法加上同步锁

public synchronized static void dogInHeat(){long threadId = Thread.currentThread().getId();System.err.println("线程: " + threadId + " 开始让狗狗发情了…………");System.err.println("线程: " + threadId + " 停止让狗狗发情了…………");}


执行结果:

线程: 12 开始让狗狗发情了…………线程: 12 停止让狗狗发情了…………线程: 16 开始让狗狗发情了…………线程: 16 停止让狗狗发情了…………线程: 15 开始让狗狗发情了…………线程: 15 停止让狗狗发情了…………线程: 14 开始让狗狗发情了…………线程: 14 停止让狗狗发情了…………线程: 13 开始让狗狗发情了…………线程: 13 停止让狗狗发情了…………
必须的整整齐齐的,一个线程工作完成后,另外一个线程才能开始调用


9、静态方法,因为是属于类的,所以每个线程拿到的是Class的实例对象的锁,哪根线程拿到该锁(每个class都是Class的实例对象),谁就能调用静态方法,调用完成后,释放锁,其它线程才有机会。。


10、华丽丽的分割线…………………………………………………………………………………………………………………………………………


11、让我们再来,除了为实例方法或者静态方法,整个方法体都加上同步锁。大牛们,还发明了 代码块锁(没错,我只同步一部分,其它随便异步),大写的牛逼………………


12、来把,代码块同步锁(实例对象的锁)

public void Barking() {long threadId = Thread.currentThread().getId();synchronized (this) {System.err.println("线程  = " + threadId + " 正在调用……");mBarkCount++;  //每调用一次,就加一次System.out.println("狗狗已经叫了:" + mBarkCount + "次");System.err.println("线程 = " + threadId + "  调用完成……");}System.out.println("线程  = " + threadId + "未同步: 开始");System.out.println("线程 = "  + threadId + "未同步: 结束");}

输出结果:

线程  = 12 正在调用……狗狗已经叫了:1次线程  = 12未同步: 开始线程 = 12未同步: 结束狗狗已经叫了:2次线程  = 15未同步: 开始线程 = 15未同步: 结束狗狗已经叫了:3次线程 = 12  调用完成……线程  = 15 正在调用……线程 = 15  调用完成……线程  = 16 正在调用……线程 = 16  调用完成……线程  = 14 正在调用……线程  = 16未同步: 开始线程 = 16未同步: 结束狗狗已经叫了:4次线程 = 14  调用完成……线程  = 13 正在调用……线程  = 14未同步: 开始线程 = 14未同步: 结束狗狗已经叫了:5次线程 = 13  调用完成……线程  = 13未同步: 开始线程 = 13未同步: 结束
加了同步锁的代码块,依旧是谁持有锁,谁就可以调用, 上面的synchronized (this),this 必须代表的是当前类的实例对象,谁拿到它的锁,谁就可以调用此部分。(所有线程可要对应同一个实例对象,你别整多个实例对象,实例对象与实例对象之间可是独立的………………)


13、代码块锁(Class的实例对象的锁)

public void Barking() {long threadId = Thread.currentThread().getId();synchronized (Dog.class) {System.err.println("线程  = " + threadId + " 正在调用……");mBarkCount++;  //每调用一次,就加一次System.out.println("狗狗已经叫了:" + mBarkCount + "次");System.err.println("线程 = " + threadId + "  调用完成……");}System.out.println("线程  = " + threadId + "未同步: 开始");System.out.println("线程 = "  + threadId + "未同步: 结束");}
这个 synchronized (Dog.class),可以用在实例方法里,可以用在静态方法里,毕竟是为了同步代码块,这里要强调的是,因为所有的实例对象,或者说一个类只有一个Class实例对象,所以这样的锁,我们也喜欢叫全局锁。

5个线程,创建5个对象,5个线程同时会调用各自实例对象的实例方法,此时一个线程拿到Class对象的锁,那这个同步代码块别的线程,你就不能执行(虽然不在同一个实例对象里)。全局锁的目的就是干这个的…………(我称为别人家的事情,老子也要干预…………zhenjiba草蛋)


14、对于 synchronized的延伸扩展,synchronized(Xx.class),虽然我们在不同的实例对象里被调用,但是哪根线程持有Class实例对象的锁,哪根线程才能调用我,哈哈,就是这么霸道!!!

public class MyWorkOne implements Runnable {@Overridepublic void run() {Dog dog = new Dog();dog.Barking();}}
每个工作线程都new 一个 Dog对象,并调用它的实例方法 Barking(); (CAO,我竟然把实例方法名称,大写开头,我是sb%…………)



class Dog {public void Barking() {long threadId = Thread.currentThread().getId();synchronized (Dog.class) {System.err.println("线程  = " + threadId + " 正在调用……");System.err.println("线程 = " + threadId + "  调用完成……");}}}
Dog有 同步代码块


public class DriveMain {@Testpublic void driveBark() {Thread[] threads = new Thread[5];Runnable runnable = new MyWorkOne();for(Thread thread : threads) {thread = new Thread(runnable);thread.start();}}}

new 5根 线程 , 开始 start()


输出结果:

线程  = 16 正在调用……线程 = 16  调用完成……线程  = 12 正在调用……线程 = 12  调用完成……线程  = 15 正在调用……线程 = 15  调用完成……线程  = 13 正在调用……线程 = 13  调用完成……线程  = 14 正在调用……线程 = 14  调用完成……












0 0