Java并发之synchronized解析

    线程安全是并发编程中的重要关注点,应该注意到的是,造成线程安全问题的主要诱因有两点,一是存在共享数据(也称临界资源),二是存在多条线程共同操作共享数据。因此为了解决这个问题,我们需要用到synchronized。

    Java语言的关键字synchronized,可用来给对象和方法或者代码块加锁,我们一般称之为互斥锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码。当两个并发线程访问同一个对象object中的这个加锁同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。然而,当一个线程访问object的一个加锁代码块时,另一个线程仍可以访问该object中的非加锁代码块。

 

synchronized的三种修饰方法:

(1)修饰实例方法(所谓的对象锁),作用于当前实例加锁,进入同步代码前要获得当前实例的锁

(2)修饰静态方法(所谓的类锁),作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁

(3)修饰代码块(可应用于私有锁),指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。

 

对象锁

    方法锁也是对象锁,对象锁有两种表现形式,在代码中的方法上加了synchronized的锁,或者synchronized(this)的代码段。

    每个synchronized方法都必须获得调用该方法的类实例的“锁”方能执行,否则所属线程阻塞。方法一旦执行,就会独占该锁,一直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,从而重新进入可执行状态。注意实例方法不包括静态方法。

    java的所有对象都含有一个互斥锁,这个锁由jvm自动获取和释放。synchronized方法正常返回或者抛异常而终止,jvm会自动释放对象锁。

    当一个线程正在访问一个对象的 synchronized 实例方法,那么其他线程不能访问该对象的其他 synchronized 方法,毕竟一个对象只有一把锁,当一个线程获取了该对象的锁之后,其他线程无法获取该对象的锁,所以无法访问该对象的其他synchronized实例方法,但是其他线程还是可以访问该实例对象的其他非synchronized方法。当然如果两个实例对象锁并不相同,那么由于不同的实例对象拥有不同的实例对象锁,其访问使用synchronized修饰的相同方法是允许的,但是此时线程安全是无法得到保证的。所以我们可以将synchronized作用于静态方法,如此对象锁就是当前的类对象(Class对象),由于无论创建多少个实例对象,但对于的类对象拥有只有一个,所有在这样的情况下对象锁就是唯一的。

 

类锁

    对象锁是用来控制实例方法之间的同步,类锁是用来控制静态方法(或静态变量互斥体)之间的同步。其实类锁只是一个概念上的东西,并不是真实存在的,它只是用来帮助我们理解锁定实例方法和静态方法的区别的。

    由于一个class不论被实例化多少次,其中的静态方法和静态变量在内存中都只有一份。所以,一旦一个静态的方法被声明为synchronized。此类所有的实例对象在调用此方法,共用同一把锁,我们称之为类锁。我们都知道,java类可能会有很多个对象,但是只有1个Class对象,也就是说类的不同实例之间共享该类的Class对象。Class对象其实也仅仅是1个java对象,只不过有点特殊而已。由于每个java对象都有1个互斥锁,而类的静态方法是需要Class对象。所以所谓的类锁,不过是Class对象的锁而已。

    对象锁有两种表现形式,在代码中的方法上加了static和synchronized的锁,或者synchronized(xxx.class)的代码段。

    值得注意的是:如果线程A访问静态 synchronized 方法,而线程B访问非静态 synchronized 方法,两者是不会发生互斥现象的,因为访问静态 synchronized 方法占用的锁是当前类的class对象,而访问非静态 synchronized 方法占用的锁是当前实例对象锁。

 

同步代码块的指定对象私有锁

    在某些情况下,我们编写的方法体可能比较大,同时存在一些比较耗时的操作,而需要同步的代码又只有一小部分,如果直接对整个方法进行同步操作,可能会得不偿失,此时我们可以使用同步代码块的方式对需要同步的代码进行包裹,这样就无需对整个方法进行同步操作了。

    其代码表现形式:在类内部声明一个私有属性如private Object lock,在需要加锁的代码段synchronized(lock)。

    将synchronized作用于一个给定的实例对象,即当前实例对象就是锁对象,每次当线程进入synchronized包裹的代码块时就会要求当前线程持有给定的实例对象的锁,如果当前有其他线程正持有该对象锁,那么新到的线程就必须等待。

 

 

 

猜你喜欢

转载自blog.csdn.net/qq_34490018/article/details/81449840