关于内部锁
synchronized关键字在多线程中,是一种“锁”的机制,它是一种
内部锁。
内部锁是
可重进入的,所谓可重进入,就是指请求是
基于每线程,而不是每调用。
synchronized锁是加在对象上的,但是
如果同一线程,多次请求这个锁,是可以的,如:
public class ReentrancySynTest {
public synchronized void a() {
System.out.println("this is a");
}
public synchronized void b() {
a();
}
public static void main(String[] args) {
ReentrancySynTest reentrancySynTest = new ReentrancySynTest();
reentrancySynTest.b(); //同一线程,可以重进入这个锁 , 所以在b()中可以再进入a()执行
}
}
重进入实现机制:通过为每个锁关联一个请求计数以及占有它的线程。当计数为0时,认为锁是未被占有的。线程请求一个未被占用的锁时,JVM将记录锁的占有者,并且将请求计数置为1.如果同一线程再次请求这个锁,计数将递增;每次占用线程退出同步块时,计数值减1.当计数值为0时,锁被释放。
过期数据:
当多个线程同时对某个变量进行读、写时,如果没有设置线程同步,很可能某线程读到的不是此变量的最新值,而是过期数据:如果一个线程调用了set,而另一个线程此时正在调用get,那么最后get得到的数据有可能是过期数据。而不是set设置的最新值。
解决方法:让一个线程调用set(get)时,其他线程不能同时调用get(set)。这可以使用synchronized关键字实现。
以下程序就不会读取过期数据,输出结果为: Thread 2 : 1
class SyncInteger {
private int value = 0;
public synchronized int get() {
return this.value;
}
public synchronized void set(int value) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.value = value;
}
}
public class ExpireDataTest {
public static void main(String[] args) {
final SyncInteger syncInteger = new SyncInteger();
Thread thread1 = new Thread() {
public void run() {
// 当Thread1执行对象的set方法后,它便获取了对象的锁,在它执行的过程中,Thread2不能执行同一对象的get方法
syncInteger.set(1);
}
};
Thread thread2 = new Thread() {
public void run() {
try {
Thread.sleep(100); // 目的让Thread1先执行set方法
} catch (InterruptedException e) {
e.printStackTrace();
}
// 由于之前的sleep使得Thread1先获得对象的锁并执行其set方法,那Thread2只有在set方法执行退出后,才能执行get
System.out.println("Thread 2 : " + syncInteger.get());
}
};
thread1.start();
thread2.start();
}
}
如果将get,set方法前面的synchronized去掉,则结果输出:Thread 2 : 0,get返回的事更新前的值,即过期数据。
非原子的64位操作
Java存储模型要求获取和存储操作都是原子的,但对于double和long类型的变量,
JVM允许见64位的读或写操作划分为两个32位操作。如果读和写发生在不同的线程,可能会出现得到一个值的高32位和另一个值的低32位。
如:开始读之前: a b
Thread1更改为 :a1 b1 Thread2更改为:a2 b2
Thread1与Thread2同时写入, Thread1写入高位a1在Thread2写入高位a2之前(a2覆盖a1),Thread2写入低位在Thread1写入低位之前(b1覆盖b2),最终结果得到: a2 b1
解决方法:可以讲它们声明为volatile类型
线程封闭
访问共享数据需要进行同步,那么如果
让数据不能共享, 就不需要进行同步了,这是实现线程安全的最简单 一种方式。
线程封闭ThreadLocal:
ThreadLocal可以
将一个“域”与一个“线程”关联起来,通过持有类型的方式,被ThreadLocal持有的类型就是域的实际类型
ThreadLocal提供了get与set访问器,它实际上
为每个使用它的线程维护了一份单独的拷贝,所以不同线程的数据不共享,某个线程的get方法得到的总是set方法设置 值。
使用ThreadLocal进行线程封闭的代码,线程安全:
使用ThreadLocal进行线程封闭的代码,线程安全:
//使用ThreadLocal进行线程封闭的
private static ThreadLocal<Connection> connectionHolder
= new ThreadLocal<Connection>() {
public Connection initialValue() {
return DriverManager.getConnection(DB_URL);
}
};
public static Connection getConnection() {
return connectionHolder.get();
}
同样的功能,不使用ThreadLocal进行线程封闭的代码,非线程安全:
//不使用ThreadLocal进行线程封闭的
private static Connection connection = DriverManager.getConnection(DB_URL);
public static Connection getConnection() {
return connection;
}