Java多线程并发编程 Synchronized关键字
synchronized 关键字解析
同步锁依赖于对象,每个对象都有一个同步锁。
现有一成员变量 test,当线程 a 调用 test 的 synchronized 方法,线程 a 获得 test 的同步锁,同时,线程 b 也去调用 test 的 synchronized 方法,此时线程 b 无法获得 test 的同步锁,必须等待线程 a 释放 test 的同步锁才能获得从而执行对应方法的代码。
综上,正确使用 synchronized 关键字可确保原子性。
synchronized 关键字的特性应用
特性 1:
当线程 a 调用某对象的synchronized 方法 或者 synchronized 代码块时,若同步锁未释放,其他线程调用同一对象的synchronized 方法 或者 synchronized 代码块时将被阻塞,直至线程 a 释放该对象的同步锁。
demo1,synchronized 方法:
public class test { private static class counter { public synchronized void count() { for (int i = 0; i < 6; i++) { system.out.println(thread.currentthread().getname() + ", i = " + i); } } } private static class mythread extends thread { private counter mcounter; public mythread(counter counter) { mcounter = counter; } @override public void run() { super.run(); mcounter.count(); } } public static void main(string[] var0) { counter counter = new counter(); // 注:mythread1 和 mythread2 是调用同一个对象 counter mythread mythread1 = new mythread(counter); mythread mythread2 = new mythread(counter); mythread1.start(); mythread2.start(); } }
demo1 输出:
thread-0, i = 0 thread-0, i = 1 thread-0, i = 2 thread-0, i = 3 thread-0, i = 4 thread-0, i = 5 thread-1, i = 0 thread-1, i = 1 thread-1, i = 2 thread-1, i = 3 thread-1, i = 4 thread-1, i = 5
demo2,synchronized 代码块:
public class test { private static class counter { public void count() { synchronized (this) { for (int i = 0; i < 6; i++) { system.out.println(thread.currentthread().getname() + ", i = " + i); } } } } private static class mythread extends thread { private counter mcounter; public mythread(counter counter) { mcounter = counter; } @override public void run() { super.run(); mcounter.count(); } } public static void main(string[] var0) { counter counter = new counter(); mythread mythread1 = new mythread(counter); mythread mythread2 = new mythread(counter); mythread1.start(); mythread2.start(); } }
demo2 输出:
thread-0, i = 0 thread-0, i = 1 thread-0, i = 2 thread-0, i = 3 thread-0, i = 4 thread-0, i = 5 thread-1, i = 0 thread-1, i = 1 thread-1, i = 2 thread-1, i = 3 thread-1, i = 4 thread-1, i = 5
可见,当同步锁未释放时,其他线程将被阻塞,直至获得同步锁。
而且 demo1 和 demo2 的输出结果是一样的,synchronized 方法 和 synchronized 代码块的不同之处在于 synchronized 方法 作用域较大,作用于整个方法,而 synchronized 代码块 可控制具体的作用域,更精准控制提高效率。(毕竟阻塞的都是时间啊)
demo3,仅修改 main 方法:
public static void main(string[] var0) { // 注意:mythread1 和 mythread2 传入的 counter 是两个不同的对象 mythread mythread1 = new mythread(new counter()); mythread mythread2 = new mythread(new counter()); mythread1.start(); mythread2.start(); }
demo3 输出:
thread-0, i = 0 thread-1, i = 0 thread-0, i = 1 thread-1, i = 1 thread-1, i = 2 thread-1, i = 3 thread-0, i = 2 thread-1, i = 4 thread-0, i = 3 thread-1, i = 5 thread-0, i = 4 thread-0, i = 5
同步锁基于对象,只要锁的来源一致,即可达到同步的作用。所以,但对象不一样,则不能达到同步效果。
特性 2:
当线程 a 调用某对象的synchronized 方法 或者 synchronized 代码块时,若同步锁未释放,其他线程调用同一对象的其他 synchronized 方法 或者 synchronized 代码块时将被阻塞,直至线程 a 释放该对象的同步锁。(注意:重点是其他)
demo4,仅修改 dootherthings 方法的修饰:
public class test { private static class counter { public synchronized void count() { system.out.println(thread.currentthread().getname() + " sleep"); try { thread.sleep(3000); } catch (interruptedexception e) { e.printstacktrace(); } system.out.println(thread.currentthread().getname() + " awake"); } public synchronized void dootherthings(){ system.out.println(thread.currentthread().getname() + " dootherthings"); } } public static void main(string[] var0) { final counter counter = new counter(); new thread(new runnable() { @override public void run() { counter.count(); } }).start(); new thread(new runnable() { @override public void run() { counter.dootherthings(); } }).start(); } }
demo4 输出:
thread-0 sleep thread-0 awake thread-1 dootherthings
可见,synchronized 获得的同步锁并非仅仅锁住代码,而是锁住整个对象。
此时应提及 happens-before 原则,正因 happens-before 原则的存在才有此现象的发生。
happens-before 原则的其中一条:
管理锁定原则:一个 unlock 操作先行发生于后面对同一个锁的 lock 操作。
(此处暂不作过多解释,解释起来能再写一篇文章了)
demo5,仅修改 dootherthings 方法:
public void dootherthings(){ synchronized (this){ system.out.println(thread.currentthread().getname() + " dootherthings"); } }
demo5 输出:
thread-0 sleep thread-0 awake thread-1 dootherthings
demo4 和 demo5 的输出结果竟然一致!没错,因为他们的同步锁来源一致(都是本实例自己),所以可以达到同步效果。
// 这两个 synchronized 锁的是同一个对象 public synchronized void count(){}; public void dootherthings(){ synchronized (this){} }
demo6,去掉 dootherthings 方法的同步关键字:
public void dootherthings(){ system.out.println(thread.currentthread().getname() + " dootherthings"); }
demo6 输出:
thread-0 sleep thread-1 dootherthings thread-0 awake
当线程 a 调用某对象的synchronized 方法 或者 synchronized 代码块时,无论同步锁是否释放,其他线程调用同一对象的其他 非 synchronized 方法 或者 非 synchronized 代码块时可立即调用。
实例锁和全局锁
以上 demo 实现的都是实例锁。锁住(作用域)的是具体某一对象实例。
什么是全局锁?
锁住整个 class,而非某个对象或实例。
注:单例型的实例锁不属于全局锁。
全局锁的实现:
静态 synchronized 方法
demo7:
public class test { private static class counter { public static synchronized void count() { system.out.println(thread.currentthread().getname() + " sleep"); try { thread.sleep(3000); } catch (interruptedexception e) { e.printstacktrace(); } system.out.println(thread.currentthread().getname() + " awake"); } public static synchronized void dootherthings(){ system.out.println(thread.currentthread().getname() + " dootherthings"); } } public static void main(string[] var0) { new thread(new runnable() { @override public void run() { counter.count(); } }).start(); new thread(new runnable() { @override public void run() { counter.dootherthings(); } }).start(); } }
demo7 输出:
thread-0 sleep thread-0 awake thread-1 dootherthings
static 声明的方法为全局方法,与对象实例化无关,所以 static synchronized 方法为全局同步方法,与对象实例化无关。
synchronized 具体 class 的代码块
demo8:
public class test { private static class counter { public static synchronized void count() { system.out.println(thread.currentthread().getname() + " sleep"); try { thread.sleep(3000); } catch (interruptedexception e) { e.printstacktrace(); } system.out.println(thread.currentthread().getname() + " awake"); } public void dootherthings(){ synchronized (counter.class){ system.out.println(thread.currentthread().getname() + " dootherthings"); } } } public static void main(string[] var0) { new thread(new runnable() { @override public void run() { counter.count(); } }).start(); new thread(new runnable() { @override public void run() { counter counter = new counter(); counter.dootherthings(); } }).start(); } }
demo8 输出:
thread-0 sleep thread-0 awake thread-1 dootherthings
synchronized (counter.class) 获得的同步锁是全局的,static synchronized 获得的同步锁也是全局的,同一个锁,所以达到同步效果。
区分 synchronized (this) 与 synchronized (class.class)
demo9:
public class test { private static class counter { public void count() { synchronized (this){ system.out.println(thread.currentthread().getname() + " sleep"); try { thread.sleep(3000); } catch (interruptedexception e) { e.printstacktrace(); } system.out.println(thread.currentthread().getname() + " awake"); } } public void dootherthings(){ synchronized (counter.class){ system.out.println(thread.currentthread().getname() + " dootherthings"); } } } public static void main(string[] var0) { final counter counter = new counter(); new thread(new runnable() { @override public void run() { counter.count(); } }).start(); new thread(new runnable() { @override public void run() { counter.dootherthings(); } }).start(); } }
demo9 输出:
thread-0 sleep thread-1 dootherthings thread-0 awake
synchronized (this) 获得的是具体对象实例 counter 的锁,而 synchronized (counter.class) 获得的是全局锁,两把不同的锁,所以不能达到同步效果。