问题思考: 我们可以带着问题理解 synchronized
问题 1:
有如下一个类 A
- class A {public synchronized void a() {
- }
- public synchronized void b() {
- }
- }
然后创建两个对象
- A a1 = new A();
- A a2 = new A();
然后在两个线程中并发访问如下代码:
- Thread1 Thread2
- a1.a(); a2.a();
请问二者能否构成线程同步?
问题 2:
如果 A 的定义是下面这种呢? 可以构成线程同步
- class A {
- public static synchronized void a() {
- }
- public static synchronized void b() {
- }
- }
1. 理解 synchronized 的含义
synchronized 是 Java 多线程中的同步锁机制可以对方法对象或代码块进行加锁, 保证在同一时间只有一个线程操作对应的资源, 避免多线程同时访问相同的资源发生冲突简单来说, synchronized 就是用来控制线程同步的, 被加锁的代码块, 不能被多个线程同时执行
2.synchronized 主要修饰的对象有以下三种
1. 修饰普通方法一个对象中的加锁方法只允许一个线程访问但要注意这种情况下锁的是访问该方法的实例对象, 多个线程并发访问同一个对象的该方法可以保证线程同步, 若多个线程不同对象访问该方法无法保证线程同步
举个例子:
- public class Syncthread implements Runnable{
- private static int count;
- public Syncthread() {
- count = 0;
- }
- @Override
- public void run() {
- String name = Thread.currentThread().getName();
- test(name);
- }
- public synchronized void test(String a) {
- for (int i = 0; i <5; i++) {
- try {
- System.out.println(Thread.currentThread().getName() + ":" + count++);
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
1> 多个线程同一个对象访问 test 方法
- Syncthread syncthread = new Syncthread();
- Thread thread1 = new Thread(syncthread, "a");
- Thread thread2 = new Thread(syncthread, "b");
- thread1.start();
- thread2.start();
输出结果为:
- a:0
- a:1
- a:2
- a:3
- a:4
- b:5
- b:6
- b:7
- b:8
- b:9
从结果可以看出当 thread1 执行完后, 才会执行 thread2 ,synchronized 修饰的方法, 同一时间只能有一个线程进行访问
2> 另一种情况, 多个线程不同对象访问 test 方法
- Syncthread syncthread = new Syncthread();
- Syncthread syncthread2 = new Syncthread();
- Thread thread1 = new Thread(syncthread, "a");
- Thread thread2 = new Thread(syncthread2, "b");
- thread1.start();
- thread2.start();
输出结果为:
- a:0
- b:1
- a:2
- b:2
- b:3
- a:3
- a:4
- b:4
- b:5
- a:6
从结果看出, test 方法同一时间被多个线程同时访问, 多个线程不同对象访问被加锁的方法, 是无法保证线程同步的
注意:
1. 在定义接口方法时不能使用 synchronized 关键字
2. 造方法不能使用 synchronized 关键字, 但可以使用 synchronized 代码块来进行同步
问题 1 的答案可以呼之欲出了, 是不能构成线程同步的
2. 修饰静态方法由于静态方法是类方法, 所以这种情况加锁的是这个类对象, 这样多个线程不同对象访问该静态方法, 也是可以保证同步的
看一下代码, 我们将 test 方法加上 static 关键字
- public synchronized static void test(String a)
- Syncthread syncthread = new Syncthread();
- Syncthread syncthread2 = new Syncthread();
- Thread thread1 = new Thread(syncthread, "a");
- Thread thread2 = new Thread(syncthread2, "b");
- thread1.start();
- thread2.start();
输出结果为:
- a:0
- a:1
- a:2
- a:3
- a:4
- b:5
- b:6
- b:7
- b:8
- b:9
从结果看出, test 方法同一时间被多个线程同时访问, 多个线程不同对象访问被加锁的静态方法, 是可以保证线程同步的
问题 2 的答案是: 可以线程同步
3. 修饰代码块其中普通代码块 如 synchronized(obj) obj 可以是类中的属性也可以是当前对象, 它的同步效果和修饰普通方法是一样的; synchronized(obj.class) 静态代码块它的同步效果和修饰静态方法类似
用代码解释更容易理解, 看一下代码
1>synchronized(obj.class) 修饰代码块, 我们将 test 方法改为
- public void test(String a) {
- synchronized (Syncthread.class) {
- for (int i = 0; i <5; i++) {
- try {
- System.out.println(Thread.currentThread().getName() + ":" + count++);
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
在多个线程同一个对象, 或者多个线程不同对象的情况下
看下输出结果:
- a:0
- a:1
- a:2
- a:3
- a:4
- b:5
- b:6
- b:7
- b:8
- b:9
果然, 两种情况都可以保证线程同步, 和修饰静态法的效果是一样的
2> synchronized(obj) 修饰代码块, 将 test 方法改为
- synchronized (this) {
- for (int i = 0; i < 5; i++) {
- try {
- System.out.println(Thread.currentThread().getName() + ":" + count++);
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
在多个线程同一个对象情况下, 输出结果为:
- a:0
- a:1
- a:2
- a:3
- a:4
- b:5
- b:6
- b:7
- b:8
- b:9
可以看出, 在多个线程同一个对象情况下, 可以保证线程同步
在多个线程, 不同对象的情况下, 输出结果为:
- b:0
- b:1
- a:2
- a:3
- b:4
- b:5
- a:6
- a:7
- b:8
多个线程, 不同对象的情况下, 不能保证线程同步, 和修饰普通方法的效果是一样的
这里在说一下, synchronized(obj) 修饰代码块
当没有明确的对象作为锁, 只是想让一段代码同步时, 可以创建一个特殊的对象来充当锁如:
- private byte[] lock = new byte[0];// 特殊的变量
- public void method(){
- synchronized(lock){
- // 同步代码块
- }
- }
说明: 零长度的 byte 数组对象创建起来比任何对象都要好, 只需要 3 条操作码, 而 object 则需要 7 行操作码
总结
synchronized 方法控制方位较大, 它会同步对象中所有的 synchronized 方法的代码
synchronized 代码块控制方位较小, 它只会控制代码块中的代码, 而位于代码块之外的代码是可以被多个线程访问的
实现同步是要很大的系统开销作为代价的, 甚至可能造成死锁, 所以尽量避免无谓的同步控制
来源: http://www.jianshu.com/p/4a4070bb07a5