synchronized细节问题(一)

synchronized锁重入:

  关键字synchronized拥有锁重入的功能,也就是在使用synchronized时,当一个线程得到一个对象的锁后,再次请求此对象时是可以再次得到该对象的锁。

下面看一段锁重入的小demo:

public class SyncDubbo1 {
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) {
final SyncDubbo1 sd = new SyncDubbo1();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
sd.method1();
}
});
t1.start();
}
}

这中方式的调用是没有任何问题的,运行的结果是

method1..
method2..
method3..

这是一个最简单的锁重入的问题,是完全没有任何问题的。

下面看一个稍微复杂点的锁重入的代码:

public class SyncDubbo2 {
static class Main {
public int i = 10;

private synchronized void operationSup() {
try {
i--;
System.out.println("Main print i = " + i);
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

static class Sub extends Main {
public synchronized void operationSub() {
try {
while (i > 0) {
i--;
System.out.println("Sub print i = " + i);
Thread.sleep(100);
this.operationSub();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
Sub sub = new Sub();
sub.operationSub();
}
});
}
}

运行结果是:

Sub print i = 9
Main print i = 8
Sub print i = 7
Main print i = 6
Sub print i = 5
Main print i = 4
Sub print i = 3
Main print i = 2
Sub print i = 1
Main print i = 0

这个结果是没有任何问题的,通过这个例子是想说明,再有父子继承关系的时候,然后都加synchronized修饰时,这样去调用也是线程安全的。这是对于synchronized应用的第二种形式。

下面在看一个synchronized中修饰的方法里面,碰到某些异常时,出现的情况,下面也看一个demo;

public class SyncException {
private int i = 0;

public synchronized void operation() {
while (true) {
try {
i++;
Thread.sleep(200);
System.out.println(Thread.currentThread().getName() + " ,i = " + i);
if (i == 5) {
Integer.parseInt("a");
}
} catch (Exception e) {
e.printStackTrace();
System.out.println(" log info i = " + i);
}
}
}

public static void main(String[] args) {
final SyncException se = new SyncException();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
se.operation();
}
},"t1");
t1.start();
}
}

这段代码在i=5的时候代码发生异常,这种情况下,synchronized会自动解锁,程序会继续执行,发生异常的情况下,我们一定要对异常情况进行处理,运行的结果是:

t1 ,i = 1
t1 ,i = 2
t1 ,i = 3
t1 ,i = 4
t1 ,i = 5
log info i = 5
java.lang.NumberFormatException: For input string: "a"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Integer.parseInt(Integer.java:492)
at java.lang.Integer.parseInt(Integer.java:527)
at Thread.SyncException.operation(SyncException.java:16)
at Thread.SyncException$1.run(SyncException.java:31)
at java.lang.Thread.run(Thread.java:744)
t1 ,i = 6
t1 ,i = 7
t1 ,i = 8

在实际开发中,对 这种异常有两种方式的处理,第一种就是上面写的这种,catch到异常后,把对应的数据记录到日志,当然有个前提条件,就是每次去执行这个任务之间都是没有关联的;还有一种情况,执行的任务是有关联的,是一个整体,假如采用前面那种情况,在异常发生后的几次情况的执行都是有问题的,这也有一个解决方法,就是补或一个打断异常,InterruptedException,或者抛出一个运行时异常,就能停止这个线程。

总结:对于web应用程序,异常释放锁的情况,如果不及时处理,很可能对你的应用程序业务逻辑产生严重的错误,比如你现在执行一个队列任务,很多对象都去在等待第一个对象正确执行完毕再去释放锁,但是第一个对象由于异常的出现,导致业务逻辑没有正常执行完毕,就释放了锁,那么可想而知后续的对象执行的都是错误的逻辑。所以这一点一定要引起注意,在编写代码的时候,一定要考虑周全。

原文地址:https://www.cnblogs.com/shmilyToHu/p/6386112.html