现在的位置: 首页 > 综合 > 正文

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

2017年08月09日 ⁄ 综合 ⁄ 共 2641字 ⁄ 字号 评论关闭

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

如下例子所示:

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

所以可以看出上面程序的执行顺序为:、
张三执行完->李四执行完->王五执行完->老张只执行到加操作,被老牛打断->老牛执行完->老朱执行完->老张执行剩下的输出操作
要想有一个正常的输出顺序,应该修改代码为:

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,这个结果是正确的。有关原子量的用法很简单,关键是对原子量的认识,原子仅仅是保证变量操作的原子性,但整个程序还需要考虑线程安全的。

转载自:http://www.strutshome.com/index.php/archives/608

抱歉!评论已关闭.