[color=green][size=medium][b]Java之线程同步与安全(Thread Synchronize & Safe)[/b][/size][/color]
[size=medium][b]一、问题背景[/b][/size]
Java语言提供了多线程的功能。
多线程创建于相同的Object,多线程间共享Object的变量或属性。
但是,当线程对共享的数据进行读写时,会导致数据的不一致(data inconsistency)。
[size=medium][b]二、线程同步情景分析[/b][/size]
数据不一致的原因是由数据操作的非原子性引起的。
即:更新任何属性或变量,非一步完成,而是需要三部:
1、读取现在的值。
2、进行必要的操作以得到要更新的值。
3、把更新的值写入到引用的变量或属性中。
来看一个简单的例子:
多个线程共享一个数据,并对其进行修改。
上述循环中,使用两个线程对 count 变量的值进行增加,每个线程各增加四次。
最后 count 的值应该是 8。但是在实际的多次运行中,它的值在 6,7,8 之间。
发生这种情况,即使 count++ 看起来是单原子操作,实际不是。
从而导致数据不一致。
[size=medium][b]三、Java中保证线程同步的方法[/b][/size]
为了在多线程运行环境中确保数据的一致,Java提供了一些方法。
下面是主要的几种:
1、使用 synchronized 关键字(最广泛使用)
2、使用 java.util.concurrent.atomic 包下的原子操作包装类。
例如: AtomicInteger
3、使用 java.util.concurrent.locks 包下的锁类
4、使用 线程安全的集合类。
例如: ConcurrentHashMap
5、使用 volatile 关键字确保读的一致性。(不能确保写一致)
确保每个线程是从内存中读取值,而不是线程缓存中。
[b]三·一 使用 synchronized 关键字[/b]
JVM 可以确保被 synchronized 修饰的代码块每次只能被一个线程访问执行。
内部通过锁住一个对象或类来实现。
1、synchronized 可以在被锁定的资源或未被锁定的资源情况下工作。
但是,在任何线程执行 synchronized 代码块之前,
它需要首先获取这个对象的锁才可以执行。
而线程执行非 synchronized 代码块不需要获取对象的锁。
当然,线程在代码块执行完毕后,需要释放该锁。
这样其它处于等待状态的线程可以获取对象的锁以执行。
2、synchronized 的使用方式有两种:
1)用在类方法的声明上。使整个方法成为 synchronized 方法。
此时会锁定 Object (实例),如果是静态方法则锁类。
为了提高运行性能,请尽量少使用该策略。
2)单独使用。形成一个 synchronized 代码块。
只锁定方法中需要锁定的代码块。
此时,需要提供资源,以从该资源中取得锁。
资源可以是 ABC.class 或 类的一个属性。
synchronized(this) 会锁定当前整个实例,
即同时获取了当前实例的锁 + 实例所有属性的锁。
注意:为了提高执行性能,被锁的对象应该是最小范围的。
例如在一个类中有多个 synchronized 的代码块,其中
一块锁定了整个实例。则其它代码块则不能被其它线程执行。
3、synchronized 是以降低性能为代价的(本来可以并行执行,先只能串行执行)。
所以非必需,不要用。
4、synchronized 只在同一个 JVM 中是有效的。
5、synchronized 会造成[死锁]现象。
synchronized 代码块同时锁定多个对象。多个线程互相等待其它线程释放对象的锁。
6、synchronized 不能用于 构造方法 和 局部变量。
7、synchronized 锁定的实例,应该避免是 常量池 中的对象。
这些常量池中的对象可能被其它 synchronized 代码块引用。例如:String pool
8、synchronized 通常锁定一个虚设的 private 的类属性,对代码块进行锁定。
因为 private 的引用指向的实例始终是一个。不会变。
下面的改进使 count 属性线程安全的代码:
让我们看一些 synchronized 的例子,看看我们能学到什么:
例子一:
如果能够拿到 MyObject 的实例,并且在锁定该实例无限长的时间。
其它代码则无法执行该实例的方法。尤其在单例模式中。
例子二:
更有甚者:
输出结果:
Lock class instance!
M: Hello, Word
分析:
C: Hello, Word
这一句没有输出,因为 MyObject.class 对象被其它 Lock 锁定
根据结果可以看出:
要锁定的对象可以是类(class),也可以是类的实例(instance)。
但是类和类的实例在锁的问题上不相关,也没有权限上你大我小的隶属关系。
[url=http://stackoverflow.com/a/2056256/2893073][b]锁对象和锁类的区别?[/b][/url]
[b]synchronized(X.class)[/b]
使用 class类 所为锁定对象, 那是因为只有一个 class 类在 JVM 中被 classLoader 加载。
如果一个线程要执行该代码块,必须拥有 class 类的锁。
此时只能有一个线程在执行该代码块。
[b]synchronized(this)[/b]
使用类的实例为锁定对象。
如果一个线程要执行该代码块,只需拥有实例的锁即可。
此时可以有多个线程在执行该代码块。
例子三:
注意:
lock 属性是 public 的。通过替换它指向引用的对象,多个线程就可以执行 synchronized 块的代码。
类似情况:private 的属性,但是有 public 的 set() 方法。
例子四:
注意:没有 static 关键字,则不等价。
例子五:
输出:
Time taken= 1501
[1:t1, 2:t2, 3:t2:t3, 4:t1, 5:t1:t2, 6:t3:t2]
加上同步锁:
输出:
Time taken= 1502
[1:t1:t2:t3, 2:t2:t1:t3, 3:t1:t2:t3, 4:t1:t2:t3, 5:t2:t1:t3, 6:t1:t2:t3]
继续阅读:
[url=http://lixh1986.iteye.com/blog/2351294]使用 java.util.concurrent.lock.Lock 实现线程同步[/url]
-
转载请注明,
原文出处:http://lixh1986.iteye.com/blog/2351243
-
引用:
http://www.journaldev.com/1061/thread-safety-in-java
[size=medium][b]一、问题背景[/b][/size]
Java语言提供了多线程的功能。
多线程创建于相同的Object,多线程间共享Object的变量或属性。
但是,当线程对共享的数据进行读写时,会导致数据的不一致(data inconsistency)。
[size=medium][b]二、线程同步情景分析[/b][/size]
数据不一致的原因是由数据操作的非原子性引起的。
即:更新任何属性或变量,非一步完成,而是需要三部:
1、读取现在的值。
2、进行必要的操作以得到要更新的值。
3、把更新的值写入到引用的变量或属性中。
来看一个简单的例子:
多个线程共享一个数据,并对其进行修改。
public class ThreadSafety {
public static void main(String[] args) throws InterruptedException {
ProcessingThread pt = new ProcessingThread();
Thread t1 = new Thread(pt, "t1");
t1.start();
Thread t2 = new Thread(pt, "t2");
t2.start();
//wait for threads to finish processing
t1.join();
t2.join();
System.out.println("Processing count="+pt.getCount());
}
}
class ProcessingThread implements Runnable{
private int count;
@Override
public void run() {
for(int i=1; i < 5; i++){
processSomething(i);
count++;
}
}
public int getCount() {
return this.count;
}
private void processSomething(int i) {
// processing some job
try {
Thread.sleep(i*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
上述循环中,使用两个线程对 count 变量的值进行增加,每个线程各增加四次。
最后 count 的值应该是 8。但是在实际的多次运行中,它的值在 6,7,8 之间。
发生这种情况,即使 count++ 看起来是单原子操作,实际不是。
从而导致数据不一致。
[size=medium][b]三、Java中保证线程同步的方法[/b][/size]
为了在多线程运行环境中确保数据的一致,Java提供了一些方法。
下面是主要的几种:
1、使用 synchronized 关键字(最广泛使用)
2、使用 java.util.concurrent.atomic 包下的原子操作包装类。
例如: AtomicInteger
3、使用 java.util.concurrent.locks 包下的锁类
4、使用 线程安全的集合类。
例如: ConcurrentHashMap
5、使用 volatile 关键字确保读的一致性。(不能确保写一致)
确保每个线程是从内存中读取值,而不是线程缓存中。
[b]三·一 使用 synchronized 关键字[/b]
JVM 可以确保被 synchronized 修饰的代码块每次只能被一个线程访问执行。
内部通过锁住一个对象或类来实现。
1、synchronized 可以在被锁定的资源或未被锁定的资源情况下工作。
但是,在任何线程执行 synchronized 代码块之前,
它需要首先获取这个对象的锁才可以执行。
而线程执行非 synchronized 代码块不需要获取对象的锁。
当然,线程在代码块执行完毕后,需要释放该锁。
这样其它处于等待状态的线程可以获取对象的锁以执行。
2、synchronized 的使用方式有两种:
1)用在类方法的声明上。使整个方法成为 synchronized 方法。
此时会锁定 Object (实例),如果是静态方法则锁类。
为了提高运行性能,请尽量少使用该策略。
2)单独使用。形成一个 synchronized 代码块。
只锁定方法中需要锁定的代码块。
此时,需要提供资源,以从该资源中取得锁。
资源可以是 ABC.class 或 类的一个属性。
synchronized(this) 会锁定当前整个实例,
即同时获取了当前实例的锁 + 实例所有属性的锁。
注意:为了提高执行性能,被锁的对象应该是最小范围的。
例如在一个类中有多个 synchronized 的代码块,其中
一块锁定了整个实例。则其它代码块则不能被其它线程执行。
3、synchronized 是以降低性能为代价的(本来可以并行执行,先只能串行执行)。
所以非必需,不要用。
4、synchronized 只在同一个 JVM 中是有效的。
5、synchronized 会造成[死锁]现象。
synchronized 代码块同时锁定多个对象。多个线程互相等待其它线程释放对象的锁。
6、synchronized 不能用于 构造方法 和 局部变量。
7、synchronized 锁定的实例,应该避免是 常量池 中的对象。
这些常量池中的对象可能被其它 synchronized 代码块引用。例如:String pool
8、synchronized 通常锁定一个虚设的 private 的类属性,对代码块进行锁定。
因为 private 的引用指向的实例始终是一个。不会变。
下面的改进使 count 属性线程安全的代码:
//dummy object variable for synchronization
private Object mutex=new Object();
//using synchronized block to read, increment and update count value synchronously
synchronized (mutex) {
count++;
}
让我们看一些 synchronized 的例子,看看我们能学到什么:
例子一:
public class MyObject {
// Locks on the object's monitor
public synchronized void doSomething() {
// ...
}
}
public class Hack{
public static void main(String[] args){
// Hackers code
MyObject myObject = Factory.getMyObject();
synchronized (myObject) {
while (true) {
// Indefinitely delay myObject
Thread.sleep(Integer.MAX_VALUE);
}
}
}
}
如果能够拿到 MyObject 的实例,并且在锁定该实例无限长的时间。
其它代码则无法执行该实例的方法。尤其在单例模式中。
例子二:
更有甚者:
package com.gentleman.sychronized;
public class Hack {
public static void main(String[] args) throws Exception{
new Thread(new R1()).start();
Thread.sleep(1000);
new Thread(new R2()).start();
new Thread(new R3()).start();
}
}
class MyObject {
public void sayHello(){
synchronized(MyObject.class){
System.out.println("C: Hello, Word");
}
}
public synchronized void sayHello2(){
System.out.println("M: Hello, Word");
}
}
class R1 implements Runnable{
@Override
public void run(){
synchronized (MyObject.class) {
System.out.println("Lock class instance!");
while (true) {
try {
Thread.sleep(Integer.MAX_VALUE);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
class R2 implements Runnable{
@Override
public void run() {
MyObject myObject = new MyObject();
myObject.sayHello();
}
}
class R3 implements Runnable{
@Override
public void run() {
MyObject myObject = new MyObject();
myObject.sayHello2();
}
}
输出结果:
Lock class instance!
M: Hello, Word
分析:
C: Hello, Word
这一句没有输出,因为 MyObject.class 对象被其它 Lock 锁定
根据结果可以看出:
要锁定的对象可以是类(class),也可以是类的实例(instance)。
但是类和类的实例在锁的问题上不相关,也没有权限上你大我小的隶属关系。
[url=http://stackoverflow.com/a/2056256/2893073][b]锁对象和锁类的区别?[/b][/url]
[b]synchronized(X.class)[/b]
使用 class类 所为锁定对象, 那是因为只有一个 class 类在 JVM 中被 classLoader 加载。
如果一个线程要执行该代码块,必须拥有 class 类的锁。
此时只能有一个线程在执行该代码块。
[b]synchronized(this)[/b]
使用类的实例为锁定对象。
如果一个线程要执行该代码块,只需拥有实例的锁即可。
此时可以有多个线程在执行该代码块。
例子三:
public class MyObject {
public Object lock = new Object();
public void doSomething() {
synchronized (lock) {
// ...
}
}
}
//untrusted code
MyObject myObject = new MyObject();
//change the lock Object reference
myObject.lock = new Object();
注意:
lock 属性是 public 的。通过替换它指向引用的对象,多个线程就可以执行 synchronized 块的代码。
类似情况:private 的属性,但是有 public 的 set() 方法。
例子四:
static void myMethod() {
synchronized(MyClass.class) {
//code
}
}
// 等价于:
static synchronized void myMethod() {
//code
}
注意:没有 static 关键字,则不等价。
void myMethod() {
synchronized(this) {
//code
}
}
// 等价于:
synchronized void myMethod() {
//code
}
例子五:
import java.util.Arrays;
public class T {
public static void main(String[] args) throws InterruptedException {
String[] arr = {"1","2","3","4","5","6"};
HashMapProcessor hmp = new HashMapProcessor(arr);
Thread t1=new Thread(hmp, "t1");
Thread t2=new Thread(hmp, "t2");
Thread t3=new Thread(hmp, "t3");
long start = System.currentTimeMillis();
//start all the threads
t1.start();t2.start();t3.start();
//wait for threads to finish
t1.join();t2.join();t3.join();
System.out.println("Time taken= "+(System.currentTimeMillis()-start));
//check the shared variable value now
System.out.println(Arrays.asList(hmp.getMap()));
}
}
class HashMapProcessor implements Runnable{
private String[] strArr = null;
public HashMapProcessor(String[] m){
this.strArr=m;
}
@Override
public void run() {
for(int i=0; i < strArr.length; i++){
processSomething(i);
addThreadName( i, Thread.currentThread().getName());
}
}
private void addThreadName(int i, String name) {
strArr[i] = strArr[i] +":"+name;
}
private void processSomething(int index) {
// processing some job
try {
Thread.sleep(index * 100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public String[] getMap() {
return strArr;
}
}
输出:
Time taken= 1501
[1:t1, 2:t2, 3:t2:t3, 4:t1, 5:t1:t2, 6:t3:t2]
加上同步锁:
private Object lock = new Object();
private void addThreadName2(int i, String name) {
synchronized(lock){
strArr[i] = strArr[i] +":"+name;
}
}
输出:
Time taken= 1502
[1:t1:t2:t3, 2:t2:t1:t3, 3:t1:t2:t3, 4:t1:t2:t3, 5:t2:t1:t3, 6:t1:t2:t3]
继续阅读:
[url=http://lixh1986.iteye.com/blog/2351294]使用 java.util.concurrent.lock.Lock 实现线程同步[/url]
-
转载请注明,
原文出处:http://lixh1986.iteye.com/blog/2351243
-
引用:
http://www.journaldev.com/1061/thread-safety-in-java
本文探讨了Java多线程环境下数据不一致的问题,并详细分析了线程同步的原理及其实现方法,包括使用synchronized关键字、原子操作类、锁类、线程安全集合和volatile关键字等。
1744

被折叠的 条评论
为什么被折叠?



