本文轉載自
IT江湖,原文鏈接:Click me
如果println函數的參數爲常量則不會出現線程併發問題,但是如果參數爲表達式形式,則JVM在執行println函數的時候會分爲幾步來執行,從而造成併發問題。
如下例子所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
public
class Test
{
public
static void
main(String[]
args)
{
ExecutorService
pool =
Executors.newFixedThreadPool(2);
Runnable t1
= new
MyRunnable("張三",
2000);
Runnable
t2 =
new MyRunnable("李四",
3600);
Runnable t3
= new
MyRunnable("王五",
2700);
Runnable
t4 =
new MyRunnable("老張",
600);
Runnable t5
= new
MyRunnable("老牛",
1300);
Runnable
t6 =
new MyRunnable("老朱",
800);
//執行各個線程
pool.execute(t1);
pool.execute(t2);
pool.execute(t3);
pool.execute(t4);
pool.execute(t5);
pool.execute(t6);
//關閉線程池
pool.shutdown();
}
}
class MyRunnable
implements Runnable
{
private
static AtomicLong
aLong =
new AtomicLong(10000);
//原子量,每個線程都可以自由操作
private
String name;
//操作人
private
int data;
//操作數
MyRunnable(String
name,
int data)
{
this.name
= name;
this.data
= data;
}
public
void run()
{
System.out.println(name
+ "執行了"
+ data
+ ",當前餘額:"
+ aLong.addAndGet(data));
}
}
|
執行的結果爲:
張三執行了2000,當前餘額:12000
李四執行了3600,當前餘額:15600
王五執行了2700,當前餘額:18300
老牛執行了1300,當前餘額:20200
老朱執行了800,當前餘額:21000
老張執行了600,當前餘額:18900
通過反編譯,上面println函數的主要處理過程分爲下面三步:
|
long
l =
aLong.addAndGet(data);
String sb
=new
StringBuilder(String.valueOf(name))).append("執行了").append(data).append(",當前餘額:").append(l);
synchronized
(this)
{
print(sb.toString);
newLine();
}
|
所以可以看出上面程序的執行順序爲:、
張三執行完->李四執行完->王五執行完->老張只執行到加操作,被老牛打斷->老牛執行完->老朱執行完->老張執行剩下的輸出操作
要想有一個正常的輸出順序,應該修改代碼爲:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
import
java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import
java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import
java.util.concurrent.locks.ReentrantLock;
public
class Test
{
public
static void
main(String[]
args)
{
ExecutorService
pool =
Executors.newFixedThreadPool(2);
Lock lock
= new
ReentrantLock(false);
Runnable
t1 =
new MyRunnable("張三",
2000,
lock);
Runnable t2
= new
MyRunnable("李四",
3600,
lock);
Runnable
t3 =
new MyRunnable("王五",
2700,
lock);
Runnable t4
= new
MyRunnable("老張",
600,
lock);
Runnable
t5 =
new MyRunnable("老牛",
1300,
lock);
Runnable t6
= new
MyRunnable("老朱",
800,
lock);
//執行各個線程
pool.execute(t1);
pool.execute(t2);
pool.execute(t3);
pool.execute(t4);
pool.execute(t5);
pool.execute(t6);
//關閉線程池
pool.shutdown();
}
}
class
MyRunnable implements
Runnable
{
private
static AtomicLong
aLong =
new AtomicLong(10000);
//原子量,每個線程都可以自由操作
private
String name;
//操作人
private
int data;
//操作數
private
Lock lock;
MyRunnable(String
name,
int data,
Lock lock)
{
this.name
= name;
this.data
= data;
this.lock
= lock;
}
public
void run()
{
lock.lock();
System.out.println(name
+ "執行了"
+ data
+ ",當前餘額:"
+ aLong.addAndGet(data));
lock.unlock();
}
}
|
結果爲:
張三執行了2000,當前餘額:12000
王五執行了2700,當前餘額:14700
老張執行了600,當前餘額:15300
老牛執行了1300,當前餘額:16600
老朱執行了800,當前餘額:17400
李四執行了3600,當前餘額:21000
【注意】這裏使用了一個對象鎖,來控制對併發代碼的訪問。不管運行多少次,執行次序如何,最終餘額均爲21000,這個結果是正確的。有關原子量的用法很簡單,關鍵是對原子量的認識,原子僅僅是保證變量操作的原子性,但整個程序還需要考慮線程安全的。