先看一个单例类,后文中都会用到:java
public class SimpleWorkingHardSingleton { private static SimpleWorkingHardSingleton simpleSingleton = new SimpleWorkingHardSingleton(); // 数量 private int count; private SimpleWorkingHardSingleton() { count = 0; } public static SimpleWorkingHardSingleton getInstance() { return simpleSingleton; } public int getCount() { return count; } public void addCount(int increment) { this.count += increment; System.out.println(this.count); } }
上文中,咱们已经知道这个类的getCount方法对count的操做是线程不安全的,咱们能够用一些原子变量来实现原子性:编程
public class SimpleWorkingHardSingleton { private static SimpleWorkingHardSingleton simpleSingleton = new SimpleWorkingHardSingleton(); // 数量 private AtomicLong atomicCount = new AtomicLong(0); private SimpleWorkingHardSingleton() { count = 0; } public static SimpleWorkingHardSingleton getInstance() { return simpleSingleton; } public AtomicLong getAtomicCount() { return atomicCount; } public void addAtomicCount(long increment) { this.atomicCount.getAndAdd(increment); } }
能够看到,在这个类中,咱们把count使用AtomicLong原子类。java的jdk包实现了一系列的原子类,这些原子类型的操做都是原子的。那么count的增长就不会分为3步(获取,增长,赋值)了,这个原子的操做是原子类内部实现的,咱们在使用过程当中只需知道这个操做过程是原子的、不可分割的便可。在使用原子类型的状况下:count变量是会达到预期的效果的。缓存
这里所说的原子变量的失效状况是指当类中使用了多个原子变量,若是一个操做要改变多个原子变量,那么仍是会出现同步问题:安全
public class SimpleWorkingHardSingleton { private static SimpleWorkingHardSingleton simpleSingleton = new SimpleWorkingHardSingleton(); // 数量 private AtomicLong atomicCount = new AtomicLong(0); private AtomicLong atomicCountCopy = new AtomicLong(0); private SimpleWorkingHardSingleton() { count = 0; } public static SimpleWorkingHardSingleton getInstance() { return simpleSingleton; } public AtomicLong getAtomicCount() { return atomicCount; } public AtomicLong getAtomicCountCopy() { return atomicCountCopy; } public void addAtomicCount(long increment) { this.atomicCount.getAndAdd(increment); this.atomicCountCopy.getAndAdd(increment); } }
这种状况下,atomicCount和atomicCountCopy各自的增长是原子的,可是两个变量都增长这个过程是两步,不是原子的。如果a、b两根线程在运行addAtomicCount方法,a线程执行完atomicCount的增长,此时a线程挂起,b线程执行,而且执行了atomicCount和atomicCountCopy的增长,那么此时atomicCountCopy就要比atomicCount小1了,由于a线程还有一半的任务没有执行呢。多线程
java提供了一种内置的锁机制同步代码块(synchronized block),它包括两部分:锁对象和由锁对象保护的代码块。并发
synchronized (lock) { // 操做或访问由lock保护的代码块 }
public class SimpleWorkingHardSingleton { private static SimpleWorkingHardSingleton simpleSingleton = new SimpleWorkingHardSingleton(); // 数量 private int count; private int countCopy; private SimpleWorkingHardSingleton() { count = 0; } public static SimpleWorkingHardSingleton getInstance() { return simpleSingleton; } public int getCount() { return count; } public int getCountCopy() { return countCopy; } public synchronized void addCount(int increment) { /* try { Thread.sleep(3000); } catch (InterruptedException e) { System.err.println(e); } */ this.count += increment; this.countCopy += increment; System.out.println(this.count); } }
上文代码中synchronized对整个方法进行了修饰,那么保护的代码就是方法中的所有代码;这样在多线程环境中,会有序递增地输出count。可是这样有一个潜在问题就是性能问题;
synchronized对整个方法进行了修饰,就会致使这个方法每次只有一个线程能够运行,这就会致使性能问题;假如这个方法中有一个耗时3s的io操做,咱们用Thread.sleep(3000);来模拟。然而synchronized保护的代码块本不该该包含这3s的操做,所以代码应该写成:ide
public void addCount(int increment) { try { Thread.sleep(3000); } catch (InterruptedException e) { System.err.println(e); } synchronized (this) { this.count += increment; System.out.println(this.count); } }
上文中两个变量不一样步的状况,就能够用synchronized同步代码块来解决;并且使用synchronized要注意,先保证正确性,便可能产生并发问题的共享变量都要放在同步代码块当中;而后再追求性能,即对尽量短的代码进行保护,也不能太过细化由于锁的使用和释放都是须要代价的。性能
一个稍微复杂的场景(多看例子多模仿系列)this
/** * 实现带缓存功能的因子分解 */ public class CachedFactorizer { private static CachedFactorizer cachedFactorizer = new CachedFactorizer(); // 上一个处理的数字 private long lastNumber; // 上一个数字分解的结果 private long[] lastFactors; // 处理数字的次数 private long hits; // 缓存命中的次数 private long cacheHits; private CachedFactorizer() { } public synchronized long getHits() { return hits; } public synchronized double getCacheHitRatio() { return (double)cacheHits / (double)hits; } public static CachedFactorizer getInstance() { return cachedFactorizer; } public long[] factor(int target) { // 伪代码,伪装实现了因子分解 return new long[] {}; } public void doFactor(int target) { Thread.sleep(300); synchronized (this) { hits++; if (target == lastNumber) { cacheHits++; } else { lastNumber = target; lastFactors = factor(target); } } } }
当一个线程请求另外一个线程持有的锁的时候,那么请求的线程会阻塞;重入的概念是:当线程去获取本身所拥有的锁,那么会请求成功;重入的原理是:为每一个锁关联一个计数器和持有者线程,当计数器为0时候,这个锁被认为是没有被任何线程持有;当有线程持有锁,计数器自增,而且记下锁的持有线程,当同一线程继续获取锁时候,计数器继续自增;当线程退出代码块时候,相应地计数器减1,直到计数器为0,锁被释放;此时这个锁才能够被其余线程得到。atom
public class Parent { public synchronized void do() { } } public class Child extends Parent { @Override public synchronized void do() { blabla super.do(); } }
若是没有重入机制,那么Child对象在执行do方法时候会发生死锁,由于它拿不到本身持有的锁