synchronized 锁重入
关键字 synchronized
拥有锁重入的功能,也就是在使用synchronized
时,当一个线程得到一个对象锁后,再次请求此对象时可以再次得到该对象的锁。实例如下:
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) {
SyncDubbo1 sd = new SyncDubbo1();
new Thread(new Runnable() {
@Override
public void run() {
sd.method1();
}
}).start();
}
}
出现异常,锁自动释放
对于web应用程序,异常释放锁的情况,如果不及时处理,很可能对你的应用程序业务逻辑产生严重的错误。
比如你现在执行一个队列任务,很多对象都去在等待第一个对象正确执行完毕再去释放锁,但是第一个对象由于异常的出现,导致业务逻辑没有正常执行完毕,就释放了锁。那么可想而知后续的对象执行的都是错误的逻辑。所有这一点一定要引起注意。
示例:
public class SyncException {
private int i = 0;
public synchronized void option() {
while (true) {
try {
i++;
Thread.sleep(200);
System.out.println(Thread.currentThread().getName() + ",i=" + i);
if (i == 10) {
Integer.parseInt("a");
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("log info = " + i);
throw new RuntimeException(); // 使线程停止
// continue;
}
}
}
public static void main(String[] args) {
final SyncException se = new SyncException();
new Thread(new Runnable() {
@Override
public void run() {
se.option();
}
}, "t1").start();
}
}
这里有两种处理方式:
1. catch的时候把有问题的数据记录日志,然后continue。
2. 如果这些任务有关联关系,使用throw new RuntimeException
,让线程停止。
synchronized 代码块
使用 synchronized 声明的方法在某些情况下是有弊端的,比如A线程调用同步的方法执行一个很长时间的任务,那么B线程就必须等待比较长的时间才能执行,这样的情况下可以使用synchronized代码块去优化代码的执行时间,也就是通常所说的减小锁的粒度。
synchronized 可以使用任意的 Object 进行加锁,用法比较灵活。另外注意一个问题:不要使用 String 的常量加锁,会出现死循环问题。new String()
这种方式可以。
锁对象的改变问题:
- 当使用一个对象进行加锁的时候,要注意对象本身发生改变的时候,那么持有的锁就不同。如果对象本身不发生改变,那么依然是同步的,即使对象的属性发生了改变。
volatile 关键字
volatile 概念
volatile 概念:volatile 的作用是使变量在多个线程间可见。
在 Java 中,每一个线程都会有一块工作内存区,其中存放着所有线程共享的主内存中的变量值的拷贝。在线程执行时,他在自己的工作内存区中操作这些变量。为了存取一个共享的变量,一个线程通常先获取锁并去清除它的内存工作区,把这些共享变量从所有线程的共享内存中正确的装入到他自己所在的工作内存中,当线程解锁时保证该工作内存区中变量的值写回到共享内存中。
一个线程可以执行的操作有使用(use)、赋值(assign)、装载(load)、存储(store)、锁定(lock)、解锁(unlock)。
而主内存可以执行的操作有:读(read)、写(write)、锁定(lock)、解锁(unlock),每个操作都是原子的。
volatile
的作用就是强制线程到主内存(共享内存)里去读取变量,而不去线程工作内存区里去读取,从而实现了多个线程间的变量可见,也就满足了线程安全的可见性。
示例:
public class RunThread extends Thread {
private volatile boolean isRunning = true;
public void setRunning(boolean isRunning) {
this.isRunning = isRunning;
}
public void run() {
while (isRunning == true) {
}
System.out.println("线程停止。。。");
}
public static void main(String[] args) throws InterruptedException {
RunThread rt = new RunThread();
rt.start();
Thread.sleep(3000);
rt.setRunning(false);
System.out.println("isRunning 被设置成了 false。。。");
Thread.sleep(1000);
System.out.println(rt.isRunning);
}
}
volatile 非原子性
volatile 虽然拥有多个线程之间的可见性,但是却不具备同步性(原子性),可以算得上是一个轻量级的synchronized,性能要比synchronized强很多,不会造成堵塞(很多开源的框架里,比如netty的底层代码就是大量使用volatile,可见netty性能一定不错)。这里要注意,一般volatile用于只针对多个线程可见的变量操作,并不能替代synchronized的同步功能。
volatile 关键字不具备 synchronized 的原子性(同步),要实现原子性建议使用atomic
类的系列对象,支持原子性操作(注意atomic类只保证本身方法的原子性,并不保证多次操作的原子性)
import java.util.concurrent.atomic.AtomicInteger;
public class VolatileNoAtomic extends Thread {
// private static volatile int count;
private static AtomicInteger count = new AtomicInteger();
private static void addCount() {
for (int i = 0; i < 1000; i++) {
// count++;
count.incrementAndGet();
}
System.out.println(count);
}
public void run() {
addCount();
}
public static void main(String[] args) {
VolatileNoAtomic[] arr = new VolatileNoAtomic[10];
for (int i = 0; i < 10; i++) {
arr[i] = new VolatileNoAtomic();
}
for (int i = 0; i < 10; i++) {
arr[i].start();
}
}
}