Java并发编程--从一个线程不安全的例子说起

原创
2012/10/27 22:28
阅读数 5.6K

并发问题再也不是一个只有高级程序员才能接触的问题了,在使用多线程编程的时候,我们更多的将目光放在追求系统的高并发和高吞吐,而这一切的前提是确保程序的正确性。在多线程编程中容易产生的问题有很多,比如线程安全问题、死锁、饥饿等。下面的例子将从线程安全开始,逐步开启并发编程的大门。

@NotThreadSafe
public class UnsafeSequence {
    private int value;

    /** Returns a unique value. */
    public int getNext() {
        return value++;
    }
}

这个例子来源于《Java Concurrency In Practice》的第一章,一个最最简单,却容易引起线程安全问题的片段。书中给出了如下解释:The problem with UnsafeSequence is that with some unlucky timing, two threads could call getNext and receive the same value. Figure 1.1 shows how this can happen. The increment notation, nextValue++, may appear to be a single operation, but is in fact three separate operations: read the value, add one to it, and write out the new value. Since operations in multiple threads may be arbitrarily interleaved by the runtime, it is possible for two threads to read the value at the same time, both see the same value, and then both add one to it. The result is that the same sequence number is returned from multiple calls in different threads.

Figure 1.1的确很明了的告诉我们线程推进的过程,这也给了我们一个寻找线程不安全的方法,通过这样图示法来分析问题。

当然,这里引出了第一个可能会引起线程不安全的因素:程序中有变量的读取、写入或判断操作

/**例如上述变量的自增*/
    public int getNext() {
        return value++;
    }
/**例如单例模式中队变量的判断操作*/
   Public Object getInstance(){
     If(obj==null){
  return new Object();
}
return obj;
}

写一个简单的程序验证一下上述问题,其实线程的学习最好的办法就是举例证明线程的不安全性,然后再想出解决方案,当然这也可能是这部分学习最难所在:

package com.a2.concurrency.chapter1;
/**
 * 线程安全第一种因素:程序中有变量的读取、写入或判断操作
 * @author ChenHui
 *
 */
public class UnsafeSequence {

	private int value;

	public int getValue() {
		return value++;
	}

	public static void main(String[] args) throws InterruptedException {
		
		final UnsafeSequence us = new UnsafeSequence();
		Thread th1 = new Thread("th1") {
			@Override
			public void run() {
				System.out.println( us.getValue()+" "+super.getName());
			}
		};

		Thread th2 = new Thread("th2") {
			@Override
			public void run() {
				System.out.println(us.getValue()+" "+super.getName());
			}
		};

		th1.start();
		/**
		 * 如果不執行Thread.sleep(1000);
		 * 偶尔結果为:
		 * 0 th2
		 * 0 th1
		 * 如果执行Thread.sleep(1000);
		 * 结果为:
		 * 0 th1
		 * 1 th2
		 */
		//Thread.sleep(1000);
		th2.start();
	}
}

对于这种因素产生的问题,我们先给出一种常用解决方案,就是使用同步机制。这里我们先给出最简单,大家也最容易想到的方案,对操作加synchronized关键字:

private volatile int value;
    public synchronized int getNext() {
        return value++;
    }

在这里使用了synchronized的情况下,是否使用volatile关键字并不是主要的。

这一节的最后例举一下一般会遇到线程安全问题的地方,引用自并发编程书中第一章:

Timer

Servlet/JSP

RMI

Swing

l  ……

注:synchronized 方法控制对类成员变量的访问: 每个类实例对应一把锁,每个 synchronized 方法都必须获得调用该方法的类实例的锁方能执行,否则所属 线程阻塞 ,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可 执行状态。这种机制确保了同一时刻对于每一个类实例,其所有声明为 synchronized 的成员函数中至多只有一个处于可执行状态(因为至多只有一个能够获得该类实例对应的锁),从而有效避免了类成员变量的访问冲突(只要所有可能访问类成员变量的方法均被声明为 synchronized)。


展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
14 收藏
4
分享
返回顶部
顶部