前言
文接上篇,本文将继续介绍 Synchronized,感兴趣的小伙伴继续跟博主一起讨论下。
更多、更体系化的内容请持续关注博主,您的 关注、点赞、收藏 都将是小编持续创作的动力!
Synchronized关键字用法
前文中提到synchronized 的用法可以从两个维度上面分类,下面蒋总结讨论具体用法:
1. 根据修饰对象分类
1.1 修饰代码块
synchronized(this|object) {}
synchronized(类.class) {}
1.2 修饰方法
修饰非静态方法
修饰静态方法
2. 根据获取的锁分类
2.1 获取对象锁
synchronized(this|object) {}
修饰非静态方法
2.2 获取类锁
synchronized(类.class) {}
修饰静态方法
Synchronized修饰实例方法
示例:
publicclassSyncDemo { privateintcount; publicsynchronizedvoidadd() { count++; } publicstaticvoidmain(String[] args) throwsInterruptedException { SyncDemosyncDemo=newSyncDemo(); Threadt1=newThread(() -> { for (inti=0; i<10000; i++) { syncDemo.add(); } }); Threadt2=newThread(() -> { for (inti=0; i<10000; i++) { syncDemo.add(); } }); t1.start(); t2.start(); t1.join(); // 阻塞住线程等待线程 t1 执行完成t2.join(); // 阻塞住线程等待线程 t2 执行完成System.out.println(syncDemo.count);// 输出结果为 20000 } }
在上面的代码当中的add
方法只有一个简单的count++
操作,因为这个方法是使用synchronized
修饰的因此每一个时刻只能有一个线程执行add
方法,因此上面打印的结果是20000。如果add
方法没有使用synchronized
修饰的话,那么线程t1和线程t2就可以同时执行add
方法,这可能会导致最终count
的结果小于20000,因为count++
操作不具备原子性。
Synchronized修饰静态方法
示例:
publicclassSyncDemo { privatestaticintcount; publicstaticsynchronizedvoidadd() { count++; } publicstaticvoidmain(String[] args) throwsInterruptedException { Threadt1=newThread(() -> { for (inti=0; i<10000; i++) { SyncDemo.add(); } }); Threadt2=newThread(() -> { for (inti=0; i<10000; i++) { SyncDemo.add(); } }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(SyncDemo.count); // 输出结果为 20000 } }
上面的代码最终输出的结果也是20000,但是与前一个程序不同的是。这里的add
方法用static
修饰的,在这种情况下真正的只能有一个线程进入到add
代码块,因为用static
修饰的话是所有对象公共的,因此和前面的那种情况不同,不存在两个不同的线程同一时刻执行add
方法。
Sychronized修饰多个方法
示例:
publicclassAddMinus { publicstaticintans; publicstaticsynchronizedvoidadd() { ans++; } publicstaticsynchronizedvoidminus() { ans--; } publicstaticvoidmain(String[] args) throwsInterruptedException { Threadt1=newThread(() -> { for (inti=0; i<10000; i++) { AddMinus.add(); } }); Threadt2=newThread(() -> { for (inti=0; i<10000; i++) { AddMinus.minus(); } }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(AddMinus.ans); // 输出结果为 0 } }
在上面的代码当中我们用synchronized
修饰了两个方法,add
和minus
。这意味着在同一个时刻这两个函数只能够有一个被一个线程执行,也正是因为add
和minus
函数在同一个时刻只能有一个函数被一个线程执行,这才会导致ans
最终输出的结果等于0。
对于一个实例对象来说:
publicclassAddMinus { publicintans; publicsynchronizedvoidadd() { ans++; } publicsynchronizedvoidminus() { ans--; } publicstaticvoidmain(String[] args) throwsInterruptedException { AddMinusaddMinus=newAddMinus(); Threadt1=newThread(() -> { for (inti=0; i<10000; i++) { addMinus.add(); } }); Threadt2=newThread(() -> { for (inti=0; i<10000; i++) { addMinus.minus(); } }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(addMinus.ans); } }
上面的代码没有使用static
关键字,因此我们需要new
出一个实例对象才能够调用add
和minus
方法,但是同样对于AddMinus
的实例对象来说同一个时刻只能有一个线程在执行add
或者minus
方法,因此上面代码的输出同样是0。
Synchronized修饰实例方法代码块
示例:
publicclassCodeBlock { privateintcount; publicvoidadd() { System.out.println("进入了 add 方法"); synchronized (this) { count++; } } publicvoidminus() { System.out.println("进入了 minus 方法"); synchronized (this) { count--; } } publicstaticvoidmain(String[] args) throwsInterruptedException { CodeBlockcodeBlock=newCodeBlock(); Threadt1=newThread(() -> { for (inti=0; i<10000; i++) { codeBlock.add(); } }); Threadt2=newThread(() -> { for (inti=0; i<10000; i++) { codeBlock.minus(); } }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(codeBlock.count); // 输出结果为 0 } }
上面的代码当中add
和minus
方法没有使用synchronized
进行修饰,因此一个时刻可以有多个线程执行这个两个方法。在上面的synchronized
代码块当中我们使用了this
对象作为锁对象,只有拿到这个锁对象的线程才能够进入代码块执行,而在同一个时刻只能有一个线程能够获得锁对象。也就是说add
函数和minus
函数用synchronized
修饰的两个代码块同一个时刻只能有一个代码块的代码能够被一个线程执行,因此上面的结果同样是0。
Synchronized修饰静态代码块
示例:
publicclassCodeBlock { privatestaticintcount; publicstaticvoidadd() { System.out.println("进入了 add 方法"); synchronized (CodeBlock.class) { count++; } } publicstaticvoidminus() { System.out.println("进入了 minus 方法"); synchronized (CodeBlock.class) { count--; } } publicstaticvoidmain(String[] args) throwsInterruptedException { Threadt1=newThread(() -> { for (inti=0; i<10000; i++) { CodeBlock.add(); } }); Threadt2=newThread(() -> { for (inti=0; i<10000; i++) { CodeBlock.minus(); } }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(CodeBlock.count); } }
上面代码的锁对象是CodeBlock.class
,这个时候他不再是锁住一个对象了,而是一个类了,这个时候的并发度就变小了,当锁对象是CodeBlock.class
的时候,实例对象之间时不能够并发的,因为这个时候的锁对象是一个类。
总结
Synchronized修饰实例方法:不同的对象之间是可以并发的; Synchronized修饰静态实例方法:不同的对象是不能并发的,但是不同的类之间可以进行并发; Sychronized修饰多个方法:多个方法在同一时刻只能有一个方法被执行,而且只能有一个线程能够执行; Synchronized修饰实例方法代码块:同一个时刻只能有一个线程执行代码块; Synchronized修饰静态代码块:同一个时刻只能有一个线程执行这个代码块,而且不同的对象之间不能够进行并发;
参考&致谢
莫笑少年江湖梦,谁不少年梦江湖.感谢前人的经验、分享和付出,让我们可以有机会站在巨人的肩膀上眺望星辰大海!