熟悉 Java 的多线程的一般都知道会有数据不一致的情况发生,比如两个线程在操作同一个类变量时,而保护数据不至于错乱的办法就是让方法同步或者代码块同步。同步时非原子操作就得同步,比如一个简单的 1.2+1 运算也该同步,以保证一个代码块或方法成为一个原子操作。
简单点说就是给在多线程环境中可能会造成数据破坏的方法,做法有两种,以及一些疑问:1. 不论是静态的或非静态的方法都加上 synchronized 关键字,那静态的方法和非静态的方法前加上 synchronized 关键字有区别吗?2. 或者在可疑的代码块两旁用 synchronized(this) 或 synchronized(someObject) 包裹起来,而选用 this 还是某一个对象--someObject,又有什么不同呢?3. 对方法加了 synchronized 关键字或用 synchronized(xxx) 包裹了代码,就一定能避免多线程环境下的数据破坏吗?4. 对方法加 synchronized 关键字与用 synchronized(xxx) 同步代码块两种规避方法又有什么分别和联系呢?为了理解上面的问题,我们还得从 Java 对线程同步的原理上说起。我们知道 Java 直接在语言级上支持多线程的。在多线程环境中我们要小心的数据是:1) 保存在堆中的实例变量2) 保存在方法区中的类变量。现实点说呢就是某个方法会触及到的同一个变量,如类变量或单态实例的实例变量。避免冲突的最容易想到的办法就是同一时刻只让一个线程去执行某段代码块或方法,于是我们就要给一段代码块或整个方法体标记出来,被保护的代码块或方法体在 Java 里叫做监视区域(Monitor Region),类似的东西在 C++ 中叫做临界区(Critical Section)。比如说一段代码:01.
public
void
operate() {
02.
flag ++;
03.
try
{
04.
//休眠一个随机时间,让不同线程能在此交替执行
05.
Thread.sleep(
new
Random().nextInt(
10
));
06.
}
catch
(InterruptedException e) {
07.
e.printStackTrace();
08.
}
09.
flag --;
10.
System.out.println(
"Current flag: "
+ flag);
11.
}
01.
public
void
operate() {
02.
synchronized
(
this
){
//只需要把可能造成麻烦的代码标记起来
03.
flag ++;
04.
try
{
05.
//休眠一个随机时间,让不同线程能在此交替执行
06.
Thread.sleep(
new
Random().nextInt(
5
));
07.
}
catch
(InterruptedException e) {
08.
e.printStackTrace();
09.
}
10.
flag --;
11.
12.
System.out.println(
"Current flag: "
+ flag);
13.
}
14.
15.
//some code out of the monitor region
16.
System.out.println(
"线程安全的代码放外面就行啦"
);
17.
18.
}
01.
public
synchronized
void
operate() {
02.
flag ++;
03.
try
{
04.
//休眠一个随机时间,让不同线程能在此交替执行
05.
Thread.sleep(
new
Random().nextInt(
10
));
06.
}
catch
(InterruptedException e) {
07.
e.printStackTrace();
08.
}
09.
flag --;
10.
System.out.println(
"Current flag: "
+ flag);
11.
}
01.
package
com.unmi;
02.
03.
import
java.util.Random;
04.
05.
/**
06.
* 多线程测试程序
07.
*
08.
* @author Unmi
09.
*/
10.
public
class
TestMultiThread {
11.
12.
// 一个标志值
13.
private
static
int
flag =
1
;
14.
15.
/**
16.
* @param args
17.
*/
18.
public
static
void
main(String[] args) {
19.
new
Thread(
"Thread-01"
) {
20.
public
void
run() {
21.
new
TestMultiThread().operate();
22.
}
23.
}.start();
// 启动第一个线程
24.
25.
new
Thread(
"Thread-02"
) {
26.
public
void
run() {
27.
new
TestMultiThread().operate();
28.
}
29.
}.start();
// 启动第二个线程
30.
}
31.
32.
/**
33.
* 对 flag 进行一个自增,然后自减的操作,正常情况下 flag 还应是 1
34.
*/
35.
public
void
operate() {
36.
flag++;
37.
try
{
38.
// 增加随机性,让不同线程能在此交替执行
39.
Thread.sleep(
new
Random().nextInt(
5
));
40.
}
catch
(InterruptedException e) {
41.
e.printStackTrace();
42.
}
43.
flag--;
44.
45.
System.out.println(
"Thread: "
+ Thread.currentThread().getName()
46.
+
" /Current flag: "
+ flag);
47.
}
48.
}
01.
public
void
operate() {
02.
synchronized
(
this
){
//只需要把可能制造麻烦的代码标记起来
03.
flag ++;
04.
try
{
05.
//增加随机性,让不同线程能在此交替执行
06.
Thread.sleep(
new
Random().nextInt(
5
));
07.
}
catch
(InterruptedException e) {
08.
e.printStackTrace();
09.
}
10.
flag --;
11.
12.
System.out.println(
"Thread: "
+ Thread.currentThread().getName() +
13.
" /Current flag: "
+ flag);
14.
}
15.
16.
//some code out of the monitor region
17.
System.out.print(
""
);
18.
}
01.
public
synchronized
void
operate() {
02.
flag++;
03.
try
{
04.
// 增加随机性,让不同线程能在此交替执行
05.
Thread.sleep(
new
Random().nextInt(
5
));
06.
}
catch
(InterruptedException e) {
07.
e.printStackTrace();
08.
}
09.
flag--;
10.
System.out.println(
"Thread: "
+ Thread.currentThread().getName()
11.
+
" /Current flag: "
+ flag);
12.
}
立此题之前,本只想就 synchronized() 中的对象来个充分理解而已,无奈,事物间总是有千丝万缕,于是牵扯出这许多事。以后有空或有必要还是该拆出多个专题不垒这样的长篇大落,至少分出以下几出:
1. 不同线程执行同步方法或同步块的互斥规则2. 同步时,监视区域是与哪一个对象相关联的2. 如何理解同步块 synchronized(xxx) 中的对象参数3. 同步块与同步方法的字节码分析..... 或者还可以拟个 写同步方法时容易碰到的几个陷阱 等等参考:1. 2. (by Bill Venners) 3.装载自http://www.blogjava.net/Unmi/archive/2010/03/23/316189.html