概述
免责声明: 这是本人第一个原创系列,希望以轻松幽默的风格讲解代码的实现原理,更因为本人水平有限,难免有疏漏的地方。如果读者遇到文章中需要改进或者看不懂,甚至是觉得错误的地方,可以给我留言。
文章中重要名词会以高亮形式展示,如synchonized
, 如果文章中以粗体展示,如 synchronized,表示这一段比较重要。 如果文章中以细斜体展示,如synchronized,表示这一段的叙述我并没有十分把握,仅以参考为主。
本实验平台主要是基于本人的 MacbookPro 基于 jdk1.8,HotSpot macOS Catalina 10.15 内存 8 GB 2133 MHz LPDDR3
$ uname -a
Darwin MacBook-Pro.local 19.0.0 Darwin Kernel Version 19.0.0: Wed Sep 25 20:18:50 PDT 2019; root:xnu-6153.11.26~2/RELEASE_X86_64 x86_64
$ java -version
java version "1.8.0_181"
Java(TM) SE Runtime Environment (build 1.8.0_181-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.181-b13, mixed mode)
openjdk 版本为 8u60 下载地址为 openjdk-8u60
helloworld
众所周知,synchronized
是java
中的关键字。 既然从青铜段位开始,我们就从synchronized
的用法开始说起。synchronized
主要有代码块和修饰方法两种使用方法。
代码块
代码块又可以分为对象和类
Object o = new Object();
synchronized (o) {
...
}
synchronized (Object.class) {
...
}
方法
方法又可以分为修饰实例方法和静态方法
public synchronized String method() {
return "实例方法";
}
public static synchronized String method() {
return "静态方法";
}
优缺点
修饰方法的话使用起来是最简单的,但是最小颗粒就是方法级别的了,而且只能以当前持有方法的对象或者是类为锁。 代码块的话,颗粒度可以自己控制,并且还能以任意对象作为锁对象,更自由。 我个人还是更倾向于代码块。
jdk的举例
jdk中已经有很多地方用到了该关键字,让我们来看看吧
代码块
public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
implements ConcurrentMap<K,V>, Serializable {
...
final V putVal(K key, V value, boolean onlyIfAbsent) {
for (Node<K,V>[] tab = table;;) {
if (tab == null || (n = tab.length) == 0)
tab = initTable();
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
synchronized (f) {
...
}
}
}
...
}
...
}
public class Observable {
...
public void notifyObservers(Object arg) {
...
synchronized (this) {
if (!changed)
return;
arrLocal = obs.toArray();
clearChanged();
}
...
}
}
方法
直接修饰方法,的确可以做到线程安全,但是过于粗暴的加锁也极大的降低了并发时候的性能。
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
...
@Override
public synchronized StringBuffer append(CharSequence s) {
...
}
@Override
public synchronized StringBuffer append(float f) {
...
}
@Override
public synchronized StringBuffer append(double d) {
...
}
...
}
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable {
...
@Override
public synchronized V put(K key, V value) {
...
}
@Override
public synchronized V remove(Object key) {
...
}
@Override
public synchronized V get(Object key) {
...
}
...
}
字节码指令
举了这些使用示例,synchronized
究竟是如何实现的呢?还是先从最简单的字节码指令说起。使用javap
命令可以把class
文件转成字节码指令
代码块
假设我有这样一个class
public class SynchronizedTest {
public static void main(String[] args) {
int i = 1;
synchronized (SynchronizedTest.class) {
i++;
}
System.out.println(i);
}
}
通过javap -v
可以查看class
文件
$ javap -v SynchronizedTest
0: iconst_1
1: istore_1
2: ldc #2 // class jvmtest/SynchronizedTest
4: dup
5: astore_2
6: monitorenter
7: iinc 1, 1
10: aload_2
11: monitorexit
12: goto 20
15: astore_3
16: aload_2
17: monitorexit
18: aload_3
19: athrow
20: iload_1
21: invokestatic #3 // Method syncMethod:(I)I
24: istore_1
25: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
28: iload_1
29: invokevirtual #5 // Method java/io/PrintStream.println:(I)V
32: return
重点看下6 monitorenter
,11 monitorexit
,17 monitorexit
,而11
,17
的区别应该就在于抛不抛异常,两条monitorexit
确保了synchronized
能在程序抛出异常后能正常解锁。
方法
同样假设我有这样一个 class
public class SynchronizedTest {
public static void main(String[] args) {
method("laoxun");
}
public static synchronized String method(String name) {
System.out.println(name);
return name;
}
}
通过javap -v
可以查看class
文件
...
public static synchronized java.lang.String method(java.lang.String);
descriptor: (Ljava/lang/String;)Ljava/lang/String;
flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=1, args_size=1
0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
3: aload_0
4: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
7: aload_0
8: areturn
LineNumberTable:
line 11: 0
line 12: 7
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 name Ljava/lang/String;
看到生成的class文件中的method
方法有ACC_SYNCHRONIZED
标识符,JVM 在执行该方法时会隐式的在方法前后加上monitorenter
和monitorexit
,并且根据该方法是实例方法还是静态方法决定锁对象是对象还是类。
代码示例
使用同一个锁对象
public class SynchronizedTest {
private static class Task extends Thread {
private SynchronizedTest synchronizedTest;
public Task(SynchronizedTest synchronizedTest, String name) {
this.synchronizedTest = synchronizedTest;
this.setName(name);
}
@Override
public void run() {
synchronized (this.synchronizedTest) {
try {
System.out.println(String.format("%s %s start", this.getName(), System.currentTimeMillis()));
Thread.sleep(5000L);
System.out.println(String.format("%s %s end", this.getName(), System.currentTimeMillis()));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
SynchronizedTest synchronizedTest = new SynchronizedTest();
Task t1 = new Task(synchronizedTest, "t1");
Task t2 = new Task(synchronizedTest, "t2");
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("main end");
}
}
控制台输出
t1 1589450854584 start
t1 1589450859607 end
t2 1589450859608 start
t2 1589450864608 end
main end
可以看到两个线程直接是同步的
使用不同的锁对象
把 t2 修改下,t1使用对象作为锁,t2 使用该对象的类作为锁
public class SynchronizedTest {
private static class Task extends Thread {
private SynchronizedTest synchronizedTest;
public Task(SynchronizedTest synchronizedTest, String name) {
this.synchronizedTest = synchronizedTest;
this.setName(name);
}
@Override
public void run() {
synchronized (this.synchronizedTest) {
try {
System.out.println(String.format("%s %s start", this.getName(), System.currentTimeMillis()));
Thread.sleep(5000L);
System.out.println(String.format("%s %s end", this.getName(), System.currentTimeMillis()));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
private static class TaskClass extends Thread {
public TaskClass(String name) {
this.setName(name);
}
@Override
public void run() {
synchronized (SynchronizedTest.class) {
try {
System.out.println(String.format("%s %s start", this.getName(), System.currentTimeMillis()));
Thread.sleep(5000L);
System.out.println(String.format("%s %s end", this.getName(), System.currentTimeMillis()));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
SynchronizedTest synchronizedTest = new SynchronizedTest();
Task t1 = new Task(synchronizedTest, "t1");
TaskClass t2 = new TaskClass("t2");
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("main end");
}
}
控制台输出
t2 1589451037997 start
t1 1589451037997 start
t2 1589451043022 end
t1 1589451043022 end
main end
两个线程是同时执行的
总结
青铜篇就介绍到这里,向大家介绍了synchronized
两种基本用法,当锁对象相同的所有线程执行是同步的,不同的锁对象之间的线程是异步的。 别急,毕竟只是第一篇,循序渐进慢慢来,如果你这一篇读完了,恭喜你可能实习的岗位也面不上。哈哈哈
扫一扫,关注它
最后
以上就是呆萌睫毛膏为你收集整理的Java 之 synchronized 讲解-青铜段的全部内容,希望文章能够帮你解决Java 之 synchronized 讲解-青铜段所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复