您的位置:首页 > 编程语言 > Java开发

关于System.out.println的多线程并发问题

2014-11-07 10:43 573 查看
本文转载自
IT江湖,原文链接:Click me

如果println函数的参数为常量则不会出现线程并发问题,但是如果参数为表达式形式,则JVM在执行println函数的时候会分为几步来执行,从而造成并发问题。

如下例子所示:

Java

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));
}
}

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函数的主要处理过程分为下面三步:

Java

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

long
l =
aLong.addAndGet(data);

String sb
=new
StringBuilder(String.valueOf(name))).append("执行了").append(data).append(",当前余额:").append(l);
synchronized
(this)

{
print(sb.toString);

newLine();
}

所以可以看出上面程序的执行顺序为:、

张三执行完->李四执行完->王五执行完->老张只执行到加操作,被老牛打断->老牛执行完->老朱执行完->老张执行剩下的输出操作

要想有一个正常的输出顺序,应该修改代码为:

Java

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();
}
}

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,这个结果是正确的。有关原子量的用法很简单,关键是对原子量的认识,原子仅仅是保证变量操作的原子性,但整个程序还需要考虑线程安全的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息