以下是执行结果中的一小片段,出现了多线程安全问题。而如果将同步代码块中的obj改为this,则不会出现多线程安全问题。
Thread-0===sale1===197 Thread-1===sale2===========197 Thread-0===sale1===195 Thread-1===sale2===========195 Thread-1===sale2===========193 Thread-0===sale1===193 Thread-0===sale1===191 Thread-1===sale2===========191 4.4 单例懒汉模式的多线程安全问题单例饿汉式:
class Single { private static final Single s = new Single(); private Single(){}; public static Single getInstance() { return s; } }单例懒汉式:
class Single { private static Single s = null; private Single(){}; public static getInstance(){ if(s==null) { s = new Single(); } return s; } }当多线程操作单例饿汉式和懒汉式对象的资源时,是否有多线程安全问题?
class Demo implements Runnable { public void run(){ Single.getInstance(); } }以上面的代码为例。当多线程分别被CPU调度时,饿汉式中的getInstance()返回的s,s是final属性修饰的,因此随便哪个线程访问都是固定不变的。而懒汉式则随着不同线程的来临,不断new Single(),也就是说各个线程获取到的对象s是不同的,存在多线程安全问题。
只需使用同步就可以解决懒汉式的多线程安全问题。例如使用同步方法。
class Single { private static Single s = null; private Single(){}; public static synchronized getInstance(){ if (s == null){ s = new Single(); } return s; } }这样一来,每个线程来执行这个任务时,都将先判断Single.class这个对象标识的锁是否已经被其他线程持有。虽然解决了问题,但因为每个线程都额外地判断一次锁,导致效率有所下降。可以采用下面的双重判断来解决这个效率降低问题。
class Single { private static Single s = null; private Single(){}; public static getInstance(){ if (s == null) { synchronized(Single.class){ if (s == null){ s = new Single(); } return s; } } } }这样一来,当第一个线程执行这个任务时,将判断s==null为true,于是执行同步代码块并持有锁,保证任务的原子性。而且,即使在最初判断s==null后切换到其他线程了,也没有关系,因为总有一个线程会执行到同步代码块并持有锁,只要持有锁了就一定执行s= new Single(),在这之后,所有的线程在第一阶段的"s==null"判断都为false,从而提高效率。其实,双重判断的同步懒汉式的判断次数和饿汉式的判断次数几乎相等。
5.死锁(DeadLock)最典型的死锁是僵局问题,A等B,B等A,谁都不释放,造成僵局,最后两个线程都无法执行下去。