线程安全与并发探究(五)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/dongping1023/article/details/47318247

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

@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 unluckytiming, two threads could call getNext and receive the samevalue. Figure 1.1 shows how this can happen. The incrementnotation, nextValue++, may appear to be a single operation, butis in fact three separate operations: read the value, add one to it, and writeout the new value. Since operations in multiple threads may be arbitrarilyinterleaved by the runtime, it is possible for two threads to read the value atthe same time, both see the same value, and then both add one to it. The resultis that the same sequence number is returned from multiple calls in differentthreads.

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

A: value=9            9+1=10              value=10

B:                  value=9             9+1=10                 value=10


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

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


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

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关键字:


1

2

3

4

private volatile int value;

    public synchronized int getNext() {

        return value++;

    }

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

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

l  Timer

l  Servlet/JSP

l  RMI

l  Swing

……

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

猜你喜欢

转载自blog.csdn.net/dongping1023/article/details/47318247
今日推荐