单例和线程安全
一.为什么会有线程安全问题?
多个线程对共享资源进行非原子性操作
总结
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方法时, 才会加载此类并创建唯一类实例.且不需要同步。
至于线程的性能问题之后在讨论.