synchronized关键字
Java以提供关键字synchronized的形式,以防止多线程时资源冲突提供了内置支持。当任务要执行被synchronized关键字保护的代码片段时,它将检查锁是否可用,然后获取锁,执行代码,释放锁。
所有对象都自动含有单一的锁(监视器)。当在对象上调用其任意synchronized方法时,此对象都被加锁。
synchronized锁住的是括号里的对象,而不是代码。对于非static的synchronized方法,锁的对象其实就是this。
注:
- synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类(类锁,见下面
synchronized(xxx.class)
的介绍) synchronized methods(){}
与synchronized(this){}
之间本质上是一样的,只是synchronized methods(){}
便于阅读理解,而synchronized(this){}
可以更精确的控制冲突限制访问区域,有时候表现更高效率。- synchronized关键字是不能继承的,也就是派生的子类如果也有同步的要求需再次使用synchronized关键字。
synchronized方法
synchronized methods(){
//方法体
}
由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。
synchronized代码块
synchronized(syncObject) {
/允许访问控制的代码
}
syncObject为互斥量(上锁的目标),一般为类实例或类
当一个线程访问object的一个synchronized(this)
同步代码块时,它就获得了这个object的对象锁。此时其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。
同步代码块synchronized(this)与synchronized(xx.class)的区别
有两种类型的同步锁,一种是对象锁(Object lock),一种是类锁(Class lock)。
对象锁与一个具体的对象实例相关联,每个对象实例只能有一个锁。当一个线程进入一个实例方法或者synchronized(Object o){}
块时对象锁被激活。
一个类锁与一个具体的类相关联,一个类只能有一个锁。当线程进入静态方法或者synchronized(X.class) {}
块时类锁被激活。也就是说类锁会锁定所有该类的实例。
举个栗子(见于CSDN某评论区):
- 假设苹果是一个类,里面有方法吃苹果,有实例苹果1,
synchronized(苹果1){吃苹果}
时(对象锁),此时有一个人吃苹果1,但只能一个一个的吃; - 再实例化一个 实例苹果2,而且此时有两个人a、b分别吃 苹果1 、苹果2 ,这时使用
synchronized(苹果.class)
时(类锁),当a吃苹果(苹果1)时b不能吃苹果,反之亦然。
对象锁与类锁时相互独立的,事实上对一个类来说,可能在某一线程拥有它的类锁的同时,另一个线程拥有它的实例锁。
volatile关键字
首先我们得了解什么原子性。
原子性:在单处理器系统(UniProcessor)中,能够在单条指令中完成的操作称为”原子操作”,因为中断只能发生于指令之间。原子操作是不可分割的,在直到执行完毕之间不会被任何其它任务或事件中断。因此把一个操作这种无法再次分隔和中断的特性称为原子性。
在对称多处理器(Symmetric Multi-Processor)结构中就不同了,由于系统中有多个处理器在独立地运行,即使能在单条指令中完成的操作也有可能受到干扰。
原子操作是不能被线程调度中断的操作。意味着可以利用原子性来编写无锁的代码,这些代码不需要同步。
原子性可以应用于除long和double之外的所有基本类型的“简单操作”,对于这些类型可以保证它们会被当作不可分的(原子)操作来操作内存。
定义long和double类型变量时可以使用volatile关键字,让他们获得(简单赋值和返回操作的)原子性。
使用方式:
volatile 成员变量
原子操作确保了应用中的可视性,如果将一个域申明为volatile,那么只要对这个域产生了写操作,那么所有的读操作都可以看到这个修改。即便使用了本地缓存也是如此,volatile域会被立即写到主存中,而读写操作就发生在主存中。
- volatile关键字为域变量的访问提供了一种免锁机制
- 使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新
- 因此每次使用该域就要重新计算,而不是使用寄存器中的值
注意:
- 如果你不是专家级的程序员,不要依赖于原子性来编写无锁的代码。
- 依赖于原子性可能是不安全的,因此避免使用原子性代替同步。
- 使用volatile而不是synchronized的唯一安全情况是类中只有一个可变的域,而第一选择和最安全的方式是采用synchronized关键字。
使用显式的lock对象
Java SE5的java.uitl.concurrent类库了有定义在java.util.concurrent.locks中的显式的互斥机制。lock对象必须被显式地创建,锁定和释放。因此,它与内建的锁形式相比,代码缺乏优雅性。但在解决某些类型的问题时更加灵活。
使用方法如下:
Lock lock = new Reentrantlock();
lock.lock();
try{
//同步的代码段
}finally{
//释放锁
lock.unlock();
}
使用try catch是为了当某些异常抛出了,你可以使用finally子句以维护在正常的状态。
使用synchronized关键字时,需要的代码量更少,并且用户错误出现的可能性更低。因此通常只有在解决特殊问题时,才会使用显式lock的这种方式。例如获取锁一段时间。
sleep()方法与wait()方法
从所属的类来看,
sleep()
方法属于Thread中的,wait()
方法在Object中。从资源调度上看,
wait()
方法会释放对象锁(等待CPU),sleep()
方法不会(占着CPU睡觉)。使用范围:
wait()
,notify()
和notifyAll()
方法只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用。
//在同步代码块中调用wait()方法
synchronized(x){
x.notify();
//或者wait()
}
参考:
[1].Thread Synchrinization