Java并发编程原理(三)

单例和线程安全

一.为什么会有线程安全问题?

多个线程对共享资源进行非原子性操作

总结

1.多线程环境下
2.共享资源
3.非原子性操作

二.多线程执行时共享了哪些资源

局部变量
局部变量存储在线程自己的栈中,也就是说,局部变量永远也不会被多个线程共享。所以,基础类型的局部变量是线程安全的。

 public static long someMethod(){
        long threadSafeInt = 0;
        threadSafeInt++;
        return threadSafeInt;
    }
  • 局部的对象引用
  public void someMethod(){
        LocalObjectThread localObject = new LocalObjectThread() ;
        localObject.callMethod();
        localObject.method2(localObject);
    }
    public static  void callMethod(){
        System.out.println("this is callMethod");
    }
    public void method2(LocalObjectThread localObject){
        System.out.println("this is method2");
    }

多线程访问someMethod方法时,由于每次访问都会创建一个LocalObjectThread对象实例,符合原子操作。即使调用method2方法,每次传递的对象引用也是唯一的。所以本例中局部对象的u引用是线程安全的。

  • 对象成员
public class NotThreadSafe {
    StringBuilder builder = new StringBuilder();
    public void add(String text){
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        builder.append(text);
        System.out.println(builder.toString());
    }
}

测试类:

public class NotThreadSafeTest {
    public static void main(String args[]) {

        NotThreadSafe sharedInstance = new NotThreadSafe();

        new Thread(new Runnable() {
            @Override
            public void run() {
                sharedInstance.add("some text1");
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                sharedInstance.add("some text2");
                }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                sharedInstance.add("some text3");
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                sharedInstance.add("some text4");
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                sharedInstance.add("some text5");
                }
        }).start();
    }
}

运行结果:
some text3some text4          
some text3some text4          
some text3some text4          
some text3some text4          
some text3some text4          

多个MyRunnable共享了同一个NotThreadSafe对象。因此,当它们调用add()方法时会造成竞态条件。

三,来看看单例模式是如何实现线程安全的

//饱汉式

public class EagerInitializedSingleton {
//同一个类的私有静态变量, 它是类的唯一实例。
    private static final EagerInitializedSingleton instance = new EagerInitializedSingleton();
   //私有构造函数来限制来自其他类的类的实例化。 
    private EagerInitializedSingleton(){}
//返回类实例的公共静态方法, 这是外部世界获取单例类实例的全局访问点。
    public static EagerInitializedSingleton getInstance(){
        return instance;
    }
}

饱汉式线程是安全的,因为每次线程调用getInstance方法时都返回同一实例,这是原子性操作

三,来看看单例模式是如何实现线程安全的

//饱汉式

public class EagerInitializedSingleton {
//同一个类的私有静态变量, 它是类的唯一实例。
private static final EagerInitializedSingleton instance = new EagerInitializedSingleton();
//私有构造函数来限制来自其他类的类的实例化。 
private EagerInitializedSingleton(){}
//返回类实例的公共静态方法, 这是外部世界获取单例类实例的全局访问点。
public static EagerInitializedSingleton getInstance(){
return instance;
}
}

饱汉式线程是安全的,因为每次线程调用getInstance方法时都返回同一实例,这是原子性操作

//延迟加载

public class LazyInitializedSingleton {
//同一个类的私有静态变量, 它是类的唯一实例。
private static LazyInitializedSingleton instance;
//私有构造函数来限制来自其他类的类的实例化。
private LazyInitializedSingleton(){}
//返回类实例的公共静态方法, 这是外部世界获取单例类实例的全局访问点
public static LazyInitializedSingleton getInstance(){
if(instance == null){
            instance = new LazyInitializedSingleton();
}
return instance;
}
}

多线程条件下getInstance()方法是线程不安全的,首先在getInstance()方法上加上锁,静态方法加锁为类锁,线程是安全的,但它降低了性能。为了避免每次都有额外的开销, 使用双重检查锁定原则。

public class LazyInitializedSingleton {
//同一个类的私有静态变量, 它是类的唯一实例。
    private static volatile LazyInitializedSingleton instance;
     //私有构造函数来限制来自其他类的类的实例化。
    private LazyInitializedSingleton(){}
    //返回类实例的公共静态方法, 这是外部世界获取单例类实例的全局访问点
public static ThreadSafeSingleton getInstanceUsingDoubleLocking(){
    if(instance == null){
        synchronized (ThreadSafeSingleton.class) {
            if(instance == null){
                instance = new ThreadSafeSingleton();
            }
        }
    }
    return instance;
}
}

两重if判断却保类的实例化,synchronized确保同一时刻只有一个线程访问。

内部静态帮助器类创建单例类

public class BillPughSingleton {
    private BillPughSingleton(){}
    private static class SingletonHelper{
        private static final BillPughSingleton INSTANCE = new BillPughSingleton();
    }
    
    public static BillPughSingleton getInstance(){
        return SingletonHelper.INSTANCE;
    }
}

加载单例类时, 类不会加载到内存中, 只有当有人调用getInstance方法时, 才会加载此类并创建唯一类实例.且不需要同步。

至于线程的性能问题之后在讨论.

发布了47 篇原创文章 · 获赞 18 · 访问量 5728

猜你喜欢

转载自blog.csdn.net/yuruizai110/article/details/86701005
今日推荐