【博客为自己复习准备面试知识梳理、总结用,如有错误,望各路大神指正,不胜感激!】
1、问题的出现
/* * 这是一个整数生成器 * canceled 表示这个对象是否已被取消 * */ public abstract class IntGenerator { private volatile boolean canceled = false; public abstract int next(); public void cancel(){ canceled = true; } public boolean isCanceled(){ return canceled; } }
/* * 这是一个任务类:当发现有奇数产生时,将数据生成器的状态设置为【取消】 * test()方法:启动启动大量【使用相同IntGenerator】的任务 */ public class EvenChecker implements Runnable { private IntGenerator generator; private final int id; public EvenChecker(IntGenerator g, int id){ //所有任务都使用同一个Generator(访问同一个资源) generator = g; this.id = id; } @Override public void run() { while( !generator.isCanceled() ){ int val = generator.next(); if(val %2 != 0){ System.out.println(val + " not even!"); generator.cancel(); } } } //启动大量【使用相同IntGenerator】的EvenChcker public static void test(IntGenerator gp, int count){ ExecutorService exec = Executors.newCachedThreadPool(); for(int i = 0; i < count; i++){ exec.execute(new EvenChecker(gp, i)); } exec.shutdown(); } public static void test(IntGenerator gp){ test(gp, 10); } }
//多个线程访问<span style="font-family:Arial, Helvetica, sans-serif;">Generator的 域 可能会使其处于不恰当的状态</span> //在java中递增不是原子性操作,如果不保护任务,即使单一的递增也是不安全的 public class EvenGenerator extends IntGenerator { private int currentEvenValue = 0; @Override public int next() { ++currentEvenValue; //分成多个步骤可能会在任何一个步骤中中断 Thread.yield(); ++currentEvenValue; //currentEvenValue += 2; return currentEvenValue; } public static void main(String[] args) { EvenGenerator eg = new EvenGenerator(); EvenChecker.test(eg); } }2.使用关键字 synchronized 解决问题
如果某个任务处于一个对标记为synchronized 的方法的调用中,那么在这个线程从该方法返回前,其他想调用该类中任何被标记为synchronized的方法的线程都会被阻塞,也就是说
对于某个特定的对象,其所有的synchronized方法共享同一把锁。
将上例第三段代码改为如下,则程序将永远处于正常状态,会不停的输出偶数
/* * 1.synchronized关键字可对方法加锁,防止其他线程调用 * 2.对于特定对象来说,其所有synchronized的方法共享同一把锁 * 3.注意事项:synchronized方法中所使用的域 应该声明为private的,以防止其他线程直接修改域 * 4.一个任务可以多次获得对象的锁 */ public class EvenGenerator extends IntGenerator { private int currentEvenValue = 0; //将可能并发的域设置成private很重要,这样可以防止其他任务直接访问域 @Override public synchronized int next() { //使用synchronized关键字后将对此函数加锁,yield()将不再起作用 ++currentEvenValue; //分成多个步骤可能会在任何一个步骤中中断 Thread.yield(); ++currentEvenValue; //currentEvenValue += 2; return currentEvenValue; } public static void main(String[] args) { EvenGenerator eg = new EvenGenerator(); EvenChecker.test(eg); } }
/* * synchronized的参见 EvenGenerator * 1.Locks对象必须被显示的创建、锁定和释放 * */ public class LockedEventGenerator extends IntGenerator{ private int currentEvenValue = 0; private Lock lock = new ReentrantLock(); //创建锁 @Override public int next() { lock.lock(); //锁定 try{ ++currentEvenValue; Thread.yield(); ++currentEvenValue; return currentEvenValue; }finally{ lock.unlock(); //解锁 } } public static void main(String[] args) { EvenChecker.test(new LockedEventGenerator()); } }
原子操作是指不能被线程调度机制中断的操作。
原子性可以应用于除double和long之外的所有基本类型上的读和写操作
当使用volatile关键字修饰long或者double类型的变量时,就会获得原子性,
虽然它很多情况下可产生跟synchronized类似的效果,但我们应该尽量避免使用volatile关键字,而应转向synchronized