澳门新葡萄京娱乐场Java synchronized 详解

synchronized 关键字解析

同步锁依赖于对象,每个对象都有一个同步锁。

现有一成员变量 Test,当线程 A 调用 Test 的 synchronized 方法,线程 A
获得 Test 的同步锁,同时,线程 B 也去调用 Test 的 synchronized
方法,此时线程 B 无法获得 Test 的同步锁,必须等待线程 A 释放 Test
的同步锁才能获得从而执行对应方法的代码。

综上,正确使用 synchronized 关键字可确保原子性。

由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题。Java语言提供了专门机制以解决这种冲突,有效避免了同一个数据对象被多个线程同时访问。
需要明确的几个问题:

Java线程同步机制synchronized关键字的理解

关键字: 线程 synchronized

文章出处: 

 

作者:来自ITPUB论坛 

线程同步: 

  由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题。Java语言提供了专门机制以解决这种冲突,有效避免了同一个数据对象被多个线程同时访问。 

  需要明确的几个问题: 

  1)synchronized关键字可以作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块。如果再细的分类,synchronized可作用于instance变量、object
reference(对象引用)、static函数和class literals(类名称字面常量)身上。 

  2)无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象,而不是把一段代码或函数当作锁――而且同步方法很可能还会被其他线程的对象访问。 

  3)每个对象只有一个锁(lock)与之相关联。 

  4)实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。 

  1、synchronized关键字的作用域有二种: 

  1)是某个对象实例内,synchronized
aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。这时,不同的对象实例的
synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法; 

  2)是某个类的范围,synchronized static
aStaticMethod{}防止多个线程同时访问这个类中的synchronized static
方法。它可以对类的所有对象实例起作用。 

  synchronized 方法控制对类成员变量的访问:每个类实例对应一把锁,每个
synchronized
方法都必须获得调用该方法的类实例的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。这种机制确保了同一时刻对于每一个类实例,其所有声明为
synchronized
的成员函数中至多只有一个处于可执行状态(因为至多只有一个能够获得该类实例对应的锁),从而有效避免了类成员变量的访问冲突(只要所有可能访问类成员变量的方法均被声明为
synchronized)。 

  在 Java
中,不光是类实例,每一个类也对应一把锁,这样我们也可将类的静态成员函数声明为
synchronized ,以控制其对类的静态成员变量的访问。 

  synchronized
方法的缺陷:同步方法,这时synchronized锁定的是哪个对象呢?它锁定的是调用这个同步方法对象。也就是说,当一个对象
P1在不同的线程中执行这个同步方法时,它们之间会形成互斥,达到同步的效果。但是这个对象所属的Class所产生的另一对象P2却可以任意调用这个被加了synchronized关键字的方法.同步方法实质是将synchronized作用于object
reference。――那个拿到了P1对象锁的线程,才可以调用P1的同步方法,而对P2而言,P1这个锁与它毫不相干,程序也可能在这种情形下摆脱同步机制的控制,造成数据混乱:(;若将一个大的方法声明为synchronized
将会大大影响效率,典型地,若将线程类的方法 run() 声明为 synchronized
,由于在线程的整个生命期内它一直在运行,因此将导致它对本类任何
synchronized
方法的调用都永远不会成功。当然我们可以通过将访问类成员变量的代码放到专门的方法中,将其声明为
synchronized ,并在主方法中调用来解决这一问题,但是 Java
为我们提供了更好的解决办法,那就是 synchronized 块。 

  2、除了方法前用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。用法是:
synchronized(this){/*区块*/},它的作用域是当前对象。 

  这时锁就是对象,谁拿到这个锁谁就可以运行它所控制的那段代码。当有一个明确的对象作为锁时,就可以这样写程序,但当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的instance变量(它得是一个对象)来充当锁: 

Java代码 

  1. class Foo implements Runnable  
  2.   {  
  3.   private byte[] lock = new byte[0]; // 特殊的instance变量  
  4. 澳门新葡萄京娱乐场,  
  5.   Public void methodA()  
  6.   {  
  7.   synchronized(lock) { //… }  
  8.   
  9.   }  
  10.   //…..  
  11.   
  12.   }  

 

注:零长度的byte数组对象创建起来将比任何对象都经济――查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object
lock = new Object()则需要7行操作码。 

3.将synchronized作用于static 函数,示例代码如下: 

Java代码 

  1. Class Foo  
  2.   {  
  3.   public synchronized static void methodAAA() // 同步的static 函数  
  4.   
  5.   {  
  6.   //….  
  7.   
  8.   }  
  9.   public void methodBBB()  
  10.   {  
  11.   synchronized(Foo.class) // class literal(类名称字面常量)  
  12.   
  13.   } }  

  代码中的methodBBB()方法是把class
literal作为锁的情况,它和同步的static函数产生的效果是一样的,取得的锁很特别,是当前调用这个方法的对象所属的类(Class,而不再是由这个Class产生的某个具体对象了)。 

 

  可以推断:如果一个类中定义了一个synchronized的static函数A,也定义了一个synchronized
的instance函数B,那么这个类的同一对象Obj在多线程中分别访问A和B两个方法时,不会构成同步,因为它们的锁都不一样。B方法的锁是Obj这个对象,而B的锁是Obj所属的那个Class。 

  对共享资源的同步访问更加安全的技巧: 

  1) 定义private 的instance变量+它的
get方法,而不要定义public/protected的instance变量。如果将变量定义为public,对象在外界可以绕过同步方法的控制而直接取得它,并改动它。这也是JavaBean的标准实现方式之一。 

  2)如果instance变量是一个对象,如数组或ArrayList什么的,那上述方法仍然不安全,因为当外界对象通过get方法拿到这个instance对象的引用后,又将其指向另一个对象,那么这个private变量也就变了,岂不是很危险。这个时候就需要将get方法也加上synchronized同步,并且,只返回这个private对象的clone()――这样,调用端得到的就是对象副本的引用了。 

  补充: 

  synchronized关键字是不能继承的,也就是说,基类的方法synchronized
f(){} 在继承类中并不自动是synchronized
f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法。

关键字: 线程 synchronized

文章出处: 

 

作者:来自ITPUB论坛 

线程同步: 

  由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题。Java语言提供了专门机制以解决这种冲突,有效避免了同一个数据对象被多个线程同时访问。 

  需要明确的几个问题: 

  1)synchronized关键字可以作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块。如果再细的分类,synchronized可作用于instance变量、object
reference(对象引用)、static函数和class literals(类名称字面常量)身上。 

  2)无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象,而不是把一段代码或函数当作锁――而且同步方法很可能还会被其他线程的对象访问。 

  3)每个对象只有一个锁(lock)与之相关联。 

  4)实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。 

  1、synchronized关键字的作用域有二种: 

  1)是某个对象实例内,synchronized
aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。这时,不同的对象实例的
synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法; 

  2)是某个类的范围,synchronized static
aStaticMethod{}防止多个线程同时访问这个类中的synchronized static
方法。它可以对类的所有对象实例起作用。 

  synchronized 方法控制对类成员变量的访问:每个类实例对应一把锁,每个
synchronized
方法都必须获得调用该方法的类实例的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。这种机制确保了同一时刻对于每一个类实例,其所有声明为
synchronized
的成员函数中至多只有一个处于可执行状态(因为至多只有一个能够获得该类实例对应的锁),从而有效避免了类成员变量的访问冲突(只要所有可能访问类成员变量的方法均被声明为
synchronized)。 

  在 Java
中,不光是类实例,每一个类也对应一把锁,这样我们也可将类的静态成员函数声明为
synchronized ,以控制其对类的静态成员变量的访问。 

  synchronized
方法的缺陷:同步方法,这时synchronized锁定的是哪个对象呢?它锁定的是调用这个同步方法对象。也就是说,当一个对象
P1在不同的线程中执行这个同步方法时,它们之间会形成互斥,达到同步的效果。但是这个对象所属的Class所产生的另一对象P2却可以任意调用这个被加了synchronized关键字的方法.同步方法实质是将synchronized作用于object
reference。――那个拿到了P1对象锁的线程,才可以调用P1的同步方法,而对P2而言,P1这个锁与它毫不相干,程序也可能在这种情形下摆脱同步机制的控制,造成数据混乱:(;若将一个大的方法声明为synchronized
将会大大影响效率,典型地,若将线程类的方法 run() 声明为 synchronized
,由于在线程的整个生命期内它一直在运行,因此将导致它对本类任何
synchronized
方法的调用都永远不会成功。当然我们可以通过将访问类成员变量的代码放到专门的方法中,将其声明为
synchronized ,并在主方法中调用来解决这一问题,但是 Java
为我们提供了更好的解决办法,那就是 synchronized 块。 

  2、除了方法前用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。用法是:
synchronized(this){/*区块*/},它的作用域是当前对象。 

  这时锁就是对象,谁拿到这个锁谁就可以运行它所控制的那段代码。当有一个明确的对象作为锁时,就可以这样写程序,但当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的instance变量(它得是一个对象)来充当锁: 

Java代码 

  1. class Foo implements Runnable  
  2.   {  
  3.   private byte[] lock = new byte[0]; // 特殊的instance变量  
  4.   
  5.   Public void methodA()  
  6.   {  
  7.   synchronized(lock) { //… }  
  8.   
  9.   }  
  10.   //…..  
  11.   
  12.   }  

 

注:零长度的byte数组对象创建起来将比任何对象都经济――查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object
lock = new Object()则需要7行操作码。 

3.将synchronized作用于static 函数,示例代码如下: 

Java代码 

  1. Class Foo  
  2.   {  
  3.   public synchronized static void methodAAA() // 同步的static 函数  
  4.   
  5.   {  
  6.   //….  
  7.   
  8.   }  
  9.   public void methodBBB()  
  10.   {  
  11.   synchronized(Foo.class) // class literal(类名称字面常量)  
  12.   
  13.   } }  

  代码中的methodBBB()方法是把class
literal作为锁的情况,它和同步的static函数产生的效果是一样的,取得的锁很特别,是当前调用这个方法的对象所属的类(Class,而不再是由这个Class产生的某个具体对象了)。 

 

  可以推断:如果一个类中定义了一个synchronized的static函数A,也定义了一个synchronized
的instance函数B,那么这个类的同一对象Obj在多线程中分别访问A和B两个方法时,不会构成同步,因为它们的锁都不一样。B方法的锁是Obj这个对象,而B的锁是Obj所属的那个Class。 

  对共享资源的同步访问更加安全的技巧: 

  1) 定义private 的instance变量+它的
get方法,而不要定义public/protected的instance变量。如果将变量定义为public,对象在外界可以绕过同步方法的控制而直接取得它,并改动它。这也是JavaBean的标准实现方式之一。 

  2)如果instance变量是一个对象,如数组或ArrayList什么的,那上述方法仍然不安全,因为当外界对象通过get方法拿到这个instance对象的引用后,又将其指向另一个对象,那么这个private变量也就变了,岂不是很危险。这个时候就需要将get方法也加上synchronized同步,并且,只返回这个private对象的clone()――这样,调用端得到的就是对象副本的引用了。 

  补充: 

  synchronized关键字是不能继承的,也就是说,基类的方法synchronized
f(){} 在继承类中并不自动是synchronized
f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法。

关键字: 线程 synchronized

文章出处: 

 

作者:来自ITPUB论坛 

线程同步: 

  由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题。Java语言提供了专门机制以解决这种冲突,有效避免了同一个数据对象被多个线程同时访问。 

  需要明确的几个问题: 

  1)synchronized关键字可以作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块。如果再细的分类,synchronized可作用于instance变量、object
reference(对象引用)、static函数和class literals(类名称字面常量)身上。 

  2)无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象,而不是把一段代码或函数当作锁――而且同步方法很可能还会被其他线程的对象访问。 

  3)每个对象只有一个锁(lock)与之相关联。 

  4)实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。 

  1、synchronized关键字的作用域有二种: 

  1)是某个对象实例内,synchronized
aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。这时,不同的对象实例的
synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法; 

  2)是某个类的范围,synchronized static
aStaticMethod{}防止多个线程同时访问这个类中的synchronized static
方法。它可以对类的所有对象实例起作用。 

  synchronized 方法控制对类成员变量的访问:每个类实例对应一把锁,每个
synchronized
方法都必须获得调用该方法的类实例的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。这种机制确保了同一时刻对于每一个类实例,其所有声明为
synchronized
的成员函数中至多只有一个处于可执行状态(因为至多只有一个能够获得该类实例对应的锁),从而有效避免了类成员变量的访问冲突(只要所有可能访问类成员变量的方法均被声明为
synchronized)。 

  在 Java
中,不光是类实例,每一个类也对应一把锁,这样我们也可将类的静态成员函数声明为
synchronized ,以控制其对类的静态成员变量的访问。 

  synchronized
方法的缺陷:同步方法,这时synchronized锁定的是哪个对象呢?它锁定的是调用这个同步方法对象。也就是说,当一个对象
P1在不同的线程中执行这个同步方法时,它们之间会形成互斥,达到同步的效果。但是这个对象所属的Class所产生的另一对象P2却可以任意调用这个被加了synchronized关键字的方法.同步方法实质是将synchronized作用于object
reference。――那个拿到了P1对象锁的线程,才可以调用P1的同步方法,而对P2而言,P1这个锁与它毫不相干,程序也可能在这种情形下摆脱同步机制的控制,造成数据混乱:(;若将一个大的方法声明为synchronized
将会大大影响效率,典型地,若将线程类的方法 run() 声明为 synchronized
,由于在线程的整个生命期内它一直在运行,因此将导致它对本类任何
synchronized
方法的调用都永远不会成功。当然我们可以通过将访问类成员变量的代码放到专门的方法中,将其声明为
synchronized ,并在主方法中调用来解决这一问题,但是 Java
为我们提供了更好的解决办法,那就是 synchronized 块。 

  2、除了方法前用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。用法是:
synchronized(this){/*区块*/},它的作用域是当前对象。 

  这时锁就是对象,谁拿到这个锁谁就可以运行它所控制的那段代码。当有一个明确的对象作为锁时,就可以这样写程序,但当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的instance变量(它得是一个对象)来充当锁: 

Java代码 

  1. class Foo implements Runnable  
  2.   {  
  3.   private byte[] lock = new byte[0]; // 特殊的instance变量  
  4.   
  5.   Public void methodA()  
  6.   {  
  7.   synchronized(lock) { //… }  
  8.   
  9.   }  
  10.   //…..  
  11.   
  12.   }  

 

注:零长度的byte数组对象创建起来将比任何对象都经济――查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object
lock = new Object()则需要7行操作码。 

3.将synchronized作用于static 函数,示例代码如下: 

Java代码 

  1. Class Foo  
  2.   {  
  3.   public synchronized static void methodAAA() // 同步的static 函数  
  4.   
  5.   {  
  6.   //….  
  7.   
  8.   }  
  9.   public void methodBBB()  
  10.   {  
  11.   synchronized(Foo.class) // class literal(类名称字面常量)  
  12.   
  13.   } }  

  代码中的methodBBB()方法是把class
literal作为锁的情况,它和同步的static函数产生的效果是一样的,取得的锁很特别,是当前调用这个方法的对象所属的类(Class,而不再是由这个Class产生的某个具体对象了)。 

 

  可以推断:如果一个类中定义了一个synchronized的static函数A,也定义了一个synchronized
的instance函数B,那么这个类的同一对象Obj在多线程中分别访问A和B两个方法时,不会构成同步,因为它们的锁都不一样。B方法的锁是Obj这个对象,而B的锁是Obj所属的那个Class。 

  对共享资源的同步访问更加安全的技巧: 

  1) 定义private 的instance变量+它的
get方法,而不要定义public/protected的instance变量。如果将变量定义为public,对象在外界可以绕过同步方法的控制而直接取得它,并改动它。这也是JavaBean的标准实现方式之一。 

  2)如果instance变量是一个对象,如数组或ArrayList什么的,那上述方法仍然不安全,因为当外界对象通过get方法拿到这个instance对象的引用后,又将其指向另一个对象,那么这个private变量也就变了,岂不是很危险。这个时候就需要将get方法也加上synchronized同步,并且,只返回这个private对象的clone()――这样,调用端得到的就是对象副本的引用了。 

  补充: 

  synchronized关键字是不能继承的,也就是说,基类的方法synchronized
f(){} 在继承类中并不自动是synchronized
f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法。

synchronized 关键字的特性应用

  • synchronized关键字可以作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块。如果
    再细的分类,synchronized可作用于instance变量、object
    reference(对象引用)、static函数和class
    literals(类名称字面常量)身上。
  • 无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象,而不是把一段代码或函数当作锁――而且同步方法很可能还会被其他线程的对象访问。
  • 每个对象只有一个锁(lock)与之相关联。
  • 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。

特性 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

同步锁基于对象,只要锁的来源一致,即可达到同步的作用。所以,但对象不一样,则不能达到同步效果。

synchronized关键字的作用域有二种:

  1. 某个对象实例内,synchronized
    aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线
    程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。这时,不同的对象实例的
    synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法;
  2. 某个类的范围,synchronized static
    aStaticMethod{}防止多个线程同时访问这个类中的synchronized static
    方法。它可以对类的所有对象实例起作用。

特性 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 代码块时可立即调用。

synchronized 方法

每个类实例对应一把锁,每个 synchronized
方法都必须获得调用该方法的类实例的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。这种机制确保了同一时刻对于每一个类实例,其所有声明为
synchronized
的成员函数中至多只有一个处于可执行状态(因为至多只有一个能够获得该类实例对应的锁),从而有效避免了类成员变量的访问冲突(只要所有可能访问类成员变量的方法均被声明为
synchronized)。
在 Java
中,不光是类实例,每一个类也对应一把锁,这样我们也可将类的静态成员函数声明为
synchronized ,以控制其对类的静态成员变量的访问。

实例锁和全局锁

以上 DEMO 实现的都是实例锁。锁住(作用域)的是具体某一对象实例。

什么是全局锁?

锁住整个 Class,而非某个对象或实例。

注:单例型的实例锁不属于全局锁。

synchronized 方法的缺陷

同步方法,这时synchronized锁定的是哪个对象呢?它锁定的是调用这个同步方法对象。也就是说,当一个对象
P1在不同的线程中执行这个同步方法时,它们之间会形成互斥,达到同步的效果。但是这个对象所属的Class所产生的另一对象P2却可以任意调用这个被加
了synchronized关键字的方法.同步方法实质是将synchronized作用于object
reference。――那个拿到了P1对象锁的线程,才可以调用P1的同步方法,而对P2而言,P1这个锁与它毫不相干,程序也可能在这种情形下摆脱同步机制的控制,造成数据混乱:(
;若将一个大的方法声明为synchronized
将会大大影响效率,典型地,若将线程类的方法 run() 声明为 synchronized
,由于在线程的整个生命期内它一直在运行,因此将导致它对本类任何
synchronized
方法的调用都永远不会成功。当然我们可以通过将访问类成员变量的代码放到专门的方法中,将其声明为
synchronized ,并在主方法中调用来解决这一问题,但是 Java
为我们提供了更好的解决办法,那就是 synchronized 块。

全局锁的实现:

静态 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 代码块

除了方法前用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。用法是:
synchronized(this){/区块/},它的作用域是当前对象。
这时锁就是对象,谁拿到这个锁谁就可以运行它所控制的那段代码。当有一个明确的对象作为锁时,就可以这样写程序,但当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的instance变量(它得是一个对象)来充当锁:

class Foo implements Runnable {
       private byte[] lock = new byte[0]; // 特殊的instance变量    
       Public void methodA() {      
         synchronized(lock) { //… }
       }
       //…..
}

注:零长度的byte数组对象创建起来将比任何对象都经济――查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object
lock = new Object()则需要7行操作码。

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 静态方法

将synchronized作用于static 函数,示例代码如下:

Class Foo {
  // 同步的static 函数
  public synchronized static void methodAAA()  {
  //….
  }
  public void methodBBB() {
       synchronized(Foo.class)   // class literal(类名称字面常量)
  }    
}

代码中的methodBBB()方法是把class
literal作为锁的情况,它和同步的static函数产生的效果是一样的,取得的锁很特别,是当前调用这个方法的对象所属的类(Class,而不再是由这个Class产生的某个具体对象了)。

可以推断:如果一个类中定义了一个synchronized 的 static
函数A,也定义了一个 synchronized 的
instance函数B,那么这个类的同一对象Obj在多线程中分别访问A和B两个方法时,不会构成同步,因为它们的锁都不一样。B方法的锁是Obj这个对象,而B的锁是Obj所属的那个Class。

区分 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) 获得的是全局锁,两把不同的锁,所以不能达到同步效果。

发表评论

电子邮件地址不会被公开。 必填项已用*标注