一、Synchronized锁重入
1、Synchronized关键字拥有所锁重入功能,也就是在使用Synchronized的时候,当一个线程获得一个对象的锁之后,在该锁里执行代码时再次请求该对象的锁,可以再次获得该对象的锁。也就是说当线程请求一个由其他线程持有的锁时,该线程会阻塞,而线程请求由自己持有的锁时,如果该锁是重入锁,请求就会成功,否则会阻塞。
2、一个简单的例子就是:在一个Synchronized修饰的方法或代码块的内部调用本类的其他Synchronized修饰的方法或代码块时,是永远可以得到锁的。实例代码A如下:
public class SyncDubbo{
public synchronized void method1(){
System.out.println("method1----------");
method2();
}
public synchronized void method2(){
System.out.println("method2----------")
method3();
}
public synchronized void method3(){
System.out.println("method3----------");
}
public static void main(String[] args){
SyncDubbo syncDubbo = new SyncDubbo();
new Thread(new Runnable(){
public void run(){
syncDubbo.method1();
}
}).start();
}
}
以上代码演示了如何在一个已经被synchronized关键字修饰过的方法再去调用对象中其他被synchronized修饰的方法。
那么为什么要引入重入锁这种机制呢? 一个对象一把锁,多个对象多把锁,重入锁的概念就是:自己可以获取自己的内部锁。
假如有1个对象T获得了对象A的锁,那么该线程T如果在未释放前再次请求该对象的锁时,如果没有可重入锁的机制,是不会获取到锁的,这样的话就会出现死锁的情况。
就如代码A体现的那样,线程T在执行到method1()内部的时候,由于线程已经获取了该对象的syncDubbo的对象锁,当执行到method2()的时候,会再次请求该对象的对象锁,如果内有可重入锁的机制的话,由于该线程T还未释放在刚进入method1()时获取的对象锁,当执行到method2()的时候就会出现死锁。
可重入锁到底有什么用?正如上述代码A和上一段中解释的那样,重入锁最大的作用就是避免死锁。假如有一个场景:用户名和密码保存在本地的txt文件中,则登录验证方法和更新密码的方法都应该被加载synchronized,那么当更新密码的时候需要验证密码的合法性,所以需要调用验证方法,此时是可以调用的。
重入锁的实现原理可以参考下面的网址:http://www.cnbolgs.com/pureEve/p/6421273.html进行学习。
可重入锁的其他特性:父子可继承性
可重入锁支持在父子类继承的环境中,示例代码如下:
public class syncDubbo{
static class Main{
public int i= 5;
public synchronized void operationSub(){
i--;
System.out.println("Main print i = " + i);
try{
Thread.sleep(5000);
}catch (InterruptedException e){
e.printStaticTrace();
}
}
}
static class Sup extends Main{
public synchronized void operationSub(){
while(i > 0){
i--;
System.out.println("sub print i = " + i);
try{
Thread.sleep(5000);
}catch (InterruptedException e){
e.printStaticTrace();
}
}
}
public static void main(String[] args){
new Thread(new Runnable(){
public void run(){
Sub sub = new Sub();
sub.operationSub();
}
}).start();
}
}
二、Synchronized的其他特性
1、出现异常时,锁自动释放
就是说,当一个线程执行的代码出现异常时,其所持有的锁会自动释放,示例如下:
public class SyncException {
private int i = 0;
public synchronized void operation() {
while(true) {
i++;
System.out.println(Thread.currentThread().getName() + " ,i= " + i );
if(i == 10) {
Integer.parseInt("a");
}
}
}
public static void main(String[] args) {
SyncException se = new SyncException();
new Thread(new Runnable() {
public void run() {
se.operation();
}
},"t1").start();
}
}
执行后可看出当代码报错的时候,程序不会再执行,即释放了锁。
2、将任意对象作为监视器
public class StringLock {
private String lock = "lock";
public void method() {
synchronized(lock) {
try {
System.out.println("当前线程: " + Thread.currentThread().getName() + "开始");
Thread.sleep(1000);
System.out.println("当前线程: " + Thread.currentThread().getName() + "结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
StringLock sl = new StringLock();
new Thread(new Runnable() {
public void run() {
sl.method();
}
},"t1").start();
new Thread(new Runnable() {
public void run() {
sl.method();
}
},"t2").start();
}
}
3、单例模式
单例中有一个经典的实现就是:双重锁校验,其中就是用到了synchronized,实现代码如下:
public class DubbleSingleton {
private static DubbleSingleton instance;
public static DubbleSingleton getInstance() {
if(instance == null) {
try {
// 模块初始化对象的准备时间
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//加上锁,表示当前对象不可以在其他线程的时候创建
synchronized(DubbleSingleton.class) {
//如果不加这一曾判断的话,这样的话每一个线程会得到一个实例
//而不是所有的线程得到的是一个实例
if(instance == null) {
instance = new DubbleSingleton();
}
}
}
return instance;
}
}
1、双重锁校验需要将对象声明为violate,不然会因为指令重排序导致第一个判断空时将为初始化的对象返回。
以上内容转载自-----------微信公众号:Java后端技术