Java并发原理学习笔记+总结+实战(3)——单例与线程安全问题

1.单例模式

饿汉模式

public class Singleton {

  // 私有化构造方法
  private Singleton() {
  }

  private static Singleton instance = new Singleton();

  public static Singleton getInstance() {
    return instance;
  }
}

懒汉模式

public class Singleton2 {
	//1.将构造方式私有化,不允许外边直接创建对象
	private Singleton2(){
	}
	
	//2.声明类的唯一实例,使用private static修饰
	private static Singleton2 instance;
	
	//3.提供一个用于获取实例的方法,使用public static修饰
	public static Singleton2 getInstance(){
		if(instance==null){
			instance=new Singleton2();
		}
		return instance;
	}

单例模式详细情况在这里就不在赘述了

1.1线程安全性

    出现线程安全性问题的前提条件必须有:1、多线程的环境下;2、必须有共享资源;3、对资源进行非原子性操作。

    在饿汉模式中,因为对象的创建是在初始化类的时候就进行并外部无法直接访问其构造函数,所以使用饿汉模式的时候,在多线程模式下是安全的。

    但是在懒汉模式中,我们保证该类是单例的关键代码是

if(instance == null)

    并且java中new是不具备原子性的(主要涉及到赋值问题),所以,在并发环境下,是可能会执行多次new操作的。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MultiThreadMain {

  public static void main(String[] args) {

    ExecutorService threadPool = Executors.newFixedThreadPool(20);

    for (int i = 0; i < 20; i++) {
      threadPool.execute(new Runnable() {
        @Override
        public void run() {
          System.out.println(Thread.currentThread().getName() + ":" + Singleton2.getInstance());
        }
      });
    }

  }

}

为了使结果更清晰,将懒汉模式稍微改造一下

if(instance==null){
    try{
       Thread.sleep(100);
     }catch(Exception e){
       e.printStackTrace();
  }
	instance=new Singleton2();	
}

结果很明显,几个线程创建了好多个不同的实例对象

 因此,我们可以:

1)、通过添加synchronize来修饰getInstance()方法,简单粗暴,但是简单粗暴同时也意味着要付出惨重的效率代价。当一个线程成功获取锁进入方法后,其他线程就需要等待锁的释放,在等待的过程中就是自旋,自旋是非常消耗CPU资源的。

1.1-1什么是自旋以及理解自旋锁、死锁和重入锁:

  • 重入锁

        synchronize就是典型的重入锁,就是说锁只能被一个线程持有,会把其他线程挡在外面。当两个不同方法A、B都是使用同一个对象去上锁的,其中A方法内部调用B方法,那么,当某一个线程成功获取A方法的锁之后,该线程也能直接访问B方法。下面是例子

public class Demo {


  public synchronized void a() {
    System.out.println("a");
    b();
  }

  public synchronized void b() {
    System.out.println("b");
  }
}

 那么,当在多个线程A方法和B方法分别调用的时候,结果也是相同的

public class Demo {


  public synchronized void a() {
    System.out.println("a");

    try {
      Thread.sleep(1000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }

  public synchronized void b() {
    System.out.println("b");

    try {
      Thread.sleep(1000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }

  }

  public static void main(String[] args) {
    Demo d1 = new Demo();
 

    new Thread(new Runnable() {

      @Override
      public void run() {
        d1.a();
      }
    }).start();
    new Thread(new Runnable() {

      @Override
      public void run() {
        d1.b();
      }
    }).start();
  }

}

 但是,当程序修改为实例化两个对象的时候,synchronize是锁不住的,因为这是两个不同的线程了,所以,只有当想成拿到同一个的对象的时候,才能进行锁重入的

public static void main(String[] args) {
    Demo d1 = new Demo();
    Demo d2 = new Demo();

    new Thread(new Runnable() {

      @Override
      public void run() {
        d1.a();
      }
    }).start();
    new Thread(new Runnable() {

      @Override
      public void run() {
        d2.b();
      }
    }).start();
  }

1.1)、双重检查加锁

    if (instance == null) {
      synchronized (Singleton2.class) {
        if (instance == null) {
          instance = new Singleton2();  //指令重排序
        }
      }
    }

指令重排序:jvm会在不影响最终结果的前提下,为了提高程序的执行效率/性能,会对指令的执行顺序进行重排序。所以,如果上述代码在jvm进行指令重排序之后,即当new Singleton2()这句代码被重排序后先执行,instance的引用就会指向该对象的内存空间地址,导致instance不为空,那么,之后执行为空判断就会出现问题。

解决办法,在声明唯一实例的时候,使用volatile关键字进行修饰

	private static volatile Singleton2 instance;
  •  自旋锁

是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。获取锁的线程一直处于活跃状态,但是并没有执行任何有效的任务。

import java.util.Random;

/**
 * 多个线程执行完毕之后,打印一句话,结束
 *
 * @author worker
 */
public class Demo2 {

  public static void main(String[] args) {

    new Thread(new Runnable() {

      @Override
      public void run() {
        System.out.println(Thread.currentThread().getName() + " 线程执行...");

        try {
          Thread.sleep(new Random().nextInt(2000));
        } catch (InterruptedException e) {
          e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName() + " 线程执行完毕了...");
      }
    }).start();
    new Thread(new Runnable() {

      @Override
      public void run() {
        System.out.println(Thread.currentThread().getName() + " 线程执行...");

        try {
          Thread.sleep(new Random().nextInt(2000));
        } catch (InterruptedException e) {
          e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName() + " 线程执行完毕了...");
      }
    }).start();
    new Thread(new Runnable() {

      @Override
      public void run() {
        System.out.println(Thread.currentThread().getName() + " 线程执行...");

        try {
          Thread.sleep(new Random().nextInt(2000));
        } catch (InterruptedException e) {
          e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName() + " 线程执行完毕了...");
      }
    }).start();
    new Thread(new Runnable() {

      @Override
      public void run() {
        System.out.println(Thread.currentThread().getName() + " 线程执行...");

        try {
          Thread.sleep(new Random().nextInt(2000));
        } catch (InterruptedException e) {
          e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName() + " 线程执行完毕了...");
      }
    }).start();
    new Thread(new Runnable() {

      @Override
      public void run() {
        System.out.println(Thread.currentThread().getName() + " 线程执行...");

        try {
          Thread.sleep(new Random().nextInt(2000));
        } catch (InterruptedException e) {
          e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName() + " 线程执行完毕了...");
      }
    }).start();

    while (Thread.activeCount() != 1) {
      // 自旋
    }
    System.out.println("所有的线程执行完毕了...");
  }

}
  • 死锁
public class Demo3 {

  private Object obj1 = new Object();
  private Object obj2 = new Object();


  public void a() {
    synchronized (obj1) {
      try {
        Thread.sleep(10);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      synchronized (obj2) {
        System.out.println("a");
      }
    }
  }

  public void b() {
    synchronized (obj2) {
      try {
        Thread.sleep(10);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      synchronized (obj1) {
        System.out.println("b");
      }
    }
  }

  public static void main(String[] args) {

    Demo3 d = new Demo3();

    new Thread(new Runnable() {

      @Override
      public void run() {
        d.a();
      }
    }).start();
    new Thread(new Runnable() {

      @Override
      public void run() {
        d.b();
      }
    }).start();
  }

}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值