关键字synchronized的使用

为了解决非线程安全问题,就需要使用线程同步。实现线程同步的一种方式就是使用synchronized关键字。

1.synchronized的用法

(1)synchronized可以修饰方法,表示这个方法在任意时刻只能由一个线程访问。

(2)synchronized用在类声明中,表明该类中的所有方法都是synchronized的。

(3)synchronized还可以用在一段代码中,被synchronized关键字修饰的代码就叫做同步语句块。

Java引入了对象互斥锁的概念,来保证数据共享操作的完整性。Java中每个对象都对应一个称为“互斥锁”的标记,即每个对象都有属于自己的一个锁Lock。被synchrionized修饰的对象,方法或代码,就相当于给他们加了锁,加锁的这段代码成为“互斥区”或“临界区”。

被synchronized关键字修饰的代码一定是排队运行的。

下面分别介绍的这几种用法。

2.synchronized修饰方法

被synchrionized修饰的方法也叫同步方法。synchrionized修饰的方法有几个重要的特性:

(1)关键字synchrionized修饰的方法取得的锁是对象锁,而不是一段代码或方法。

哪个线程先执行带synchrionized关键字的代码,哪个线程就持有该方法所属对象的锁Lock,如果多个线程访问的是同一个对象,那么其他线程就必须等待。

(2)synchrionized修饰的方法不影响非synchrionized方法的执行

如果两个线程A和B访问同一个对象object,如果A先持有了object对象的Lock锁,B线程可以以异步的方式调用object对象中的非synchrionized方法。

(3)一个对象中的多个synchrionized方法要排队执行

对象object有两个被synchrionized关键字修饰的同步方法,如果A访问object对象的一个同步方法时,先持有了object对象的Lock锁,B线程如果这时调用object的另一个同步方法,就必须等待,也就是同步。由此也可以看出关键字synchrionized修饰的方法取得的锁是对象锁,在object的一个synchrionized方法未被线程访问完毕时,其他的线程不能访问object。

(4)关键字synchrionized具有锁重入的功能。

锁重入就是在使用synchrionized时,当一个线程得到一个对象锁后,再次请求此对象锁时可以再次得到该对象的锁。因此在一个synchrionized方法/块的内部调用本类的其他synchrionized方法/块时,是永远可以得到锁的。

“可重入锁”就是自己可以再次获取自己的内部锁。比如一条线程获得了某个对象的锁,此时这个对象锁还没有释放,当其想要再次获得这个对象的锁的时候还是可以获取的,如果锁不可以重入的话,就会造成死锁。

“可重入锁”也支持在父子类继承的环境中。当存在父子类继承关系时,子类完全可以通过“可重入锁”调用父类的同步方法。

(5)当一个线程执行的代码出现异常时,其所持有的锁会自动释放。

如果一个线程在执行synchrionized方法的过程中出现异常,即使该方法没有执行完毕,线程也会自动释放原来占有的对象锁供其他线程使用。

(6)同步不具有继承性。

子类继承父类,父类中有一个同步方法,那么子类重写和调用该父类方法并不是同步执行的,必须也改成synchrionized方法才能同步执行。

(7)静态同步synchronized方法作用的范围是整个Class类

关键字synchronized还可以用在static静态方法上,如果这样写,就是对当前的*.java文件对应的Class类进行持锁。synchronized关键字加到static静态方法上是给Class类上锁,而synchronized关键字加到非static静态方法上是给对象上锁。

3.synchronized修饰代码段

用关键字synchrionized修饰的方法在某些情况下是有弊端的。比如线程A调用调用同步方法执行了一个长时间的任务,那么B线程要想使用该方法必须等待比较长的时间。在这种情况下可以使用synchrionized同步语句块来解决。

synchrionized修饰的代码块也有几个特征:

(1)synchrionized(this)代码块同步执行

和synchrionized修饰方法类似,当两个并发线程访问同一对象object的synchrionized(this)的同步代码块时,一段时间内只能有一个线程被执行,另一个线程必须等待当前线程执行完该代码以后才能接着执行该代码块。通常synchrionized(this)的代码块是在方法中,形式如:

public class Demo{
     public void method( ){
         synchronized(this){
         //同步代码块中的内容
         }
         //其他代码
     }
}

 如何使用synchrionized(this)代码块解决上述提到的使用synchrionized方法,程序的执行效率低的问题呢?

(2)当一个线程访问object的同步代码块时,另一个线程仍然可以访问该object对象中的非synchrionized(this)同步代码块。

有了这个结论,那我们在执行有关对共享数据进行访问和操作的代码时,可以把比较耗时但不涉及共享数据访问的操作的代码放到非同步代码块中,同步代码块中只存放和共享数据访问和操作的代码,这样就能减少多线程执行的时间,提高程序运行效率。

(3)不在synchrionized(this)的代码块中的就不是同步执行,在synchrionized(this)代码块中的就是同步执行。

根据第(2)个结论,也就很容易得出这个结论。

(4)一个对象中的多个synchrionized(this)代码块要排队执行

object中有多个synchrionized(this)方法。当一个线程访问object的一个synchrionized(this)同步代码块时,其他线程对同一个关键字修饰的object中所有其他对象synchrionized(this)同步代码块的访问将被阻塞,说明synchronized使用的“对象监视器”是一个,都是object对象。

(5)synchrionized(this)锁定当前对象。

和synchrionized方法一样,synchrionized(this)锁定的也是当前对象。

(6)将实例变量及方法参数作为对象监视器

前面都是使用synchrionized(this)来同步代码块,其实Java还支持“任意对象”作为“对象监视器”来实现同步的功能。这个“任意对象”大多数是实例变量及方法的参数,使用格式为synchrionized(非this对象)。

锁非this对象具有一定的优点:如果在一个类中有很多synchronized方法,这时虽然能实现同步,但会因为同步受到阻塞,所以影响运行效率;但如果使用同步代码块锁非this对象,则synchronized(非this)代码块中的程序与同步方法是异步的,不与其他锁this同步方法争抢this锁,则可大大提高运行效率。

使用“synchrionized(非this对象x)”同步代码块格式进行同步操作时,对象监视器必须是同一个对象。如果不是同一个对象监视器,运行的结果就是异步调用,就会交叉运行。

(7)将类作为对象监视器

同步synchrionized(class)就是将类作为对象监视器,同步synchrionized(class)代码块的作用和synchrionized static方法的作用是一样的,锁住的都是整个对象。

(8)不使用String字符串做对象监视器

同步synchrionized(非this对象)大多数情况下不使用String字符串作为对象的监视器,而改用其他,比如new Object( )实例化一个object对象。如果想要实现的效果是异步的,使用字符串对象作为锁对象,那么字符串应该是不同的。但是现在如果有两个线程拥有不同的string对象,两个string对象的值却相同,那么这两个对象就持有相同的锁,所以在一个线程执行时另一个线程就不能执行了。这是因为String常量池具有缓冲作用所带来的效果。

另外

(1)如果一个对象有既有同步方法也有同步代码块,那么两个线程分别调用同步方法和同步代码块,他们之间的执行就是异步的,因为同步方法和同步代码块持有的不是同一个锁。只有持有同一个锁的方法和代码才是同步执行的。

4.使用同步会引起的问题

(1)同步方法容易造成死循环

如果调用同一个对象的两个synchronized方法,那么它们是同步执行的。这时如果第一个正在执行的synchronized方法出现死循环,那么第二个synchronized方法将永远得不到运行的机会。

解决方法是可以把同步方法变为同步块,两个方法中声明不同的同步块,并且同步块中的对象监视器是不同的,这样这两个方法就是异步执行,这两个方法的执行就不会相互影响了。

(2)多线程死锁

当多个线程共享一个资源的时候需要进行同步,但是过多的同步可能导致死锁。Java线程死锁就是所有的线程都在等待其他线程持有的锁被释放而对自己持有的锁又保持不放,不同的线程都在等待根本不可能释放的锁,最后就是没有锁释放,导致所有的任务都无法继续向前推进。只要互相等待对方释放锁就有可能出现死锁。

当两个或两个以上的线程在执行过程中,因争夺资源而造成了互相等待,并且若无外力作用,它们都将无法推进下去的现象称为系统处在死锁状态或系统产生了死锁。

资源占用是互斥的,当某个线程提出申请资源后,如果有关线程在无外力协助下,永远分配不到必需的资源而无法继续运行,就会发生死锁。

产生死锁的必要条件:

互斥条件:指线程对所分配到的资源进行排它性使用。

请求和保持条件:指线程已经保持至少一个资源,但又提出了新的资源请求。

不剥夺条件:进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。

循环等待条件:指在发生死锁时,必然存在一个线程——资源的环形链。

在多线程技术中,死锁是必须避免的,因为这会造成线程的“假死”。在设计程序时要避免双方互相持有对方的锁的情况。

关于snchronized这些特点和用法的更多实例,可以参考:《Java多线程编程核心技术》---高洪岩(Chapter2 2.1,2.2)的内容。

猜你喜欢

转载自blog.csdn.net/kongmin_123/article/details/81301317