java并发编程合集


【Java并发编程】之一:可重入内置锁 每个Java对象都可以用做一个实现同步的锁,这些锁被称为内置锁或监视器锁。线程在进入同步代码块之前会自 动获取锁,并且在退出同步代码块时会自动释放锁。获得内置锁的唯一途径就是进入由这个锁保护的同步代码块或 方法。 当某个线程请求一个由其他线程持有的锁时,发出请求的线程就会阻塞。然而,由于内置锁是可重入的,因此如 果摸个线程试图获得一个已经由它自己持有的锁,那么这个请求就会成功。“重入”意味着获取锁的操作的粒度是“ 线程”,而不是调用。重入的一种实现方法是,为每个锁关联一个获取计数值和一个所有者线程。当计数值为0时,这 个锁就被认为是没有被任何线程所持有,当线程请求一个未被持有的锁时,JVM将记下锁的持有者,并且将获取计 数值置为1,如果同一个线程再次获取这个锁,计数值将递增,而当线程退出同步代码块时,计数器会相应地递减。 当计数值为0时,这个锁将被释放。 重入进一步提升了加锁行为的封装性,因此简化了面向对象并发代码的开发。分析如下程序: public class Father { public synchronized void doSomething(){ ...... } } public class Child extends Father { public synchronized void doSomething(){ ...... super.doSomething(); } } 子类覆写了父类的同步方法,然后调用父类中的方法,此时如果没有可重入的锁,那么这段代码件产生死锁。 由于Fither和Child中的doSomething方法都是synchronized方法,因此每个doSomething方法在执行前都会获 取Child对象实例上的锁。如果内置锁不是可重入的,那么在调用super.doSomething时将无法获得该Child对象上的 互斥锁,因为这个锁已经被持有,从而线程会永远阻塞下去,一直在等待一个永远也无法获取的锁。重入则避免了这 种死锁情况的发生。 同一个线程在调用本类中其他synchronized方法/块或父类中的synchronized方法/块时,都不会阻碍该线程地 执行,因为互斥锁时可重入的。 【Java并发编程】之二:线程中断(含代码) 转载请注明出处:转载请注明出处:http://blog.csdn.net/ns_code/article/details/17091267 使用使用interrupt()中断线程()中断线程 当一个线程运行时,另一个线程可以调用对应的Thread对象的interrupt()方法来中断它,该方法只是在目标线 程中设置一个标志,表示它已经被中断,并立即返回。这里需要注意的是,如果只是单纯的调用interrupt()方法 ,线程并没有实际被中断,会继续往下执行。 下面一段代码演示了休眠线程的中断: public class SleepInterrupt extends Object implements Runnable{ public void run(){ try{ System.out.println("in run() - about to sleep for 20 seconds"); Thread.sleep(20000); System.out.println("in run() - woke up"); }catch(InterruptedException e){ System.out.println("in run() - interrupted while sleeping"); //处理完中断异常后,返回到run()方法人口, //如果没有return,线程不会实际被中断,它会继续打印下面的信息 return; } System.out.println("in run() - leaving normally"); } public static void main(String[] args) { SleepInterrupt si = new SleepInterrupt(); Thread t = new Thread(si); t.start(); //主线程休眠2秒,从而确保刚才启动的线程有机会执行一段时间 try { Thread.sleep(2000); }catch(InterruptedException e){ e.printStackTrace(); } System.out.println("in main() - interrupting other thread"); //中断线程t t.interrupt(); System.out.println("in main() - leaving"); } } 运行结果如下: 主线程启动新线程后,自身休眠2秒钟,允许新线程获得运行时间。新线程打印信息“about to sleep for 20 seconds”后,继而休眠20秒钟,大约2秒钟后,main线程通知新线程中断,那么新线程的20秒的休眠将被打断,从 而抛出InterruptException异常,执行跳转到catch块,打印出“interrupted while sleeping”信息,并立即从run() 方法返回,然后消亡,而不会打印出catch块后面的“leaving normally”信息。 请注意:由于不确定的线程规划,上图运行结果的后两行可能顺序相反,这取决于主线程和新线程哪个先消亡。 但前两行信息的顺序必定如上图所示。 另外,如果将catch块中的return语句注释掉,则线程在抛出异常后,会继续往下执行,而不会被中断,从而会打 印出”leaving normally“信息。 待决中断待决中断 在上面的例子中,sleep()方法的实现检查到休眠线程被中断,它会相当友好地终止线程,并抛 出InterruptedException异常。另外一种情况,如果线程在调用sleep()方法前被中断,那么该中断称为待决中断 ,它会在刚调用sleep()方法时,立即抛出InterruptedException异常。 下面的代码演示了待决中断: public class PendingInterrupt extends Object { public static void main(String[] args){ //如果输入了参数,则在mian线程中中断当前线程(亦即main线程) if( args.length > 0 ){ Thread.currentThread().interrupt(); } //获取当前时间 long startTime = System.currentTimeMillis(); try{ Thread.sleep(2000); System.out.println("was NOT interrupted"); }catch(InterruptedException x){ System.out.println("was interrupted"); } //计算中间代码执行的时间 System.out.println("elapsedTime=" + ( System.currentTimeMillis() - startTime)); } } 如果PendingInterrupt不带任何命令行参数,那么线程不会被中断,最终输出的时间差距应该在2000附近(具体 时间由系统决定,不精确),如果PendingInterrupt带有命令行参数,则调用中断当前线程的代码,但main线程仍然 运行,最终输出的时间差距应该远小于2000,因为线程尚未休眠,便被中断,因此,一旦调用sleep()方法,会立 即打印出catch块中的信息。执行结果如下: 这种模式下,main线程中断它自身。除了将中断标志(它是Thread的内部标志)设置为true外,没有其他任何 影响。线程被中断了,但main线程仍然运行,main线程继续监视实时时钟,并进入try块,一旦调用sleep()方法, 它就会注意到待决中断的存在,并抛出InterruptException。于是执行跳转到catch块,并打印出线程被中断的信息 。最后,计算并打印出时间差。 使用使用isInterrupted()方法判断中断状态()方法判断中断状态 可以在Thread对象上调用isInterrupted()方法来检查任何线程的中断状态。这里需要注意:线程一旦被 中断,isInterrupted()方法便会返回true,而一旦sleep()方法抛出异常,它将清空中断标志,此 时isInterrupted()方法将返回false。 下面的代码演示了isInterrupted()方法的使用: public class InterruptCheck extends Object{ public static void main(String[] args){ Thread t = Thread.currentThread(); System.out.println("Point A: t.isInterrupted()=" + t.isInterrupted()); //待决中断,中断自身 t.interrupt(); System.out.println("Point B: t.isInterrupted()=" + t.isInterrupted()); System.out.println("Point C: t.isInterrupted()=" + t.isInterrupted()); try{ Thread.sleep(2000); System.out.println("was NOT interrupted"); }catch( InterruptedException x){ System.out.println("was interrupted"); } //抛出异常后,会清除中断标志,这里会返回false System.out.println("Point D: t.isInterrupted()=" + t.isInterrupted()); } } 运行结果如下: 使用使用Thread.interrupted()方法判断中断状态()方法判断中断状态 可以使用Thread.interrupted()方法来检查当前线程的中断状态(并隐式重置为false)。又由于它是静态方法 ,因此不能在特定的线程上使用,而只能报告调用它的线程的中断状态,如果线程被中断,而且中断状态尚不清楚, 那么,这个方法返回true。与isInterrupted()不同,它将自动重置中断状态为false,第二次调 用Thread.interrupted()方法,总是返回false,除非中断了线程。 如下代码演示了Thread.interrupted()方法的使用: public class InterruptReset extends Object { public static void main(String[] args) { System.out.println( "Point X: Thread.interrupted()=" + Thread.interrupted()); Thread.currentThread().interrupt(); System.out.println( "Point Y: Thread.interrupted()=" + Thread.interrupted()); System.out.println( "Point Z: Thread.interrupted()=" + Thread.interrupted()); } } 运行结果如下: 从结果中可以看出,当前线程中断自身后,在Y点,中断状态为true,并由Thread.interrupted()自动重置 为false,那么下次调用该方法得到的结果便是false。 补充补充 这里补充下yield和join方法的使用。 join方法用线程对象调用,如果在一个线程A中调用另一个线程B的join方法,线程A将会等待线程B执行完毕后再 执行。 yield可以直接用Thread类调用,yield让出CPU执行权给同等级的线程,如果没有相同级别的线程在等待CPU的执 行权,则该线程继续执行。 【Java并发编程】之三:线程挂起、恢复与终止的正确方法(含代码) 转载请注明出处:转载请注明出处:http://blog.csdn.net/ns_code/article/details/17095733 挂起和恢复线程挂起和恢复线程 Thread 的API中包含两个被淘汰的方法,它们用于临时挂起和重启某个线程,这些方法已经被淘汰,因为它们 是不安全的,不稳定的。如果在不合适的时候挂起线程(比如,锁定共享资源时),此时便可能会发生死锁条件—— 其他线程在等待该线程释放锁,但该线程却被挂起了,便会发生死锁。另外,在长时间计算期间挂起线程也可能导致 问题。 下面的代码演示了通过休眠来延缓运行,模拟长时间运行的情况,使线程更可能在不适当的时候被挂起: public class DeprecatedSuspendResume extends Object implements Runnable{ //volatile关键字,表示该变量可能在被一个线程使用的同时,被另一个线程修改 private volatile int firstVal; private volatile int secondVal; //判断二者是否相等 public boolean areValuesEqual(){ return ( firstVal == secondVal); } public void run() { try{ firstVal = 0; secondVal = 0; workMethod(); }catch(InterruptedException x){ System.out.println("interrupted while in workMethod()"); } } private void workMethod() throws InterruptedException { int val = 1; while (true){ stepOne(val); stepTwo(val); val++; Thread.sleep(200); //再次循环钱休眠200毫秒 } } //赋值后,休眠300毫秒,从而使线程有机会在stepOne操作和stepTwo操作之间被挂起 private void stepOne(int newVal) throws InterruptedException{ firstVal = newVal; Thread.sleep(300); //模拟长时间运行的情况 } private void stepTwo(int newVal){ secondVal = newVal; } public static void main(String[] args){ DeprecatedSuspendResume dsr = new DeprecatedSuspendResume(); Thread t = new Thread(dsr); t.start(); //休眠1秒,让其他线程有机会获得执行 try { Thread.sleep(1000);} catch(InterruptedException x){} for (int i = 0; i < 10; i++){ //挂起线程 t.suspend(); System.out.println("dsr.areValuesEqual()=" + dsr.areValuesEqual()); //恢复线程 t.resume(); try{ //线程随机休眠0~2秒 Thread.sleep((long)(Math.random()*2000.0)); }catch(InterruptedException x){ //略 } } System.exit(0); //中断应用程序 } } 某次运行结果如下: 从areValuesEqual()返回的值有时为true,有时为false。以上代码中,在设置firstVal之后,但在设 置secondVal之前,挂起新线程会产生麻烦,此时输出的结果会为false(情况1),这段时间不适宜挂起线程,但因 为线程不能控制何时调用它的suspend方法,所以这种情况是不可避免的。 当然,即使线程不被挂起(注释掉挂起和恢复线程的两行代码),如果在main线程中执行asr.areValuesEqual() 进行比较时,恰逢stepOne操作执行完,而stepTwo操作还没执行,那么得到的结果同样可能是false(情况2)。 下面我们给出不用上述两个方法来实现线程挂起和恢复的策略——设置标志位。通过该方法实现线程的挂起和恢 复有一个很好的地方,就是可以在线程的指定位置实现线程的挂起和恢复,而不用担心其不确定性。 对于上述代码的改进代码如下: public class AlternateSuspendResume extends Object implements Runnable { private volatile int firstVal; private volatile int secondVal; //增加标志位,用来实现线程的挂起和恢复 private volatile boolean suspended; public boolean areValuesEqual() { return ( firstVal == secondVal ); } public void run() { try { suspended = false; firstVal = 0; secondVal = 0; workMethod(); } catch ( InterruptedException x ) { System.out.println("interrupted while in workMethod()"); } } private void workMethod() throws InterruptedException { int val = 1; while ( true ) { //仅当贤臣挂起时,才运行这行代码 waitWhileSuspended(); stepOne(val); stepTwo(val); val++; //仅当线程挂起时,才运行这行代码 waitWhileSuspended(); Thread.sleep(200); } } private void stepOne(int newVal) throws InterruptedException { firstVal = newVal; Thread.sleep(300); } private void stepTwo(int newVal) { secondVal = newVal; } public void suspendRequest() { suspended = true; } public void resumeRequest() { suspended = false; } private void waitWhileSuspended() throws InterruptedException { //这是一个“繁忙等待”技术的示例。 //它是非等待条件改变的最佳途径,因为它会不断请求处理器周期地执行检查, //更佳的技术是:使用Java的内置“通知-等待”机制 while ( suspended ) { Thread.sleep(200); } } public static void main(String[] args) { AlternateSuspendResume asr = new AlternateSuspendResume(); Thread t = new Thread(asr); t.start(); //休眠1秒,让其他线程有机会获得执行 try { Thread.sleep(1000); } catch ( InterruptedException x ) { } for ( int i = 0; i < 10; i++ ) { asr.suspendRequest(); //让线程有机会注意到挂起请求 //注意:这里休眠时间一定要大于 //stepOne操作对firstVal赋值后的休眠时间,即300ms, //目的是为了防止在执行asr.areValuesEqual()进行比较时, //恰逢stepOne操作执行完,而stepTwo操作还没执行 try { Thread.sleep(350); } catch ( InterruptedException x ) { } System.out.println("dsr.areValuesEqual()=" + asr.areValuesEqual()); asr.resumeRequest(); try { //线程随机休眠0~2秒 Thread.sleep( ( long ) (Math.random() * 2000.0) ); } catch ( InterruptedException x ) { //略 } } System.exit(0); //退出应用程序 } } 运行结果如下: 由结果可以看出,输出的所有结果均为true。首先,针对情况1(线程挂起的位置不确定),这里确定了线程挂起 的位置,不会出现线程在stepOne操作和stepTwo操作之间挂起的情况;针对情况2(main线程中执 行asr.areValuesEqual()进行比较时,恰逢stepOne操作执行完,而stepTwo操作还没执行),在发出挂起请求后 ,还没有执行asr.areValuesEqual()操作前,让main线程休眠450ms(>300ms),如果挂起请求发出时,新线程 正执行到或即将执行到stepOne操作(如果在其前面的话,就会响应挂起请求,从而挂起线程),那么在stepTwo操 作执行前,main线程的休眠还没结束,从而main线程休眠结束后执行asr.areValuesEqual()操作进行比 较时,stepTwo操作已经执行完,因此也不会出现输出结果为false的情况。 可以将ars.suspendRequest()代码后的sleep代码去掉,或将休眠时间改为200(明显小于300即可)后,查看 执行结果,会发现结果中依然会有出现false的情况。如下图所示: 总结:线程的挂起和恢复实现的正确方法是:通过设置标志位,让线程在安全的位置挂起总结:线程的挂起和恢复实现的正确方法是:通过设置标志位,让线程在安全的位置挂起 终止线程终止线程 当调用Thread的start()方法,执行完run()方法后,或在run()方法中return,线程便会自然消亡。另 外Thread API中包含了一个stop()方法,可以突然终止线程。但它在JDK1.2后便被淘汰了,因为它可能导致数据 对象的崩溃。一个问题是,当线程终止时,很少有机会执行清理工作;另一个问题是,当在某个线程上调用stop() 方法时,线程释放它当前持有的所有锁,持有这些锁必定有某种合适的理由——也许是阻止其他线程访问尚未处于一 致性状态的数据,突然释放锁可能使某些对象中的数据处于不一致状态,而且不会出现数据可能崩溃的任何警告。 终止线程的替代方法:同样是使用标志位,通过控制标志位来终止线程。终止线程的替代方法:同样是使用标志位,通过控制标志位来终止线程。 【Java并发编程】之四:守护线程与线程阻塞的四种情况 转载请注明出处:转载请注明出处:http://blog.csdn.net/ns_code/article/details/17099981 守护线程守护线程 Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程) 用户线程即运行在前台的线程,而守护线程是运行在后台的线程。 守护线程作用是为其他前台线程的运行提供便 利服务,而且仅在普通、非守护线程仍然运行时才需要,比如垃圾回收线程就是一个守护线程。当VM检测仅剩一个 守护线程,而用户线程都已经退出运行时,VM就会退出,因为没有如果没有了被守护这,也就没有继续运行程序的 必要了。如果有非守护线程仍然存活,VM就不会退出。 守护线程并非只有虚拟机内部提供,用户在编写程序时也可以自己设置守护线程。用户可以用Thread 的setDaemon(true)方法设置当前线程为守护线程。 虽然守护线程可能非常有用,但必须小心确保其他所有非守护线程消亡时,不会由于它的终止而产生任何危害 。因为你不可能知道在所有的用户线程退出运行前,守护线程是否已经完成了预期的服务任务。一旦所有的用户线程 退出了,虚拟机也就退出运行了。 因此,不要在守护线程中执行业务逻辑操作(比如对数据的读写等)。、 另外有几点需要注意: 1、setDaemon(true)必须在调用线程的start()方法之前设置,否则会跑出IllegalThreadStateException异常 。 2、在守护线程中产生的新线程也是守护线程。 3、 不要认为所有的应用都可以分配给守护线程来进行服务,比如读写操作或者计算逻辑。 线程阻塞线程阻塞 线程可以阻塞于四种状态: 1、当线程执行Thread.sleep()时,它一直阻塞到指定的毫秒时间之后,或者阻塞被另一个线程打断; 2、当线程碰到一条wait()语句时,它会一直阻塞到接到通知(notify())、被中断或经过了指定毫秒时间为 止(若制定了超时值的话) 3、线程阻塞与不同I/O的方式有多种。常见的一种方式是InputStream的read()方法,该方法一直阻塞到从流 中读取一个字节的数据为止,它可以无限阻塞,因此不能指定超时时间; 4、线程也可以阻塞等待获取某个对象锁的排他性访问权限(即等待获得synchronized语句必须的锁时阻塞)。 注意,并非所有的阻塞状态都是可中断的,以上阻塞状态的前两种可以被中断,后两种不会对中断做出反应 【Java并发编程】之六:Runnable和Thread实现多线程的区 转载请注明出处:转载请注明出处:http://blog.csdn.net/ns_code/article/details/17161237 Java中实现多线程有两种方法:继承Thread类、实现Runnable接口,在程序开发中只要是多线程,肯定永远以实 现Runnable接口为主,因为实现Runnable接口相比继承Thread类有如下优势: 1、可以避免由于Java的单继承特性而带来的局限; 2、增强程序的健壮性,代码能够被多个线程共享,代码与数据是独立的; 3、适合多个相同程序代码的线程区处理同一资源的情况。 下面以典型的买票程序(基本都是以这个为例子)为例,来说明二者的区别。 首先通过继承Thread类实现,代码如下: class MyThread extends Thread{ private int ticket = 5; public void run(){ for (int i=0;i<10;i++) { if(ticket > 0){ System.out.println("ticket = " + ticket--); } } } } public class ThreadDemo{ public static void main(String[] args){ new MyThread().start(); new MyThread().start(); new MyThread().start(); } } 某次的执行结果如下: 从结果中可以看出,每个线程单独卖了5张票,即独立地完成了买票的任务,但实际应用中,比如火车站售票,需 要多个线程去共同完成任务,在本例中,即多个线程共同买5张票。 下面是通过实现Runnable接口实现的多线程程序,代码如下: class MyThread implements Runnable{ private int ticket = 5; public void run(){ for (int i=0;i<10;i++) { if(ticket > 0){ System.out.println("ticket = " + ticket--); } } } } public class RunnableDemo{ public static void main(String[] args){ MyThread my = new MyThread(); new Thread(my).start(); new Thread(my).start(); new Thread(my).start(); } } 某次的执行结果如下: 从结果中可以看出,三个线程一共卖了5张票,即它们共同完成了买票的任务,实现了资源的共享。 针对以上代码补充三点: 1、在第二种方法(Runnable)中,ticket输出的顺序并不是54321,这是因为线程执行的时机难以预测,ticket-- 并不是原子操作。 2、在第一种方法中,我们new了3个Thread对象,即三个线程分别执行三个对象中的代码,因此便是三个线程去 独立地完成卖票的任务;而在第二种方法中,我们同样也new了3个Thread对象,但只有一个Runnable对象,3 个Thread对象共享这个Runnable对象中的代码,因此,便会出现3个线程共同完成卖票任务的结果。如果我们new出 3个Runnable对象,作为参数分别传入3个Thread对象中,那么3个线程便会独立执行各自Runnable对象中的代码, 即3个线程各自卖5张票。 3、在第二种方法中,由于3个Thread对象共同执行一个Runnable对象中的代码,因此可能会造成线程的不安全, 比如可能ticket会输出-1(如果我们System.out....语句前加上线程休眠操作,该情况将很有可能出现),这种情况的 出现是由于,一个线程在判断ticket为1>0后,还没有来得及减1,另一个线程已经将ticket减1,变为了0,那么接下 来之前的线程再将ticket减1,便得到了-1。这就需要加入同步操作(即互斥锁),确保同一时刻只有一个线程在执 行每次for循环中的操作。而在第一种方法中,并不需要加入同步操作,因为每个线程执行自己Thread对象中的代码 ,不存在多个线程共同执行同一个方法的情况。 【Java并发编程】之五:volatile变量修饰符—意料之外的问题( 转载请注明出处:转载请注明出处:http://blog.csdn.net/ns_code/article/details/17101369 volatile用处说明用处说明 在JDK1.2之前,Java的内存模型实现总是从主存(即共享内存)读取变量,是不需要进行特别的注意的。而随 着JVM的成熟和优化,现在在多线程环境下volatile关键字的使用变得非常重要。 在当前的Java内存模型下,线程可以把变量保存在本地内存(比如机器的寄存器)中,而不是直接在主存中进行 读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的 拷贝,造成数据的不一致。 要解决这个问题,就需要把变量声明为volatile(也可以使用同步,参 见http://blog.csdn.net/ns_code/article/details/17288243),这就指示JVM,这个变量是不稳定的,每次使用 它都到主存中进行读取。一般说来,多任务环境下,各任务间共享的变量都应该加volatile修饰符。 Volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发 生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值 。 Java语言规范中指出:为了获得最佳速度,允许线程保存共享成员变量的私有拷贝,而且只当线程进入或者离 开同步代码块时才将私有拷贝与共享内存中的原始值进行比较。 这样当多个线程同时与某个对象交互时,就必须注意到要让线程及时的得到共享成员变量的变化。而volatile关 键字就是提示JVM:对于这个成员变量,不能保存它的私有拷贝,而应直接与共享成员变量交互。 volatile是一种稍弱的同步机制,在访问volatile变量时不会执行加锁操作,也就不会执行线程阻塞,因此volatilei 变量是一种比synchronized关键字更轻量级的同步机制。 使用建议:在两个或者更多的线程需要访问的成员变量上使用volatile。当要访问的变量已在synchronized代码 块中,或者为常量时,没必要使用volatile。 由于使用volatile屏蔽掉了JVM中必要的代码优化,所以在效率上比较低,因此一定在必要时才使用此关键字。 示例程序示例程序 下面给出一段代码,通过其运行结果来说明使用关键字volatile产生的差异,但实际上遇到了意料之外的问题: public class Volatile extends Object implements Runnable { //value变量没有被标记为volatile private int value; //missedIt变量被标记为volatile private volatile boolean missedIt; //creationTime不需要声明为volatile,因为代码执行中它没有发生变化 private long creationTime; public Volatile() { value = 10; missedIt = false; //获取当前时间,亦即调用Volatile构造函数时的时间 creationTime = System.currentTimeMillis(); } public void run() { print("entering run()"); //循环检查value的值是否不同 while ( value < 20 ) { //如果missedIt的值被修改为true,则通过break退出循环 if ( missedIt ) { //进入同步代码块前,将value的值赋给currValue int currValue = value; //在一个任意对象上执行同步语句,目的是为了让该线程在进入和离开同步代码块时, //将该线程中的所有变量的私有拷贝与共享内存中的原始值进行比较, //从而发现没有用volatile标记的变量所发生的变化 Object lock = new Object(); synchronized ( lock ) { //不做任何事 } //离开同步代码块后,将此时value的值赋给valueAfterSync int valueAfterSync = value; print("in run() - see value=" + currValue +", but rumor has it that it changed!"); print("in run() - valueAfterSync=" + valueAfterSync); break; } } print("leaving run()"); } public void workMethod() throws InterruptedException { print("entering workMethod()"); print("in workMethod() - about to sleep for 2 seconds"); Thread.sleep(2000); //仅在此改变value的值 value = 50; print("in workMethod() - just set value=" + value); print("in workMethod() - about to sleep for 5 seconds"); Thread.sleep(5000); //仅在此改变missedIt的值 missedIt = true; print("in workMethod() - just set missedIt=" + missedIt); print("in workMethod() - about to sleep for 3 seconds"); Thread.sleep(3000); print("leaving workMethod()"); } /* *该方法的功能是在要打印的msg信息前打印出程序执行到此所化去的时间,以及打印msg的代码所在的线程 */ private void print(String msg) { //使用java.text包的功能,可以简化这个方法,但是这里没有利用这一点 long interval = System.currentTimeMillis() - creationTime; String tmpStr = " " + ( interval / 1000.0 ) + "000"; int pos = tmpStr.indexOf("."); String secStr = tmpStr.substring(pos - 2, pos + 4); String nameStr = " " + Thread.currentThread().getName(); nameStr = nameStr.substring(nameStr.length() - 8, nameStr.length()); System.out.println(secStr + " " + nameStr + ": " + msg); } public static void main(String[] args) { try { //通过该构造函数可以获取实时时钟的当前时间 Volatile vol = new Volatile(); //稍停100ms,以让实时时钟稍稍超前获取时间,使print()中创建的消息打印的时间值大于0 Thread.sleep(100); Thread t = new Thread(vol); t.start(); //休眠100ms,让刚刚启动的线程有时间运行 Thread.sleep(100); //workMethod方法在main线程中运行 vol.workMethod(); } catch ( InterruptedException x ) { System.err.println("one of the sleeps was interrupted"); } } } 按照以上的理论来分析,由于value变量不是volatile的,因此它在main线程中的改变不会被Thread-0线程( 在main线程中新开启的线程)马上看到,因此Thread-0线程中的while循环不会直接退出,它会继续判断missedIt 的值,由于missedIt是volatile的,当main线程中改变了missedIt时,Thread-0线程会立即看到该变化,那么if语句 中的代码便得到了执行的机会,由于此时Thread-0依然没有看到value值的变化,因此,currValue的值为10,继续 向下执行,进入同步代码块,因为进入前后要将该线程内的变量值与共享内存中的原始值对比,进行校准,因此离开 同步代码块后,Thread-0便会察觉到value的值变为了50,那么后面的valueAfterSync的值便为50,最后从break 跳出循环,结束Thread-0线程。 意料之外的问题意料之外的问题 但实际的执行结果如下: 从结果中可以看出,Thread-0线程并没有进入while循环,说明Thread-0线程在value的值发生变 化后,missedIt的值发生变化前,便察觉到了value值的变化,从而退出了while循环。这与理论上的分析不符,我便 尝试注释掉value值发生改变与missedIt值发生改变之间的线程休眠代码Thread.sleep(5000),以确保Thread-0线 程在missedIt的值发生改变前,没有时间察觉到value值的变化。但执行的结果与上面大同小异(可能有一两行顺序 不同,但依然不会打印出if语句中的输出信息)。 问题分析问题分析 在JDK1.7~JDK1.3之间的版本上输出结果与上面基本大同小异,只有在JDK1.2上才得到了预期的结果, 即Thread-0线程中的while循环是从if语句中退出的,这说明Thread-0线程没有及时察觉到value值的变化。 这里需要注意:volatile是针对JIT带来的优化,因此JDK1.2以前的版本基本不用考虑,另外,在JDK1.3.1开始 ,开始运用HotSpot虚拟机,用来代替JIT。因此,是不是HotSpot的问题呢?这里需要再补充一点: JIT或HotSpot编译器在server模式和client模式编译不同,server模式为了使线程运行更快,如果其中一个线 程更改了变量boolean flag 的值,那么另外一个线程会看不到,因为另外一个线程为了使得运行更快所以从寄存器或 者本地cache中取值,而不是从内存中取值,那么使用volatile后,就告诉不论是什么线程,被volatile修饰的变量都 要从内存中取值。《内存栅栏》 但看了这个帖子http://segmentfault.com/q/1010000000147713(也有人遇到同样的问题了)说,尝试 了HotSpot的server和client两种模式,以及JDK1.3的classic,都没有效果,只有JDK1.2才能得到预期的结果。 哎!看来自己知识还是比较匮乏,看了下网友给出的答案,对于非volatile修饰的变量,尽管jvm的优化,会导致 变量的可见性问题,但这种可见性的问题也只是在短时间内高并发的情况下发生,CPU执行时会很快刷新Cache,一 般的情况下很难出现,而且出现这种问题是不可预测的,与jvm, 机器配置环境等都有关。 姑且先这么理解吧!一点点积累。。。 正确的分析在这里:正确的分析在这里:http://blog.csdn.net/ns_code/article/details/17382679 这里附上分析结果时参考的帖子及文章 http://segmentfault.com/q/1010000000147713 http://www.iteye.com/problems/98213 http://www.oldcaptain.cc/articles/2013/08/21/1377092100971.html 【Java并发编程】之七:使用synchronized获取互斥锁的几点 转载请注明出处:转载请注明出处:http://blog.csdn.net/ns_code/article/details/17199201 在并发编程中,多线程同时并发访问的资源叫做临界资源,当多个线程同时访问对象并要求操作相同资源时,分 割了原子操作就有可能出现数据的不一致或数据不完整的情况,为避免这种情况的发生,我们会采取同步机制,以确 保在某一时刻,方法内只允许有一个线程。 采用synchronized修饰符实现的同步机制叫做互斥锁机制,它所获得的锁叫做互斥锁。每个对象都有一 个monitor(锁标记),当线程拥有这个锁标记时才能访问这个资源,没有锁标记便进入锁池。任何一个对象系统都会 为其创建一个互斥锁,这个锁是为了分配给线程的,防止打断原子操作。每个对象的锁只能分配给一个线程,因此叫 做互斥锁。 这里就使用同步机制获取互斥锁的情况,进行几点说明: 1、如果同一个方法内同时有两个或更多线程,则每个线程有自己的局部变量拷贝。 2、类的每个实例都有自己的对象级别锁。当一个线程访问实例对象中的synchronized同步代码块或同步方法时 ,该线程便获取了该实例的对象级别锁,其他线程这时如果要访问synchronized同步代码块或同步方法,便需要阻塞 等待,直到前面的线程从同步代码块或方法中退出,释放掉了该对象级别锁。 3、访问同一个类的不同实例对象中的同步代码块,不存在阻塞等待获取对象锁的问题,因为它们获取的是各自 实例的对象级别锁,相互之间没有影响。 4、持有一个对象级别锁不会阻止该线程被交换出来,也不会阻塞其他线程访问同一示例对象中的 非synchronized代码。当一个线程A持有一个对象级别锁(即进入了synchronized修饰的代码块或方法中)时,线程 也有可能被交换出去,此时线程B有可能获取执行该对象中代码的时间,但它只能执行非同步代码(没有 用synchronized修饰),当执行到同步代码时,便会被阻塞,此时可能线程规划器又让A线程运行,A线程继续持有 对象级别锁,当A线程退出同步代码时(即释放了对象级别锁),如果B线程此时再运行,便会获得该对象级别锁, 从而执行synchronized中的代码。 5、持有对象级别锁的线程会让其他线程阻塞在所有的synchronized代码外。例如,在一个类中有三 个synchronized方法a,b,c,当线程A正在执行一个实例对象M中的方法a时,它便获得了该对象级别锁,那么其他 的线程在执行同一实例对象(即对象M)中的代码时,便会在所有的synchronized方法处阻塞,即在方法a,b,c处 都要被阻塞,等线程A释放掉对象级别锁时,其他的线程才可以去执行方法a,b或者c中的代码,从而获得该对象级 别锁。 6、使用synchronized(obj)同步语句块,可以获取指定对象上的对象级别锁。obj为对象的引用,如果获取 了obj对象上的对象级别锁,在并发访问obj对象时时,便会在其synchronized代码处阻塞等待,直到获取到该obj对 象的对象级别锁。当obj为this时,便是获取当前对象的对象级别锁。 7、类级别锁被特定类的所有示例共享,它用于控制对static成员变量以及static方法的并发访问。具体用法与对 象级别锁相似。 8、互斥是实现同步的一种手段,临界区、互斥量和信号量都是主要的互斥实现方式。synchronized关键字经过编 译后,会在同步块的前后分别形成monitorenter和monitorexit这两个字节码指令。根据虚拟机规范的要求,在执 行monitorenter指令时,首先要尝试获取对象的锁,如果获得了锁,把锁的计数器加1,相应地,在执行monitorexit 指令时会将锁计数器减1,当计数器为0时,锁便被释放了。由于synchronized同步块对同一个线程是可重入的,因此 一个线程可以多次获得同一个对象的互斥锁,同样,要释放相应次数的该互斥锁,才能最终释放掉该锁。 【Java并发编程】之十:使用waitnotifynotifyAll实 转载请注明出处:转载请注明出处:http://blog.csdn.net/ns_code/article/details/17225469 在Java中,可以通过配合调用Object对象的wait()方法和notify()方法或notifyAll()方法来实现线程间的 通信。在线程中调用wait()方法,将阻塞等待其他线程的通知(其他线程调用notify()方法或notifyAll() 方法),在线程中调用notify()方法或notifyAll()方法,将通知其他线程从wait()方法处返回。 Object是所有类的超类,它有5个方法组成了等待/通知机制的核心:notify()、notifyAll()、wait()、 wait(long)和wait(long,int)。在Java中,所有的类都从Object继承而来,因此,所有的类都拥有这些共有方 法可供使用。而且,由于他们都被声明为final,因此在子类中不能覆写任何一个方法。 这里详细说明一下各个方法在使用中需要注意的几点: 1、wait() public final void wait() throws InterruptedException,IllegalMonitorStateException 该方法用来将当前线程置入休眠状态,直到接到通知或被中断为止。在调用wait()之前,线程必须要获得该对 象的对象级别锁,即只能在同步方法或同步块中调用wait()方法。进入wait()方法后,当前线程释放锁。在 从wait()返回前,线程与其他线程竞争重新获得锁。如果调用wait()时,没有持有适当的锁,则抛 出IllegalMonitorStateException,它是RuntimeException的一个子类,因此,不需要try-catch结构。 2、notify() public final native void notify() throws IllegalMonitorStateException 该方法也要在同步方法或同步块中调用,即在调用前,线程也必须要获得该对象的对象级别锁,的如果调 用notify()时没有持有适当的锁,也会抛出IllegalMonitorStateException。 该方法用来通知那些可能等待该对象的对象锁的其他线程。如果有多个线程等待,则线程规划器任意挑选出其中 一个wait()状态的线程来发出通知,并使它等待获取该对象的对象锁(notify后,当前线程不会马上释放该对 象锁,wait所在的线程并不能马上获取该对象锁,要等到程序退出synchronized代码块后,当前线程才会释 放锁,wait所在的线程也才可以获取该对象锁),但不惊动其他同样在等待被该对象notify的线程们。当第一个获得 了该对象锁的wait线程运行完毕以后,它会释放掉该对象锁,此时如果该对象没有再次使用notify语句,则即便该对 象已经空闲,其他wait状态等待的线程由于没有得到该对象的通知,会继续阻塞在wait状态,直到这个对象发出一 个notify或notifyAll。这里需要注意:它们等待的是被notify或notifyAll,而不是锁。这与下面的notifyAll()方法 执行后的情况不同。 3、notifyAll() public final native void notifyAll() throws IllegalMonitorStateException 该方法与notify()方法的工作方式相同,重要的一点差异是: notifyAll使所有原来在该对象上wait的线程统统退出wait的状态(即全部被唤醒,不再等待notify或notifyAll,但 由于此时还没有获取到该对象锁,因此还不能继续往下执行),变成等待获取该对象上的锁,一旦该对象锁被释 放(notifyAll线程退出调用了notifyAll的synchronized代码块的时候),他们就会去竞争。如果其中一个线程获得了 该对象锁,它就会继续往下执行,在它退出synchronized代码块,释放锁后,其他的已经被唤醒的线程将会继续竞争 获取该锁,一直进行下去,直到所有被唤醒的线程都执行完毕。 4、wait(long)和wait(long,int) 显然,这两个方法是设置等待超时时间的,后者在超值时间上加上ns,精度也难以达到,因此,该方法很少使用 。对于前者,如果在等待线程接到通知或被中断之前,已经超过了指定的毫秒数,则它通过竞争重新获得锁,并 从wait(long)返回。另外,需要知道,如果设置了超时时间,当wait()返回时,我们不能确定它是因为接到了通 知还是因为超时而返回的,因为wait()方法不会返回任何相关的信息。但一般可以通过设置标志位来判断, 在notify之前改变标志位的值,在wait()方法后读取该标志位的值来判断,当然为了保证notify不被遗漏,我们还 需要另外一个标志位来循环判断是否调用wait()方法。 深入理解:深入理解: 如果线程调用了对象的如果线程调用了对象的wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该 对象的锁。对象的锁。 当有线程调用了对象的当有线程调用了对象的notifyAll()方法(唤醒所有()方法(唤醒所有wait线程)或线程)或notify()方法(只随机唤醒一个()方法(只随机唤醒一个wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。 优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再 次调用次调用wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完 了了synchronized代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。 【Java并发编程】之九:死锁(含代码) 转载请注明出处:转载请注明出处:http://blog.csdn.net/ns_code/article/details/17200937 当线程需要同时持有多个锁时,有可能产生死锁。考虑如下情形: 线程A当前持有互斥所锁lock1,线程B当前持有互斥锁lock2。接下来,当线程A仍然持有lock1时,它试图获 取lock2,因为线程B正持有lock2,因此线程A会阻塞等待线程B对lock2的释放。如果此时线程B在持有lock2的时候 ,也在试图获取lock1,因为线程A正持有lock1,因此线程B会阻塞等待A对lock1的释放。二者都在等待对方所持有锁 的释放,而二者却又都没释放自己所持有的锁,这时二者便会一直阻塞下去。这种情形称为死锁。 下面给出一个两个线程间产生死锁的示例,如下: public class Deadlock extends Object { private String objID; public Deadlock(String id) { objID = id; } public synchronized void checkOther(Deadlock other) { print("entering checkOther()"); try { Thread.sleep(2000); } catch ( InterruptedException x ) { } print("in checkOther() - about to " + "invoke 'other.action()'"); //调用other对象的action方法,由于该方法是同步方法,因此会试图获取other对象的对象锁 other.action(); print("leaving checkOther()"); } public synchronized void action() { print("entering action()"); try { Thread.sleep(500); } catch ( InterruptedException x ) { } print("leaving action()"); } public void print(String msg) { threadPrint("objID=" + objID + " - " + msg); } public static void threadPrint(String msg) { String threadName = Thread.currentThread().getName(); System.out.println(threadName + ": " + msg); } public static void main(String[] args) { final Deadlock obj1 = new Deadlock("obj1"); final Deadlock obj2 = new Deadlock("obj2"); Runnable runA = new Runnable() { public void run() { obj1.checkOther(obj2); } }; Thread threadA = new Thread(runA, "threadA"); threadA.start(); try { Thread.sleep(200); } catch ( InterruptedException x ) { } Runnable runB = new Runnable() { public void run() { obj2.checkOther(obj1); } }; Thread threadB = new Thread(runB, "threadB"); threadB.start(); try { Thread.sleep(5000); } catch ( InterruptedException x ) { } threadPrint("finished sleeping"); threadPrint("about to interrupt() threadA"); threadA.interrupt(); try { Thread.sleep(1000); } catch ( InterruptedException x ) { } threadPrint("about to interrupt() threadB"); threadB.interrupt(); try { Thread.sleep(1000); } catch ( InterruptedException x ) { } threadPrint("did that break the deadlock?"); } } 运行结果如下: 从结果中可以看出,在执行到other.action()时,由于两个线程都在试图获取对方的锁,但对方都没有释放自己 的锁,因而便产生了死锁,在主线程中试图中断两个线程,但都无果。 大部分代码并不容易产生死锁,死锁可能在代码中隐藏相当长的时间,等待不常见的条件地发生,但即使是很小 的概率,一旦发生,便可能造成毁灭性的破坏。避免死锁是一件困难的事,遵循以下原则有助于规避死锁: 1、只在必要的最短时间内持有锁,考虑使用同步语句块代替整个同步方法; 2、尽量编写不在同一时刻需要持有多个锁的代码,如果不可避免,则确保线程持有第二个锁的时间尽量短暂; 3、创建和使用一个大锁来代替若干小锁,并把这个锁用于互斥,而不是用作单个对象的对象级别锁; 【Java并发编程】之八:多线程环境中安全使用集合API(含代码) 转载请注明出处:转载请注明出处:http://blog.csdn.net/ns_code/article/details/17200509 在集合API中,最初设计的Vector和Hashtable是多线程安全的。例如:对于Vector来说,用来添加和删除元素 的方法是同步的。如果只有一个线程与Vector的实例交互,那么,要求获取和释放对象锁便是一种浪费,另外在不 必要的时候如果滥用同步化,也有可能会带来死锁。因此,对于更改集合内容的方法,没有一个是同步化的。集合本 质上是非多线程安全的,当多个线程与集合交互时,为了使它多线程安全,必须采取额外的措施。 在Collections类 中有多个静态方法,它们可以获取通过同步方法封装非同步集合而得到的集合: public static Collection synchronizedCollention(Collection c) public static List synchronizedList(list l) public static Map synchronizedMap(Map m) public static Set synchronizedSet(Set s) public static SortedMap synchronizedSortedMap(SortedMap sm) public static SortedSet synchronizedSortedSet(SortedSet ss) 这些方法基本上返回具有同步集合方法版本的新类。比如,为了创建多线程安全且由ArrayList支持的List,可以 使用如下代码: List list = Collection.synchronizedList(new ArrayList()); 注意,ArrayList实例马上封装起来,不存在对未同步化ArrayList的直接引用(即直接封装匿名实例)。这是一 种最安全的途径。如果另一个线程要直接引用ArrayList实例,它可以执行非同步修改。 下面给出一段多线程中安全遍历集合元素的示例。我们使用Iterator逐个扫描List中的元素,在多线程环境中, 当遍历当前集合中的元素时,一般希望阻止其他线程添加或删除元素。安全遍历的实现方法如下: import java.util.*; public class SafeCollectionIteration extends Object { public static void main(String[] args) { //为了安全起见,仅使用同步列表的一个引用,这样可以确保控制了所有访问 //集合必须同步化,这里是一个List List wordList = Collections.synchronizedList(new ArrayList()); //wordList中的add方法是同步方法,会获取wordList实例的对象锁 wordList.add("Iterators"); wordList.add("require"); wordList.add("special"); wordList.add("handling"); //获取wordList实例的对象锁, //迭代时,阻塞其他线程调用add或remove等方法修改元素 synchronized ( wordList ) { Iterator iter = wordList.iterator(); while ( iter.hasNext() ) { String s = (String) iter.next(); System.out.println("found string: " + s + ", length=" + s.length()); } } } } 这里需要注意的是:在Java语言中,大部分的线程安全类都是相对线程安全的,它能保证对这个对象单独的操作 时线程安全的,我们在调用的时候不需要额外的保障措施,但是对于一些特定的连续调用,就可能需要在调用端使用 额外的同步手段来保证调用的正确性。例如Vector、HashTable、Collections的synchronizedXxxx()方法包装的 集合等。 【Java并发编程】之十一:线程间通信中notify通知的遗漏(含代码 转载请注明出处:转载请注明出处:http://blog.csdn.net/ns_code/article/details/17228213 notify通知的遗漏很容易理解,即threadA还没开始wait的时候,threadB已经notify了,这样,threadB通知是没 有任何响应的,当threadB退出synchronized代码块后,threadA再开始wait,便会一直阻塞等待,直到被别的线程 打断。 遗漏通知的代码遗漏通知的代码 下面给出一段代码演示通知是如何遗漏的,如下: public class MissedNotify extends Object { private Object proceedLock; public MissedNotify() { print("in MissedNotify()"); proceedLock = new Object(); } public void waitToProceed() throws InterruptedException { print("in waitToProceed() - entered"); synchronized ( proceedLock ) { print("in waitToProceed() - about to wait()"); proceedLock.wait(); print("in waitToProceed() - back from wait()"); } print("in waitToProceed() - leaving"); } public void proceed() { print("in proceed() - entered"); synchronized ( proceedLock ) { print("in proceed() - about to notifyAll()"); proceedLock.notifyAll(); print("in proceed() - back from notifyAll()"); } print("in proceed() - leaving"); } private static void print(String msg) { String name = Thread.currentThread().getName(); System.out.println(name + ": " + msg); } public static void main(String[] args) { final MissedNotify mn = new MissedNotify(); Runnable runA = new Runnable() { public void run() { try { //休眠1000ms,大于runB中的500ms, //是为了后调用waitToProceed,从而先notifyAll,后wait, //从而造成通知的遗漏 Thread.sleep(1000); mn.waitToProceed(); } catch ( InterruptedException x ) { x.printStackTrace(); } } }; Thread threadA = new Thread(runA, "threadA"); threadA.start(); Runnable runB = new Runnable() { public void run() { try { //休眠500ms,小于runA中的1000ms, //是为了先调用proceed,从而先notifyAll,后wait, //从而造成通知的遗漏 Thread.sleep(500); mn.proceed(); } catch ( InterruptedException x ) { x.printStackTrace(); } } }; Thread threadB = new Thread(runB, "threadB"); threadB.start(); try { Thread.sleep(10000); } catch ( InterruptedException x ) {} //试图打断wait阻塞 print("about to invoke interrupt() on threadA"); threadA.interrupt(); } } 执行结果如下: 分析:由于threadB在执行mn.proceed()之前只休眠了500ms,而threadA在执行mn.waitToProceed()之前 休眠了1000ms,因此,threadB会先苏醒,继而执行mn.proceed(),获取到proceedLock的对象锁,继而执行其 中的notifyAll(),当退出proceed()方法中的synchronized代码块时,threadA才有机会获取proceedLock的对 象锁,继而执行其中的wait()方法,但此时notifyAll()方法已经执行完毕,threadA便漏掉了threadB的通知, 便会阻塞下去。后面主线程休眠10秒后,尝试中断threadA线程,使其抛出InterruptedException。 修正后的代码修正后的代码 为了修正MissedNotify,需要添加一个boolean指示变量,该变量只能在同步代码块内部访问和修改。修改后的代 码如下: public class MissedNotifyFix extends Object { private Object proceedLock; //该标志位用来指示线程是否需要等待 private boolean okToProceed; public MissedNotifyFix() { print("in MissedNotify()"); proceedLock = new Object(); //先设置为false okToProceed = false; } public void waitToProceed() throws InterruptedException { print("in waitToProceed() - entered"); synchronized ( proceedLock ) { print("in waitToProceed() - entered sync block"); //while循环判断,这里不用if的原因是为了防止早期通知 while ( okToProceed == false ) { print("in waitToProceed() - about to wait()"); proceedLock.wait(); print("in waitToProceed() - back from wait()"); } print("in waitToProceed() - leaving sync block"); } print("in waitToProceed() - leaving"); } public void proceed() { print("in proceed() - entered"); synchronized ( proceedLock ) { print("in proceed() - entered sync block"); //通知之前,将其设置为true,这样即使出现通知遗漏的情况,也不会使线程在wait出阻塞 okToProceed = true; print("in proceed() - changed okToProceed to true"); proceedLock.notifyAll(); print("in proceed() - just did notifyAll()"); print("in proceed() - leaving sync block"); } print("in proceed() - leaving"); } private static void print(String msg) { String name = Thread.currentThread().getName(); System.out.println(name + ": " + msg); } public static void main(String[] args) { final MissedNotifyFix mnf = new MissedNotifyFix(); Runnable runA = new Runnable() { public void run() { try { //休眠1000ms,大于runB中的500ms, //是为了后调用waitToProceed,从而先notifyAll,后wait, Thread.sleep(1000); mnf.waitToProceed(); } catch ( InterruptedException x ) { x.printStackTrace(); } } }; Thread threadA = new Thread(runA, "threadA"); threadA.start(); Runnable runB = new Runnable() { public void run() { try { //休眠500ms,小于runA中的1000ms, //是为了先调用proceed,从而先notifyAll,后wait, Thread.sleep(500); mnf.proceed(); } catch ( InterruptedException x ) { x.printStackTrace(); } } }; Thread threadB = new Thread(runB, "threadB"); threadB.start(); try { Thread.sleep(10000); } catch ( InterruptedException x ) {} print("about to invoke interrupt() on threadA"); threadA.interrupt(); } } 执行结果如下: 注意代码中加了注释的部分,在threadB进行通知之前,先将okToProceed置为true,这样如果threadA将通知 遗漏,那么就不会进入while循环,也便不会执行wait方法,线程也就不会阻塞。如果通知没有被遗漏,wait方法返 回后,okToProceed已经被置为true,下次while循环判断条件不成立,便会退出循环。 这样,通过标志位和wait、notifyAll的配合使用,便避免了通知遗漏而造成的阻塞问题。 总结:在使用线程的等待总结:在使用线程的等待/通知机制时,一般都要配合一个通知机制时,一般都要配合一个boolean变量值(或者其他能够判断真假的条件)变量值(或者其他能够判断真假的条件) ,在,在notify之前改变该之前改变该boolean变量的值,让变量的值,让wait返回后能够退出返回后能够退出while循环(一般都要在循环(一般都要在wait方法外围加一方法外围加一 层层while循环,以防止早期通知),或在通知被遗漏后,不会被阻塞在循环,以防止早期通知),或在通知被遗漏后,不会被阻塞在wait方法处。这样便保证了程序的正确性方法处。这样便保证了程序的正确性 。。 【Java并发编程】之十二:线程间通信中notifyAll造成的早期通 转载请注明出处:转载请注明出处:http://blog.csdn.net/ns_code/article/details/17229601 如果线程在等待时接到通知,但线程等待的条件还不满足,此时,线程接到的就是早期通知,如果条件满足的时 间很短,但很快又改变了,而变得不再满足,这时也将发生早期通知。这种现象听起来很奇怪,下面通过一个示例程 序来说明问题。 很简单,两个线程等待删除List中的元素,同时另外一个线程正要向其中添加项目。代码如下: import java.util.*; public class EarlyNotify extends Object { private List list; public EarlyNotify() { list = Collections.synchronizedList(new LinkedList()); } public String removeItem() throws InterruptedException { print("in removeItem() - entering"); synchronized ( list ) { if ( list.isEmpty() ) { //这里用if语句会发生危险 print("in removeItem() - about to wait()"); list.wait(); print("in removeItem() - done with wait()"); } //删除元素 String item = (String) list.remove(0); print("in removeItem() - leaving"); return item; } } public void addItem(String item) { print("in addItem() - entering"); synchronized ( list ) { //添加元素 list.add(item); print("in addItem() - just added: '" + item + "'"); //添加后,通知所有线程 list.notifyAll(); print("in addItem() - just notified"); } print("in addItem() - leaving"); } private static void print(String msg) { String name = Thread.currentThread().getName(); System.out.println(name + ": " + msg); } public static void main(String[] args) { final EarlyNotify en = new EarlyNotify(); Runnable runA = new Runnable() { public void run() { try { String item = en.removeItem(); print("in run() - returned: '" + item + "'"); } catch ( InterruptedException ix ) { print("interrupted!"); } catch ( Exception x ) { print("threw an Exception!!!\n" + x); } } }; Runnable runB = new Runnable() { public void run() { en.addItem("Hello!"); } }; try { //启动第一个删除元素的线程 Thread threadA1 = new Thread(runA, "threadA1"); threadA1.start(); Thread.sleep(500); //启动第二个删除元素的线程 Thread threadA2 = new Thread(runA, "threadA2"); threadA2.start(); Thread.sleep(500); //启动增加元素的线程 Thread threadB = new Thread(runB, "threadB"); threadB.start(); Thread.sleep(10000); // wait 10 seconds threadA1.interrupt(); threadA2.interrupt(); } catch ( InterruptedException x ) {} } } 执行结果如下: 分析:首先启动threadA1,threadA1在removeItem()中调用wait(),从而释放list上的对象锁。再 过500ms,启动threadA2,threadA2调用removeItem(),获取list上的对象锁,也发现列表为空,从而 在wait()方法处阻塞,释放list上的对象锁。再过500ms后,启动threadB,并调用addItem,获得list上的对象锁 ,并在list中添加一个元素,同时用notifyAll通知所有线程。 threadA1和threadA2都从wait()返回,等待获取list对象上的对象锁,并试图从列表中删除添加的元素,这就 会产生麻烦,只有其中一个操作能成功。假设threadA1获取了list上的对象锁,并删除元素成功,在退 出synchronized代码块时,它便会释放list上的对象锁,此时threadA2便会获取list上的对象锁,会继续删除list中的 元素,但是list已经为空了,这便会抛出IndexOutOfBoundsException。 要避免以上问题只需将wait外围的if语句改为while循环即可,这样当list为空时,线程便会继续等待,而不会继续 去执行删除list中元素的代码。 修改后的执行结果如下: 总结:在使用线程的等待总结:在使用线程的等待/通知机制时,一般都要在通知机制时,一般都要在while循环中调用循环中调用wait()方法,满足条件时,才让()方法,满足条件时,才让while 循环退出,这样一般也要配合使用一个循环退出,这样一般也要配合使用一个boolean变量(或其他能判断真假的条件,如本文中的变量(或其他能判断真假的条件,如本文中的list.isEmpty())) ,满足,满足while循环的条件时,进入循环的条件时,进入while循环,执行循环,执行wait()方法,不满足()方法,不满足while循环的条件时,跳出循环,执行循环的条件时,跳出循环,执行 后面的代码。后面的代码。 【Java并发编程】之十三:生产者—消费者模型(含代码) 转载请注明出处:转载请注明出处:http://blog.csdn.net/ns_code/article/details/17249321 生产者消费者问题是线程模型中的经典问题:生产者和消费者在同一时间段内共用同一存储空间,生产者向空间 里生产数据,而消费者取走数据。 这里实现如下情况的生产--消费模型: 生产者不断交替地生产两组数据“姓名--1 --> 内容--1”,“姓名--2--> 内容--2”,消费者不断交替地取得这两组数据 ,这里的“姓名--1”和“姓名--2”模拟为数据的名称,“内容--1 ”和“内容--2 ”模拟为数据的内容。 由于本程序中牵扯到线程运行的不确定性,因此可能会出现以下问题: 1、假设生产者线程刚向数据存储空间添加了数据的名称,还没有加入该信息的内容,程序就切换到了消费者线程 ,消费者线程将把信息的名称和上一个信息的内容联系在一起; 2、生产者生产了若干次数据,消费者才开始取数据,或者是,消费者取完一次数据后,还没等生产者放入新的 数据,又重复取出了已取过的数据。 问题1很明显要靠同步来解决,问题2则需要线程间通信,生产者线程放入数据后,通知消费者线程取出数据,消 费者线程取出数据后,通知生产者线程生产数据,这里用wait/notify机制来实现。 详细的实现代码如下: class Info{ // 定义信息类 private String name = "name";//定义name属性,为了与下面set的name属性区别开 private String content = "content" ;// 定义content属性,为了与下面set的content属性区别开 private boolean flag = true ; // 设置标志位,初始时先生产 public synchronized void set(String name,String content){ while(!flag){ try{ super.wait() ; }catch(InterruptedException e){ e.printStackTrace() ; } } this.setName(name) ; // 设置名称 try{ Thread.sleep(300) ; }catch(InterruptedException e){ e.printStackTrace() ; } this.setContent(content) ; // 设置内容 flag = false ; // 改变标志位,表示可以取走 super.notify(); } public synchronized void get(){ while(flag){ try{ super.wait() ; }catch(InterruptedException e){ e.printStackTrace() ; } } try{ Thread.sleep(300) ; }catch(InterruptedException e){ e.printStackTrace() ; } System.out.println(this.getName() + " --> " + this.getContent()) ; flag = true ; // 改变标志位,表示可以生产 super.notify(); } public void setName(String name){ this.name = name ; } public void setContent(String content){ this.content = content ; } public String getName(){ return this.name ; } public String getContent(){ return this.content ; } } class Producer implements Runnable{ // 通过Runnable实现多线程 private Info info = null ; // 保存Info引用 public Producer(Info info){ this.info = info ; } public void run(){ boolean flag = true ; // 定义标记位 for(int i=0;i<10;i++){ if(flag){ this.info.set("姓名--1","内容--1") ; // 设置名称 flag = false ; }else{ this.info.set("姓名--2","内容--2") ; // 设置名称 flag = true ; } } } } class Consumer implements Runnable{ private Info info = null ; public Consumer(Info info){ this.info = info ; } public void run(){ for(int i=0;i<10;i++){ this.info.get() ; } } } public class ThreadCaseDemo03{ public static void main(String args[]){ Info info = new Info(); // 实例化Info对象 Producer pro = new Producer(info) ; // 生产者 Consumer con = new Consumer(info) ; // 消费者 new Thread(pro).start() ; //启动了生产者线程后,再启动消费者线程 try{ Thread.sleep(500) ; }catch(InterruptedException e){ e.printStackTrace() ; } new Thread(con).start() ; } } 执行结果如下: 另外,在run方法中,二者循环的次数要相同,否则,当一方的循环结束时,另一方的循环依然继续,它会阻塞 在wait()方法处,而等不到对方的notify通知。 【Java并发编程】之十四:图文讲述同步的另一个重要功能:内存可见性 转载请注明出处:转载请注明出处:http://blog.csdn.net/ns_code/article/details/17288243 加锁(synchronized同步)的功能不仅仅局限于互斥行为,同时还存在另外一个重要的方面:内存可见性。我们 不仅希望防止某个线程正在使用对象状态而另一个线程在同时修改该状态,而且还希望确保当一个线程修改了对象状 态后,其他线程能够看到该变化。而线程的同步恰恰也能够实现这一点。 内置锁可以用于确保某个线程以一种可预测的方式来查看另一个线程的执行结果。为了确保所有的线程都能看到 共享变量的最新值,可以在所有执行读操作或写操作的线程上加上同一把锁。下图示例了同步的可见性保证。 当线程A执行某个同步代码块时,线程B随后进入由同一个锁保护的同步代码块,这种情况下可以保证,当锁被释 放前,A看到的所有变量值(锁释放前,A看到的变量包括y和x)在B获得同一个锁后同样可以由B看到。换句话说, 当线程B执行由锁保护的同步代码块时,可以看到线程A之前在同一个锁保护的同步代码块中的所有操作结果。如果 在线程A unlock M之后,线程B才进入lock M,那么线程B都可以看到线程A unlock M之前的操作,可以得到i=1,j=1 。如果在线程B unlock M之后,线程A才进入lock M,那么线程B就不一定能看到线程A中的操作,因此j的值就不一 定是1。 现在考虑如下代码: public class MutableInteger { private int value; public int get(){ return value; } public void set(int value){ this.value = value; } } 以上代码中,get和set方法都在没有同步的情况下访问value。如果value被多个线程共享,假如某个线程调用 了set,那么另一个正在调用get的线程可能会看到更新后的value值,也可能看不到。 通过对set和get方法进行同步,可以使MutableInteger成为一个线程安全的类,如下: public class SynchronizedInteger { private int value; public synchronized int get(){ return value; } public synchronized void set(int value){ this.value = value; } } 对set和get方法进行了同步,加上了同一把对象锁,这样get方法可以看到set方法中value值的变化,从而每次通 过get方法取得的value的值都是最新的value值。 【Java并发编程】之十六:深入Java内存模型——happenbef 转载请注明出处:转载请注明出处:http://blog.csdn.net/ns_code/article/details/17348313 happen—before规则介绍规则介绍 Java语言中有一个“先行发生”(happen—before)的规则,它是Java内存模型中定义的两项操作之间的偏序关系 ,如果操作A先行发生于操作B,其意思就是说,在发生操作B之前,操作A产生的影响都能被操作B观察到,“影响” 包括修改了内存中共享变量的值、发送了消息、调用了方法等,它与时间上的先后发生基本没有太大关系。这个原则 特别重要,它是判断数据是否存在竞争、线程是否安全的主要依据。 举例来说,假设存在如下三个线程,分别执行对应的操作: --------------------------------------------------------------------------- 线程A中执行如下操作:i=1 线程B中执行如下操作:j=i 线程C中执行如下操作:i=2 --------------------------------------------------------------------------- 假设线程A中的操作”i=1“ happen—before线程B中的操作“j=i”,那么就可以保证在线程B的操作执行后,变量j的值 一定为1,即线程B观察到了线程A中操作“i=1”所产生的影响;现在,我们依然保持线程A和线程B之间的happen —before关系,同时线程C出现在了线程A和线程B的操作之间,但是C与B并没有happen—before关系,那么j的值就 不确定了,线程C对变量i的影响可能会被线程B观察到,也可能不会,这时线程B就存在读取到不是最新数据的风险 ,不具备线程安全性。 下面是Java内存模型中的八条可保证happen—before的规则,它们无需任何同步器协助就已经存在,可以在编码 中直接使用。如果两个操作之间的关系不在此列,并且无法从下列规则推导出来的话,它们就没有顺序性保障,虚拟 机可以对它们进行随机地重排序。 1、程序次序规则:在一个单独的线程中,按照程序代码的执行流顺序,(时间上)先执行的操作happen —before(时间上)后执行的操作。 2、管理锁定规则:一个unlock操作happen—before后面(时间上的先后顺序,下同)对同一个锁的lock操作。 3、volatile变量规则:对一个volatile变量的写操作happen—before后面对该变量的读操作。 4、线程启动规则:Thread对象的start()方法happen—before此线程的每一个动作。 5、线程终止规则:线程的所有操作都happen—before对此线程的终止检测,可以通过Thread.join()方法 结束、Thread.isAlive()的返回值等手段检测到线程已经终止执行。 6、线程中断规则:对线程interrupt()方法的调用happen—before发生于被中断线程的代码检测到中断时事件 的发生。 7、对象终结规则:一个对象的初始化完成(构造函数执行结束)happen—before它的finalize()方法的开始。 8、传递性:如果操作A happen—before操作B,操作B happen—before操作C,那么可以得出A happen—before 操作C。 时间上先后顺序和时间上先后顺序和happen—before原则原则 ”时间上执行的先后顺序“与”happen—before“之间有何不同呢? 1、首先来看操作首先来看操作A在时间上先与操作在时间上先与操作B发生,是否意味着操作发生,是否意味着操作A happen—before操作操作B?? 一个常用来分析的例子如下: private int value = 0; public int get(){ return value; } public void set(int value){ this.value = value; } } 假设存在线程A和线程B,线程A先(时间上的先)调用了setValue(3)操作,然后(时间上的后)线程B调用了 同一对象的getValue()方法,那么线程B得到的返回值一定是3吗? 对照以上八条happen—before规则,发现没有一条规则适合于这里的value变量,从而我们可以判定线程A中 的setValue(3)操作与线程B中的getValue()操作不存在happen—before关系。因此,尽管线程A的setValue(3 )在操作时间上先于操作B的getvalue(),但无法保证线程B的getValue()操作一定观察到了线程A 的setValue(3)操作所产生的结果,也即是getValue()的返回值不一定为3(有可能是之前setValue所设置 的值)。这里的操作不是线程安全的。 因此,”一个操作时间上先发生于另一个操作“并不代表”一个操作happen—before另一个操作“。 解决方法:可以将setValue(int)方法和getValue()方法均定义为synchronized方法,也可以把value定义 为volatile变量(value的修改并不依赖value的原值,符合volatile的使用场景),分别对应happen—before规则的第 2和第3条。注意,只将setValue(int)方法和getvalue()方法中的一个定义为synchronized方法是不行的,必须 对同一个变量的所有读写同步,才能保证不读取到陈旧的数据,仅仅同步读或写是不够的 。 2、其次来看,其次来看,操作操作A happen—before操作操作B,,是否意味着是否意味着操作操作A在时间上先与操作在时间上先与操作B发生?发生? 看有如下代码: x = 1; y = 2; 假设同一个线程执行上面两个操作:操作A:x=1和操作B:y=2。根据happen—before规则的第1条,操作A happen—before 操作B,但是由于编译器的指令重排序(Java语言规范规定了JVM线程内部维持顺序化语义,也就 是说只要程序的最终结果等同于它在严格的顺序化环境下的结果,那么指令的执行顺序就可能与代码的顺序不一致。 这个过程通过叫做指令的重排序。指令重排序存在的意义在于:JVM能够根据处理器的特性(CPU的多级缓存系统 、多核处理器等)适当的重新排序机器指令,使机器指令更符合CPU的执行特点,最大限度的发挥机器的性能。在没 有同步的情况下,编译器、处理器以及运行时等都可能对操作的执行顺序进行一些意想不到的调整)等原因,操作A 在时间上有可能后于操作B被处理器执行,但这并不影响happen—before原则的正确性。 因此,”一个操作happen—before另一个操作“并不代表”一个操作时间上先发生于另一个操作“。 最后,一个操作和另一个操作必定存在某个顺序,要么一个操作或者是先于或者是后于另一个操作,或者与两个 操作同时发生。同时发生是完全可能存在的,特别是在多CPU的情况下。而两个操作之间却可能没有happen-before 关系,也就是说有可能发生这样的情况,操作A不happen-before操作B,操作B也不happen-before操作A,用数学 上的术语happen-before关系是个偏序关系。两个存在happen-before关系的操作不可能同时发生,一个操作A happen-before操作B,它们必定在时间上是完全错开的,这实际上也是同步的语义之一(独占访问)。 利用利用happen—before规则分析规则分析DCL DCL即双重检查加锁,关于单例模式的DCL机制,可以参看 :http://blog.csdn.net/ns_code/article/details/17359719一文,这里不再详细介绍。下面是一个典型的在单例 模式中使用DCL的例子: public class LazySingleton { private int someField; private static LazySingleton instance; private LazySingleton() { this.someField = new Random().nextInt(200)+1; // (1) } public static LazySingleton getInstance() { if (instance == null) { // (2) synchronized(LazySingleton.class) { // (3) if (instance == null) { // (4) instance = new LazySingleton(); // (5) } } } return instance; // (6) } public int getSomeField() { return this.someField; // (7) } } 这里得到单一的instance实例是没有问题的,问题的关键在于尽管得到了Singleton的正确引用,但是却有可能访 问到其成员变量的不正确值。具体来说Singleton.getInstance().getSomeField()有可能返回someField的默认值0。 如果程序行为正确的话,这应当是不可能发生的事,因为在构造函数里设置的someField的值不可能为0。为也说明 这种情况理论上有可能发生,我们只需要说明语句(1)和语句(7)并不存在happen-before关系。 假设线程Ⅰ是初次调用getInstance()方法,紧接着线程Ⅱ也调用了getInstance()方法和getSomeField()方法,我 们要说明的是线程Ⅰ的语句(1)并不happen-before线程Ⅱ的语句(7)。线程Ⅱ在执行getInstance()方法的语句(2)时 ,由于对instance的访问并没有处于同步块中,因此线程Ⅱ可能观察到也可能观察不到线程Ⅰ在语句(5)时对instance 的写入,也就是说instance的值可能为空也可能为非空。我们先假设instance的值非空,也就观察到了线程Ⅰ 对instance的写入,这时线程Ⅱ就会执行语句(6)直接返回这个instance的值,然后对这个instance调 用getSomeField()方法,该方法也是在没有任何同步情况被调用,因此整个线程Ⅱ的操作都是在没有同步的情况下 调用 ,这时我们便无法利用上述8条happen-before规则得到线程Ⅰ的操作和线程Ⅱ的操作之间的任何有效 的happen-before关系(主要考虑规则的第2条,但由于线程Ⅱ没有在进入synchronized块,因此不存在lock 与unlock锁的问题),这说明线程Ⅰ的语句(1)和线程Ⅱ的语句(7)之间并不存在happen-before关系,这就意味着线 程Ⅱ在执行语句(7)完全有可能可能观测不到线程Ⅰ在语句(1)处对someFiled写入的值,这就是DCL的问题所在。很荒谬 ,是吧?DCL原本是为了逃避同步,它达到了这个目的,也正是因为如此,它最终受到惩罚,这样的程序存在严重 的bug,虽然这种bug被发现的概率绝对比中彩票的概率还要低得多,而且是转瞬即逝,更可怕的是,即使发生了你 也不会想到是DCL所引起的。 前面我们说了,线程Ⅱ在执行语句(2)时也有可能观察空值,如果是种情况,那么它需要进入同步块,并执行语 句(4)。在语句(4)处线程Ⅱ还能够读到instance的空值吗?不可能。这里因为这时对instance的写和读都是发生在同 一个锁确定的同步块中,这时读到的数据是最新的数据。为也加深印象,我再用happen-before规则分析一遍。线程 Ⅱ在语句(3)处会执行一个lock操作,而线程Ⅰ在语句(5)后会执行一个unlock操作,这两个操作都是针对同一个锁-- Singleton.class,因此根据第2条happen-before规则,线程Ⅰ的unlock操作happen-before线程Ⅱ的lock操作,再利 用单线程规则,线程Ⅰ的语句(5) -> 线程Ⅰ的unlock操作,线程Ⅱ的lock操作 -> 线程Ⅱ的语句(4),再根据传递规则 ,就有线程Ⅰ的语句(5) -> 线程Ⅱ的语句(4),也就是说线程Ⅱ在执行语句(4)时能够观测到线程Ⅰ在语句(5)时 对Singleton的写入值。接着对返回的instance调用getSomeField()方法时,我们也能得到线程Ⅰ的语句(1) -> 线程Ⅱ 的语句(7)(由于线程Ⅱ有进入synchronized块,根据规则2可得),这表明这时getSomeField能够得到正确的值。 但是仅仅是这种情况的正确性并不妨碍DCL的不正确性,一个程序的正确性必须在所有的情况下的行为都是正确的, 而不能有时正确,有时不正确。 对DCL的分析也告诉我们一条经验原则:对引用(包括对象引用和数组引用)的非同步访问,即使得到该引用的最 新值,却并不能保证也能得到其成员变量(对数组而言就是每个数组元素)的最新值。 解决方案:解决方案: 1、最简单而且安全的解决方法是使用static内部类的思想,它利用的思想是:一个类直到被使用时才被初始化, 而类初始化的过程是非并行的,这些都有JLS保证。 如下述代码: public class Singleton { private Singleton() {} // Lazy initialization holder class idiom for static fields private static class InstanceHolder { private static final Singleton instance = new Singleton(); } public static Singleton getSingleton() { return InstanceHolder.instance; } } 2、另外,可以将instance声明为volatile,即 private volatile static LazySingleton instance; 这样我们便可以得到,线程Ⅰ的语句(5) -> 语线程Ⅱ的句(2),根据单线程规则,线程Ⅰ的语句(1) -> 线程Ⅰ的语 句(5)和语线程Ⅱ的句(2) -> 语线程Ⅱ的句(7),再根据传递规则就有线程Ⅰ的语句(1) -> 语线程Ⅱ的句(7),这表示线 程Ⅱ能够观察到线程Ⅰ在语句(1)时对someFiled的写入值,程序能够得到正确的行为。 注:注: 1、、volatile屏蔽指令重排序的语义在屏蔽指令重排序的语义在JDK1.5中才被完全修复,此前的中才被完全修复,此前的JDK中及时将变量声明为中及时将变量声明为volatile,也,也 仍然不能完全避免重排序所导致的问题(主要是仍然不能完全避免重排序所导致的问题(主要是volatile变量前后的代码仍然存在重排序问题),这点也是变量前后的代码仍然存在重排序问题),这点也是 在在JDK1.5之前的之前的Java中无法安全使用中无法安全使用DCL来实现单例模式的原因。来实现单例模式的原因。 2、、把把volatile写和写和volatile读这两个操作综合起来看,在读线程读这两个操作综合起来看,在读线程B读一个读一个volatile变量后,写线程变量后,写线程A在写这在写这 个个volatile变量之前,所有可见的共享变量的值都将立即变得对读线程变量之前,所有可见的共享变量的值都将立即变得对读线程B可见。可见。 3、 在java5之前对final字段的同步语义和其它变量没有什么区别,在java5中,final变量一旦在构造函数中设置完 成(前提是在构造函数中没有泄露this引用),其它线程必定会看到在构造函数中设置的值。而DCL的问题正好在于看 到对象的成员变量的默认值,因此我们可以将LazySingleton的someField变量设置成final,这样在java5中就能够正 确运行了。 参考资料:参考资料:http://www.iteye.com/topic/260515/ 《深入理解Java虚拟机——JVM高级特性与最佳实践》第12章 【Java并发编程】之十五:并发编程中实现内存可见的两种方法比较:加锁 转载请注明出处:转载请注明出处:http://blog.csdn.net/ns_code/article/details/17290021 在http://blog.csdn.net/ns_code/article/details/17288243这篇博文中,讲述了通过同步实现内存可见性的 方法,在http://blog.csdn.net/ns_code/article/details/17101369这篇博文中,讲述了通过volatile变量实现内存 可见性的方法,这里比较下二者的区别。 1、volatile变量是一种稍弱的同步机制在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞, 因此volatile变量是一种比synchronized关键字更轻量级的同步机制。 2、从内存可见性的角度看,写入volatile变量相当于退出同步代码块,而读取volatile变量相当于进入同步代码块 。 3、在代码中如果过度依赖volatile变量来控制状态的可见性,通常会比使用锁的代码更脆弱,也更难以理解。仅 当volatile变量能简化代码的实现以及对同步策略的验证时,才应该使用它。一般来说,用同步机制会更安全些。 4、加锁机制(即同步机制)既可以确保可见性又可以确保原子性,而volatile变量只能确保可见性,原因是声明 为volatile的简单变量如果当前值与该变量以前的值相关,那么volatile关键字不起作用,也就是说如下的表达式都不 是原子操作:“count++”、“count = count+1”。 当且仅当满足以下所有条件时,才应该使用volatile变量: 1、对变量的写入操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值。 2、该变量没有包含在具有其他变量的不变式中。 总结:在需要同步的时候,第一选择应该是总结:在需要同步的时候,第一选择应该是synchronized关键字,这是最安全的方式,关键字,这是最安全的方式,尝试其他任何方式都是尝试其他任何方式都是 有风险的。有风险的。尤其在、尤其在、jdK1.5之后,对之后,对synchronized同步机制做了很多优化,如:自适应的自旋锁、锁粗化、锁同步机制做了很多优化,如:自适应的自旋锁、锁粗化、锁 消除、轻量级锁等,使得它的性能明显有了很大的提升。消除、轻量级锁等,使得它的性能明显有了很大的提升。 【Java并发编程】之十七:深入Java内存模型—内存操作规则总结 转载请注明出处:转载请注明出处:http://blog.csdn.net/ns_code/article/details/17377197 主内存与工作内存主内存与工作内存 Java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变 量这样的底层细节。此处的变量主要是指共享变量,存在竞争问题的变量。Java内存模型规定所有的变量都存储在 主内存中,而每条线程还有自己的工作内存,线程的工作内存中保存了该线程使用到的变量的主内存副本拷贝,线程 对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量(根据Java虚拟机 规范的规定,volatile变量依然有共享内存的拷贝,但是由于它特殊的操作顺序性规定——从工作内存中读写数据前 ,必须先将主内存中的数据同步到工作内存中,所有看起来如同直接在主内存中读写访问一般,因此这里的描述对 于volatile也不例外)。不同线程之间也无法直接访问对方工作内存中的变量,线程间变量值得传递均需要通过主内 存来完成。 内存间交互操作内存间交互操作 Java内存模型中定义了以下8中操作来完成主内存与工作内存之间交互的实现细节: 1、luck(锁定):作用于主内存的变量,它把一个变量标示为一条线程独占的状态。 2、unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他 线程锁定。 3、read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到工作内存中,以便随后的load动作 使用。 4、load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。 5、use(使用):作用于工作内存的变量,它把工作内存中的一个变量的值传递给执行引擎,每当虚拟机遇到一 个需要使用到变量的值得字节码指令时将会执行这个操作。 6、assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机 遇到一个给变量赋值的字节码指令时执行这个操作。 7、store(存储):作用于工作内存的变量,它把工作内存中的一个变量的值传递到主内存中,以便随后的write 操作使用。 8、write(写入):作用于主内存的变量,它把store操作从工作内存中得到的变量值放入主内存的变量中。 Java内存模型还规定了执行上述8种基本操作时必须满足如下规则: 1、不允许read和load、store和write操作之一单独出现,以上两个操作必须按顺序执行,但没有保证必须连续 执行,也就是说,read与load之间、store与write之间是可插入其他指令的。 2、不允许一个线程丢弃它的最近的assign操作,即变量在工作内存中改变了之后必须把该变化同步回主内存。 3、不允许一个线程无原因地(没有发生过任何assign操作)把数据从线程的工作内存同步回主内存中。 4、一个新的变量只能从主内存中“诞生”,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量 ,换句话说就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作。 5、一个变量在同一个时刻只允许一条线程对其执行lock操作,但lock操作可以被同一个条线程重复执行多次,多 次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。 6、如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执 行load或assign操作初始化变量的值。 7、如果一个变量实现没有被lock操作锁定,则不允许对它执行unlock操作,也不允许去unlock一个被其他线程锁 定的变量。 8、对一个变量执行unlock操作之前,必须先把此变量同步回主内存(执行store和write操作)。 volatile型变量的特殊规则型变量的特殊规则 Java内存模型对volatile专门定义了一些特殊的访问规则,当一个变量被定义成volatile之后,他将具备两种特性: 1、保证此变量对所有线程的可见性。这里不具体解释了。需要注意,需要注意,volatile变量的写操作除了对它本身的读变量的写操作除了对它本身的读 操作可见外,操作可见外,volatile写操作之前的所有共享变量均对写操作之前的所有共享变量均对volatile读操作之后的操作可见,另外注意其适用场景读操作之后的操作可见,另外注意其适用场景 ,,详 见http://blog.csdn.net/ns_code/article/details/17290021和http://blog.csdn.net/ns_code/article/details/17101369 这两篇博文。 2、禁止指令重排序优化。普通的变量仅仅会保证在该方法的执行过程中所有依赖赋值结果的地方都能获得正确的 结果,而不能保证变量赋值操作的顺序与程序中的执行顺序一致,在单线程中,我们是无法感知这一点的。 补充:Java语言规范规定了JVM线程内部维持顺序化语义,也就是说只要程序的最终结果等同于它在严格的顺序 化环境下的结果,那么指令的执行顺序就可能与代码的顺序不一致,这个过程通过叫做指令的重排序。指令重排序存 在的意义在于:JVM能够根据处理器的特性(CPU的多级缓存系统、多核处理器等)适当的重新排序机器指令,使 机器指令更符合CPU的执行特点,最大限度的发挥机器的性能。在没有同步的情况下,编译器、处理器以及运行时等 都可能对操作的执行顺序进行一些意想不到的调整 final域域 final类型的域是不能修改的,除了这一点外,在Java内存模型中,final域还有着特殊的语义,final域能确保初始 化过程的安全性,从而可以不受限制地访问不可变对象,并在共享这些对象时无须同步。具体而言,就是被final修饰 的字段在构造器中一旦被初始化完成,并且构造器没有把“this”的引用传递出去(this引用逃逸是一件很危险的事情 ,其他线程有可能通过这个引用访问到“初始化了一半”的对象),那么在其他线程中就能看到final字段的值,而且 其外、外部可见状态永远也不会改变。它所带来的安全性是最简单最纯粹的。 long和和double型变量的特殊规则型变量的特殊规则 Java内存模型要求lock、unlock、read、load、assign、use、store和write这8个操作都具有原子性,但是对 于64位的数据类型long和double,在模型中特别定义了一条宽松的规定:允许虚拟机将没有被volatile修饰的64位数 据的读写操作划分为两次32位的操作来进行。这样,如果有多个线程共享一个未被声明为volatile的long或double类 型的变量,并且同时对它们进行读取和修改操作,那么某些线程可能会读到一个既非原值,也非其他线程修改值得代 表了“半个变量”的数值。不过这种读取到“半个变量”的情况非常罕见,因为Java内存模型虽然允许虚拟机不把long 和double变量的读写实现成原子操作,但允许迅疾选择把这些操作实现为具有原子性的操作,而且还“强烈建议”虚拟 机这样实现。目前各种平台下的商用虚拟机几乎都选择吧64位数据的读写操作作为原子操作来对待,因此在编码时 ,不需要将long和double变量专门声明为volatile。 参考资料:参考资料:http://blog.csdn.net/vking_wang/article/details/8574376#t2 《深入理解Java虚拟机——JVM高级特性与最佳实践》第12章 【Java并发编程】之十八:第五篇中volatile意外问题的正确分析 转载请注明出处:转载请注明出处:http://blog.csdn.net/ns_code/article/details/17382679 在《《Java并发编程学习笔记之五:并发编程学习笔记之五:volatile变量修饰符变量修饰符—意料之外的问题》意料之外的问题》一文中遗留了一个问题,就 是volatile只修饰了missedIt变量,而没修饰value变量,但是在线程读取value的值的时候,也读到的是最新的数据 。但是在网上查了很多资料都无果,看来很多人对volatile的规则并不是太清晰,或者说只停留在很表面的层次,一 知半解。 这两天看《深入Java虚拟机——JVM高级特性与最佳实践》第12章:Java内存模型与线程,并在网上查阅了Java 内存模型相关资料,学到了不少东西,尤其在看这篇这篇文章的volatile部分的讲解之后,算是确定了问题出现的原因。 首先明确一点:假如有两个线程分别读写volatile变量时,线程A写入了某volatile变量,线程B在读取该volatile变 量时,便能看到线程A对该volatile变量的写入操作,关键在这里,它不仅会看到对该关键在这里,它不仅会看到对该volatile变量的写入操作,变量的写入操作,A 线程在写线程在写volatile变量之前所有可见的共享变量,在变量之前所有可见的共享变量,在B线程读同一个线程读同一个volatile变量后,都将立即变得对变量后,都将立即变得对B线程可见线程可见 。。 回过头来看文章中出现的问题,由于程序中volatile变量missedIt的写入操作在value变量写入操作之后,而且根 据volatile规则,又不能重排序,因此,在线程B读取由线程A改变后的missedIt之后,它之前的value变量在线程A的 改变也对线程B变得可见了。 我们颠倒一下value=50和missedIt=true这两行代码试下,即missedIt=true在前,value=50在后,这样便会得到 我们想要的结果:value值的改变不会被看到。 这应该是JDK1.2之后对volatile规则做了一些修订的结果。 修改后的代码如下: public class Volatile extends Object implements Runnable { //value变量没有被标记为volatile private int value; //missedIt变量被标记为volatile private volatile boolean missedIt; //creationTime不需要声明为volatile,因为代码执行中它没有发生变化 private long creationTime; public Volatile() { value = 10; missedIt = false; //获取当前时间,亦即调用Volatile构造函数时的时间 creationTime = System.currentTimeMillis(); } public void run() { print("entering run()"); //循环检查value的值是否不同 while ( value < 20 ) { //如果missedIt的值被修改为true,则通过break退出循环 if ( missedIt ) { //进入同步代码块前,将value的值赋给currValue int currValue = value; //在一个任意对象上执行同步语句,目的是为了让该线程在进入和离开同步代码块时, //将该线程中的所有变量的私有拷贝与共享内存中的原始值进行比较, //从而发现没有用volatile标记的变量所发生的变化 Object lock = new Object(); synchronized ( lock ) { //不做任何事 } //离开同步代码块后,将此时value的值赋给valueAfterSync int valueAfterSync = value; print("in run() - see value=" + currValue +", but rumor has it that it changed!"); print("in run() - valueAfterSync=" + valueAfterSync); break; } } print("leaving run()"); } public void workMethod() throws InterruptedException { print("entering workMethod()"); print("in workMethod() - about to sleep for 2 seconds"); Thread.sleep(2000); //仅在此改变value的值 missedIt = true; // value = 50; print("in workMethod() - just set value=" + value); print("in workMethod() - about to sleep for 5 seconds"); Thread.sleep(5000); //仅在此改变missedIt的值 // missedIt = true; value = 50; print("in workMethod() - just set missedIt=" + missedIt); print("in workMethod() - about to sleep for 3 seconds"); Thread.sleep(3000); print("leaving workMethod()"); } /* *该方法的功能是在要打印的msg信息前打印出程序执行到此所化去的时间,以及打印msg的代码所在的线程 */ private void print(String msg) { //使用java.text包的功能,可以简化这个方法,但是这里没有利用这一点 long interval = System.currentTimeMillis() - creationTime; String tmpStr = " " + ( interval / 1000.0 ) + "000"; int pos = tmpStr.indexOf("."); String secStr = tmpStr.substring(pos - 2, pos + 4); String nameStr = " " + Thread.currentThread().getName(); nameStr = nameStr.substring(nameStr.length() - 8, nameStr.length()); System.out.println(secStr + " " + nameStr + ": " + msg); } public static void main(String[] args) { try { //通过该构造函数可以获取实时时钟的当前时间 Volatile vol = new Volatile(); //稍停100ms,以让实时时钟稍稍超前获取时间,使print()中创建的消息打印的时间值大于0 Thread.sleep(100); Thread t = new Thread(vol); t.start(); //休眠100ms,让刚刚启动的线程有时间运行 Thread.sleep(100); //workMethod方法在main线程中运行 vol.workMethod(); } catch ( InterruptedException x ) { System.err.println("one of the sleeps was interrupted"); } } } 执行结果如下: 很明显,这其实并不符合使用很明显,这其实并不符合使用volatile的第二个条件:的第二个条件:该变量要没有包含在具有其他变量的不变式中该变量要没有包含在具有其他变量的不变式中。因此,。因此, 在这里使用在这里使用volatile是不安全的。是不安全的。 附上一篇讲述附上一篇讲述volatile关键字正确使用的很好的文章:关键字正确使用的很好的文章:http://www.ibm.com/developerworks/cn/java/j- jtp06197.html 【Java并发编程】之十九:并发新特性—Executor框架与线程池( 转载请注明出处:转载请注明出处:http://blog.csdn.net/ns_code/article/details/17465497 Executor框架简介框架简介 在Java 5之后,并发编程引入了一堆新的启动、调度和管理线程的API。Executor框架便是Java 5中引入的,其内部使用了线程池机制,它 在java.util.cocurrent 包下,通过该框架来控制线程的启动、执行和关闭,可以简化并发编程的操作。因此,在Java 5之后,通过Executor来启动线 程比使用Thread的start方法更好,除了更易管理,效率更好(用线程池实现,节约开销)外,还有关键的一点:有助于避免this逃逸问题——如果我 们在构造器中启动一个线程,因为另一个任务可能会在构造器结束之前开始执行,此时可能会访问到初始化了一半的对象用Executor在构造器中。 Executor框架包括:线程池,Executor,Executors,ExecutorService,CompletionService,Future,Callable等。 Executor接口中之定义了一个方法execute(Runnable command),该方法接收一个Runable实例,它用来执行一个任务,任务即一个实现 了Runnable接口的类。ExecutorService接口继承自Executor接口,它提供了更丰富的实现多线程的方法,比如,ExecutorService提供了关闭自己 的方法,以及可为跟踪一个或多个异步任务执行状况而生成 Future 的方法。 可以调用ExecutorService的shutdown()方法来平滑地关闭 ExecutorService,调用该方法后,将导致ExecutorService停止接受任何新的任务且等待已经提交的任务执行完成(已经提交的任务会分两类:一类是 已经在执行的,另一类是还没有开始执行的),当所有已经提交的任务执行完毕后将会关闭ExecutorService。因此我们一般用该接口来实现和管理多 线程。 ExecutorService的生命周期包括三种状态:运行、关闭、终止。创建后便进入运行状态,当调用了shutdown()方法时,便进入关闭状态,此时 意味着ExecutorService不再接受新的任务,但它还在执行已经提交了的任务,当素有已经提交了的任务执行完后,便到达终止状态。如果不调 用shutdown()方法,ExecutorService会一直处在运行状态,不断接收新的任务,执行新的任务,服务器端一般不需要关闭它,保持一直运行即可 。 Executors提供了一系列工厂方法用于创先线程池,返回的线程池都实现了ExecutorService接口。 public static ExecutorService newFixedThreadPool(int nThreads) 创建固定数目线程的线程池。 public static ExecutorService newCachedThreadPool() 创建一个可缓存的线程池,调用execute将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线 程并添加到池中 。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。 public static ExecutorService newSingleThreadExecutor() 创建一个单线程化的Executor。 public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。 这四种方法都是用的Executors中的ThreadFactory建立的线程,下面就以上四个方法做个比较 newCachedThreadPool() -缓存型池子,先查看池中有没有以前建立的线程,如果有,就 reuse.如果 没有,就建一个新的线程加入池中 -缓存型池子通常用于执行一些生存期很短的异步型任务 因此在一些面向连接的daemon型SERVER中用得不多。但对于生存期短 的异步任务,它是Executor的首选。 -能reuse的线程,必须是timeout IDLE内的池中线程,缺省 timeout 是60s,超过这个IDLE时长,线程实例将被终止及移出池。 注意,放入CachedThreadPool的线程不必担心其结束,超过TIMEOUT 不活动,其会自动被终止。 newFixedThreadPool(int) -newFixedThreadPool与cacheThreadPool差不多,也是能reuse就用, 但不能随时建新的线程 -其独特之处:任意时间点,最多只能有固定数目的活动线程存在,此时如果 有新的线程要建立,只能放在另外的队列中等待,直到当前的线程中某个 线程终止直接被移出池子 -和cacheThreadPool不同,FixedThreadPool没有IDLE机制(可能也有 ,但既然文档没提,肯定非常长,类似依赖上层的TCP或UDP IDLE机制之 类的),所以FixedThreadPool多数针对一些很稳定很固定的正规并发 线程,多用于服务器 -从方法的源代码看,cache池和fixed 池调用的是同一个底层 池,只不过 参数不同: fixed池线程数固定,并且是0秒IDLE(无IDLE) cache池线程数支持0-Integer.MAX_VALUE(显然完全没考虑主机的资源 承受能力),60秒IDLE newScheduledThreadPool(int) -调度型线程池 -这个池子里的线程可以按schedule依次delay执行,或周期执行 SingleThreadExecutor() -单例线程,任意时间池中只能有一个线程 -用的是和cache池和fixed池相同的底层池,但线程数目是1-1,0秒IDLE( 无IDLE) 一般来说,CachedTheadPool在程序执行过程中通常会创建与所需数量相同的线程,然后在它回收旧线程时停止创建新线程,因此它是合理 的Executor的首选,只有当这种方式会引发问题时(比如需要大量长时间面向连接的线程时),才需要考虑用FixedThreadPool。(该段话摘 自《Thinking in Java》第四版) Executor执行执行Runnable任务任务 通过Executors的以上四个静态工厂方法获得 ExecutorService实例,而后调用该实例的execute(Runnable command)方法即可。一 旦Runnable任务传递到execute()方法,该方法便会自动在一个线程上执行。下面是是Executor执行Runnable任务的示例代码: import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class TestCachedThreadPool{ public static void main(String[] args){ ExecutorService executorService = Executors.newCachedThreadPool(); // ExecutorService executorService = Executors.newFixedThreadPool(5); // ExecutorService executorService = Executors.newSingleThreadExecutor(); for (int i = 0; i < 5; i++){ executorService.execute(new TestRunnable()); System.out.println("************* a" + i + " *************"); } executorService.shutdown(); } } class TestRunnable implements Runnable{ public void run(){ System.out.println(Thread.currentThread().getName() + "线程被调用了。"); } } 某次执行后的结果如下: 从结果中可以看出,pool-1-thread-1和pool-1-thread-2均被调用了两次,这是随机的,execute会首先在线程池中选择一个已有空闲线程来执行任务,如果线程池中没有空闲线程,它便会创建一个新的线程来执行任务。 Executor执行执行Callable任务任务 在Java 5之后,任务分两类:一类是实现了Runnable接口的类,一类是实现了Callable接口的类。两者都可以被ExecutorService执行,但 是Runnable任务没有返回值,而Callable任务有返回值。并且Callable的call()方法只能通过ExecutorService的submit(Callable task) 方法来 执行,并且返回一个 Future,是表示任务等待完成的 Future。 Callable接口类似于Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。但是 Runnable 不会返回结果,并且无法抛出经过检 查的异常而Callable又返回结果,而且当获取返回结果时可能会抛出异常。Callable中的call()方法类似Runnable的run()方法,区别同样是有返回值 ,后者没有。 当将一个Callable的对象传递给ExecutorService的submit方法,则该call方法自动在一个线程上执行,并且会返回执行结果Future对象。同样, 将Runnable的对象传递给ExecutorService的submit方法,则该run方法自动在一个线程上执行,并且会返回执行结果Future对象,但是在该Future 对象上调用get方法,将返回null。 下面给出一个Executor执行Callable任务的示例代码: import java.util.ArrayList; import java.util.List; import java.util.concurrent.*; public class CallableDemo{ public static void main(String[] args){ ExecutorService executorService = Executors.newCachedThreadPool(); List> resultList = new ArrayList>(); //创建10个任务并执行 for (int i = 0; i < 10; i++){ //使用ExecutorService执行Callable类型的任务,并将结果保存在future变量中 Future future = executorService.submit(new TaskWithResult(i)); //将任务执行结果存储到List中 resultList.add(future); } //遍历任务的结果 for (Future fs : resultList){ try{ while(!fs.isDone);//Future返回如果没有完成,则一直循环等待,直到Future返回完成 System.out.println(fs.get()); //打印各个线程(任务)执行的结果 }catch(InterruptedException e){ e.printStackTrace(); }catch(ExecutionException e){ e.printStackTrace(); }finally{ //启动一次顺序关闭,执行以前提交的任务,但不接受新任务 executorService.shutdown(); } } } } class TaskWithResult implements Callable{ private int id; public TaskWithResult(int id){ this.id = id; } /** * 任务的具体过程,一旦任务传给ExecutorService的submit方法, * 则该方法自动在一个线程上执行 */ public String call() throws Exception { System.out.println("call()方法被自动调用!!! " + Thread.currentThread().getName()); //该返回结果将被Future的get方法得到 return "call()方法被自动调用,任务返回的结果是:" + id + " " + Thread.currentThread().getName(); } } 某次执行结果如下: 从结果中可以同样可以看出,submit也是首先选择空闲线程来执行任务,如果没有,才会创建新的线程来执行任务。另外,需要注意:如果Future的返回尚未完成,则get()方法会阻塞等待,直到Future完成返回,可以通过调用isDone()方法判断Future是否完成了返回。 自定义线程池自定义线程池 自定义线程池,可以用ThreadPoolExecutor类创建,它有多个构造方法来创建线程池,用该类很容易实现自定义的线程池,这里先贴上示例程序 : import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class ThreadPoolTest{ public static void main(String[] args){ //创建等待队列 BlockingQueue bqueue = new ArrayBlockingQueue(20); //创建线程池,池中保存的线程数为3,允许的最大线程数为5 ThreadPoolExecutor pool = new ThreadPoolExecutor(3,5,50,TimeUnit.MILLISECONDS,bqueue); //创建七个任务 Runnable t1 = new MyThread(); Runnable t2 = new MyThread(); Runnable t3 = new MyThread(); Runnable t4 = new MyThread(); Runnable t5 = new MyThread(); Runnable t6 = new MyThread(); Runnable t7 = new MyThread(); //每个任务会在一个线程上执行 pool.execute(t1); pool.execute(t2); pool.execute(t3); pool.execute(t4); pool.execute(t5); pool.execute(t6); pool.execute(t7); //关闭线程池 pool.shutdown(); } } class MyThread implements Runnable{ @Override public void run(){ System.out.println(Thread.currentThread().getName() + "正在执行。。。"); try{ Thread.sleep(100); }catch(InterruptedException e){ e.printStackTrace(); } } } 运行结果如下: 从结果中可以看出,七个任务是在线程池的三个线程上执行的。这里简要说明下用到的ThreadPoolExecuror类的构造方法中各个参数的含义。 public ThreadPoolExecutor (int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,BlockingQueue workQueue) corePoolSize:线程池中所保存的核心线程数,包括空闲线程。 maximumPoolSize:池中允许的最大线程数。 keepAliveTime:线程池中的空闲线程所能持续的最长时间。 unit:持续时间的单位。 workQueue:任务执行前保存任务的队列,仅保存由execute方法提交的Runnable任务。 根据ThreadPoolExecutor源码前面大段的注释,我们可以看出,当试图通过excute方法讲一个Runnable任务添加到线程池中时,按照如下顺序来处理: 1、如果线程池中的线程数量少于corePoolSize,即使线程池中有空闲线程,也会创建一个新的线程来执行新添加的任务; 2、如果线程池中的线程数量大于等于corePoolSize,但缓冲队列workQueue未满,则将新添加的任务放到workQueue中,按照FIFO的原则依次等待执行(线程池中有线程空闲出来后依次将缓冲队列中的任务交付给空闲的线程执行); 3、如果线程池中的线程数量大于等于corePoolSize,且缓冲队列workQueue已满,但线程池中的线程数量小于maximumPoolSize,则会创建新 的线程来处理被添加的任务; 4、如果线程池中的线程数量等于了maximumPoolSize,有4种才处理方式(该构造方法调用了含有5个参数的构造方法,并将最后一个构造方法 为RejectedExecutionHandler类型,它在处理线程溢出时有4种方式,这里不再细说,要了解的,自己可以阅读下源码)。 总结起来,也即是说,当有新的任务要处理时,先看线程池中的线程数量是否大于corePoolSize,再看缓冲队列workQueue是否满,最后看线程池中的线程数量是否大于maximumPoolSize。 另外,当线程池中的线程数量大于corePoolSize时,如果里面有线程的空闲时间超过了keepAliveTime,就将其移除线程池,这样,可以动态地调整线程池中线程的数量。 我们大致来看下Executors的源码,newCachedThreadPool的不带RejectedExecutionHandler参数(即第五个参数,线程数量超过maximumPoolSize时,指定处理方式)的构造方法如下: public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue()); } 它将corePoolSize设定为0,而将maximumPoolSize设定为了Integer的最大值,线程空闲超过60秒,将会从线程池中移除。由于 核心线程数为0,因此每次添加任务,都会先从线程池中找空闲线程,如果没有就会创建一个线程(SynchronousQueue 决定的,后面会说)来执行新的任务,并将该线程加入到线程池中,而最大允许的线程数为Integer的最大值,因此这个线程池理论 上可以不断扩大。 再来看newFixedThreadPool的不带RejectedExecutionHandler参数的构造方法,如下: public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()); } 它将corePoolSize和maximumPoolSize都设定为了nThreads,这样便实现了线程池的大小的固定,不会动态地扩大,另外,keepAliveTime设 定为了0,也就是说线程只要空闲下来,就会被移除线程池,敢于LinkedBlockingQueue下面会说。 下面说说几种排队的策略: 1、直接提交。缓冲队列采用 SynchronousQueue,它将任务直接交给线程处理而不保持它们。如果不存在可用于立即运行任务的线程(即线程池 中的线程都在工作),则试图把任务加入缓冲队列将会失败,因此会构造一个新的线程来处理新添加的任务,并将其加入到线程池中。直接提交通常 要求无界 maximumPoolSizes(Integer.MAX_VALUE) 以避免拒绝新提交的任务。newCachedThreadPool采用的便是这种策略。 2、无界队列。使用无界队列(典型的便是采用预定义容量的 LinkedBlockingQueue,理论上是该缓冲队列可以对无限多的任务排队)将导致在 所有 corePoolSize 线程都工作的情况下将新任务加入到缓冲队列中。这样,创建的线程就不会超过 corePoolSize,也因此,maximumPoolSize 的 值也就无效了。当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列。newFixedThreadPool采用的便是这种策略。 3、有界队列。当使用有限的 maximumPoolSizes 时,有界队列(一般缓冲队列使用ArrayBlockingQueue,并制定队列的最大长度)有助于防止 资源耗尽,但是可能较难调整和控制,队列大小和最大池大小需要相互折衷,需要设定合理的参数。 【Java并发编程】之二十一:并发新特性—阻塞队列和阻塞栈(含代码) 转载请注明出处:转载请注明出处:http://blog.csdn.net/ns_code/article/details/17511147 阻塞队列阻塞队列 阻塞队列是Java 5并发新特性中的内容,阻塞队列的接口是java.util.concurrent.BlockingQueue,它有多个实现类:ArrayBlockingQueue、 DelayQueue、LinkedBlockingQueue、PriorityBlockingQueue、SynchronousQueue等,用法大同小异,具体可查看JDK文档,这里简单举例 看下ArrayBlockingQueue,它实现了一个有界队列,当队列满时,便会阻塞等待,直到有元素出队,后续的元素才可以被加入队列。 看下面的例子: import java.util.concurrent.BlockingQueue; import java.util.concurrent.ArrayBlockingQueue; public class BlockingQueueTest{ public static void main(String[] args) throws InterruptedException { BlockingQueue bqueue = new ArrayBlockingQueue(20); for (int i = 0; i < 30; i++) { //将指定元素添加到此队列中 bqueue.put("加入元素" + i); System.out.println("向阻塞队列中添加了元素:" + i); } System.out.println("程序到此运行结束,即将退出----"); } } 输出结果如下: 从执行结果中可以看出,由于队列中元素的数量限制在了20个,因此添加20个元素后,其他元素便在队列外阻塞等待,程序并没有终止。 如果队列已满后,我们将队首元素移出,并可以继续向阻塞队列中添加元素,修改代码如下: import java.util.concurrent.BlockingQueue; import java.util.concurrent.ArrayBlockingQueue; public class BlockingQueueTest{ public static void main(String[] args) throws InterruptedException { BlockingQueue bqueue = new ArrayBlockingQueue(20); for (int i = 0; i < 30; i++) { //将指定元素添加到此队列中 bqueue.put("" + i); System.out.println("向阻塞队列中添加了元素:" + i); if(i > 18){ //从队列中获取队头元素,并将其移出队列 System.out.println("从阻塞队列中移除元素:" + bqueue.take()); } } System.out.println("程序到此运行结束,即将退出----"); } } 执行结果如下: 从结果中可以看出,当添加了第20个元素后,我们从队首移出一个元素,这样便可以继续向队列中添加元素,之后每添加一个元素,便从将队首元 素移除,这样程序便可以执行结束。 阻塞栈阻塞栈 阻塞栈与阻塞队列相似,只是它是Java 6中加入的新特性,阻塞栈的接口java.util.concurrent.BlockingDeque也有很多实现类,使用方法也比较 相似,具体查看JDK文档。 下面同样给出一个简单的例子: import java.util.concurrent.BlockingDeque; import java.util.concurrent.LinkedBlockingDeque; public class BlockingDequeTest { public static void main(String[] args) throws InterruptedException { BlockingDeque bDeque = new LinkedBlockingDeque(20); for (int i = 0; i < 30; i++) { //将指定元素添加到此阻塞栈中 bDeque.putFirst("" + i); System.out.println("向阻塞栈中添加了元素:" + i); } System.out.println("程序到此运行结束,即将退出----"); } } 执行结果如下: 程序依然会阻塞等待,我们改为如下代码: import java.util.concurrent.BlockingDeque; import java.util.concurrent.LinkedBlockingDeque; public class BlockingDequeTest { public static void main(String[] args) throws InterruptedException { BlockingDeque bDeque = new LinkedBlockingDeque(20); for (int i = 0; i < 30; i++) { //将指定元素添加到此阻塞栈中 bDeque.putFirst("" + i); System.out.println("向阻塞栈中添加了元素:" + i); if(i > 18){ //从阻塞栈中取出栈顶元素,并将其移出 System.out.println("从阻塞栈中移出了元素:" + bDeque.pollFirst()); } } System.out.println("程序到此运行结束,即将退出----"); } } 执行结果如下: 从结果中可以看出,当添加了第20个元素后,我们从将栈顶元素移处,这样便可以继续向栈中添加元素,之后每添加一个元素,便将栈顶元素移出 ,这样程序便可以执行结束。 【Java并发编程】之二十:并发新特性—Lock锁和条件变量(含代码) 转载请注明出处:转载请注明出处:http://blog.csdn.net/ns_code/article/details/17487337 简单使用简单使用Lock锁锁 Java 5中引入了新的锁机制——java.util.concurrent.locks中的显式的互斥锁:Lock接口,它提供了比synchronized更加广泛的锁定操作。Lock 接口有3个实现它的类:ReentrantLock、ReetrantReadWriteLock.ReadLock和ReetrantReadWriteLock.WriteLock,即重入锁、读锁和写锁。 lock必须被显式地创建、锁定和释放,为了可以使用更多的功能,一般用ReentrantLock为其实例化。为了保证锁最终一定会被释放(可能会有异常 发生),要把互斥区放在try语句块内,并在finally语句块中释放锁,尤其当有return语句时,return语句必须放在try字句中,以确保unlock()不 会过早发生,从而将数据暴露给第二个任务。因此,采用lock加锁和释放锁的一般形式如下: Lock lock = new ReentrantLock();//默认使用非公平锁,如果要使用公平锁,需要传入参数true ........ lock.lock(); try { //更新对象的状态 //捕获异常,必要时恢复到原来的不变约束 //如果有return语句,放在这里 } finally { lock.unlock(); //锁必须在finally块中释放 } ReetrankLock与与synchronized比较比较 性能比较性能比较 在JDK1.5中,synchronized是性能低效的。因为这是一个重量级操作,它对性能最大的影响是阻塞的是实现,挂起线程和恢复线程的操作都需要 转入内核态中完成,这些操作给系统的并发性带来了很大的压力。相比之下使用Java提供的Lock对象,性能更高一些。Brian Goetz对这两种锁 在JDK1.5、单核处理器及双Xeon处理器环境下做了一组吞吐量对比的实验,发现多线程环境下,synchronized的吞吐量下降的非常严重, 而ReentrankLock则能基本保持在同一个比较稳定的水平上。但与其说ReetrantLock性能好,倒不如说synchronized还有非常大的优化余地,于是 到了JDK1.6,发生了变化,对synchronize加入了很多优化措施,有自适应自旋,锁消除,锁粗化,轻量级锁,偏向锁等等。导致在JDK1.6 上synchronize的性能并不比Lock差。官方也表示,他们也更支持synchronize,在未来的版本中还有优化余地,所以还是提倡在synchronized能实 现需求的情况下,优先考虑使用synchronized来进行同步。 下面浅析以下两种锁机制的底层的实现策略。 互斥同步最主要的问题就是进行线程阻塞和唤醒所带来的性能问题,因而这种同步又称为阻塞同步,它属于一种悲观的并发策略,即线程获得的是 独占锁。独占锁意味着其他线程只能依靠阻塞来等待线程释放锁。而在CPU转换线程阻塞时会引起线程上下文切换,当有很多线程竞争锁的时候,会 引起CPU频繁的上下文切换导致效率很低。synchronized采用的便是这种并发策略。 随着指令集的发展,我们有了另一种选择:基于冲突检测的乐观并发策略,通俗地讲就是先进性操作,如果没有其他线程争用共享数据,那操作就 成功了,如果共享数据被争用,产生了冲突,那就再进行其他的补偿措施(最常见的补偿措施就是不断地重拾,直到试成功为止),这种乐观的并发 策略的许多实现都不需要把线程挂起,因此这种同步被称为非阻塞同步。ReetrantLock采用的便是这种并发策略。 在乐观的并发策略中,需要操作和冲突检测这两个步骤具备原子性,它靠硬件指令来保证,这里用的是CAS操作(Compare and Swap)。 JDK1.5之后,Java程序才可以使用CAS操作。我们可以进一步研究ReentrantLock的源代码,会发现其中比较重要的获得锁的一个方法 是compareAndSetState,这里其实就是调用的CPU提供的特殊指令。现代的CPU提供了指令,可以自动更新共享数据,而且能够检测到其他线程的 干扰,而compareAndSet() 就用这些代替了锁定。这个算法称作非阻塞算法,意思是一个线程的失败或者挂起不应该影响其他线程的失败或挂起。 Java 5中引入了注入AutomicInteger、AutomicLong、AutomicReference等特殊的原子性变量类,它们提供的如:compareAndSet()、 incrementAndSet()和getAndIncrement()等方法都使用了CAS操作。因此,它们都是由硬件指令来保证的原子方法。 用途比较用途比较 基本语法上,ReentrantLock与synchronized很相似,它们都具备一样的线程重入特性,只是代码写法上有点区别而已,一个表现为API层面的互 斥锁(Lock),一个表现为原生语法层面的互斥锁(synchronized)。ReentrantLock相对synchronized而言还是增加了一些高级功能,主要有以 下三项: 1、等待可中断:当持有锁的线程长期不释放锁时,正在等待的线程可以选择放弃等待,改为处理其他事情,它对处理执行时间非常上的同步块很 有帮助。而在等待由synchronized产生的互斥锁时,会一直阻塞,是不能被中断的。 有帮助。而在等待由synchronized产生的互斥锁时,会一直阻塞,是不能被中断的。 2、可实现公平锁:多个线程在等待同一个锁时,必须按照申请锁的时间顺序排队等待,而非公平锁则不保证这点,在锁释放时,任何一个等待锁 的线程都有机会获得锁。synchronized中的锁时非公平锁,ReentrantLock默认情况下也是非公平锁,但可以通过构造方法ReentrantLock(ture) 来要求使用公平锁。 3、锁可以绑定多个条件:ReentrantLock对象可以同时绑定多个Condition对象(名曰:条件变量或条件队列),而在synchronized中,锁对象 的wait()和notify()或notifyAll()方法可以实现一个隐含条件,但如果要和多于一个的条件关联的时候,就不得不额外地添加一个锁, 而ReentrantLock则无需这么做,只需要多次调用newCondition()方法即可。而且我们还可以通过绑定Condition对象来判断当前线程通知的是哪 些线程(即与Condition对象绑定在一起的其他线程)。 可中断锁可中断锁 ReetrantLock有两种锁:忽略中断锁和响应中断锁。忽略中断锁与synchronized实现的互斥锁一样,不能响应中断,而响应中断锁可以响应中断 。 如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让 它中断自己或者在别的线程中中断它,如果此时ReetrantLock提供的是忽略中断锁,则它不会去理会该中断,而是让线程B继续等待,而如果此 时ReetrantLock提供的是响应中断锁,那么它便会处理中断,让线程B放弃等待,转而去处理其他事情。 获得响应中断锁的一般形式如下: ReentrantLock lock = new ReentrantLock(); ........... lock.lockInterruptibly();//获取响应中断锁 try { //更新对象的状态 //捕获异常,必要时恢复到原来的不变约束 //如果有return语句,放在这里 }finally{ lock.unlock(); //锁必须在finally块中释放 } 这里有一个不错的分析中断的示例代码(摘自网上) 当用synchronized中断对互斥锁的等待时,并不起作用,该线程依然会一直等待,如下面的实例: public class Buffer { private Object lock; public Buffer() { lock = this; } public void write() { synchronized (lock) { long startTime = System.currentTimeMillis(); System.out.println("开始往这个buff写入数据…"); for (;;)// 模拟要处理很长时间 { if (System.currentTimeMillis() - startTime > Integer.MAX_VALUE) { break; } } System.out.println("终于写完了"); } } public void read() { synchronized (lock) { System.out.println("从这个buff读数据"); } } public static void main(String[] args) { Buffer buff = new Buffer(); final Writer writer = new Writer(buff); final Reader reader = new Reader(buff); writer.start(); reader.start(); new Thread(new Runnable() { @Override public void run() { long start = System.currentTimeMillis(); for (;;) { //等5秒钟去中断读 if (System.currentTimeMillis() - start > 5000) { System.out.println("不等了,尝试中断"); reader.interrupt(); //尝试中断读线程 break; } } } }).start(); // 我们期待“读”这个线程能退出等待锁,可是事与愿违,一旦读这个线程发现自己得不到锁, // 就一直开始等待了,就算它等死,也得不到锁,因为写线程要21亿秒才能完成 T_T ,即使我们中断它, // 它都不来响应下,看来真的要等死了。这个时候,ReentrantLock给了一种机制让我们来响应中断, // 让“读”能伸能屈,勇敢放弃对这个锁的等待。我们来改写Buffer这个类,就叫BufferInterruptibly吧,可中断缓存。 } } class Writer extends Thread { private Buffer buff; public Writer(Buffer buff) { this.buff = buff; } @Override public void run() { buff.write(); } } class Reader extends Thread { private Buffer buff; public Reader(Buffer buff) { this.buff = buff; } @Override public void run() { buff.read();//这里估计会一直阻塞 System.out.println("读结束"); } } 执行结果如下: 我们等待了很久,后面依然没有输出,说明读线程对互斥锁的等待并没有被中断,也就是该户吃锁没有响应对读线程的中断。 我们再将上面代码中synchronized的互斥锁改为ReentrantLock的响应中断锁,即改为如下代码: import java.util.concurrent.locks.ReentrantLock; public class BufferInterruptibly { private ReentrantLock lock = new ReentrantLock(); public void write() { lock.lock(); try { long startTime = System.currentTimeMillis(); System.out.println("开始往这个buff写入数据…"); for (;;)// 模拟要处理很长时间 { if (System.currentTimeMillis() - startTime > Integer.MAX_VALUE) { break; } } System.out.println("终于写完了"); } finally { lock.unlock(); } } public void read() throws InterruptedException { lock.lockInterruptibly();// 注意这里,可以响应中断 try { System.out.println("从这个buff读数据"); } finally { lock.unlock(); } } public static void main(String args[]) { BufferInterruptibly buff = new BufferInterruptibly(); final Writer2 writer = new Writer2(buff); final Reader2 reader = new Reader2(buff); writer.start(); reader.start(); new Thread(new Runnable() { @Override public void run() { long start = System.currentTimeMillis(); for (;;) { if (System.currentTimeMillis() - start > 5000) { System.out.println("不等了,尝试中断"); reader.interrupt(); //此处中断读操作 break; } } } }).start(); } } class Reader2 extends Thread { private BufferInterruptibly buff; public Reader2(BufferInterruptibly buff) { this.buff = buff; } @Override public void run() { try { buff.read();//可以收到中断的异常,从而有效退出 } catch (InterruptedException e) { System.out.println("我不读了"); } System.out.println("读结束"); } } class Writer2 extends Thread { private BufferInterruptibly buff; public Writer2(BufferInterruptibly buff) { this.buff = buff; } @Override public void run() { buff.write(); } } 执行结果如下: 从结果中可以看出,尝试中断后输出了catch语句块中的内容,也输出了后面的“读结束”,说明线程对互斥锁的等待被中断了,也就是该互斥锁 响应了对读线程的中断。 条件变量实现线程间协作条件变量实现线程间协作 在生产者——消费者模型一文中,我们用synchronized实现互斥,并配合使用Object对象的wait()和notify()或notifyAll()方法来实现线 程间协作。Java 5之后,我们可以用Reentrantlock锁配合Condition对象上的await()和signal()或signalAll()方法来实现线程间协作。 在ReentrantLock对象上newCondition()可以得到一个Condition对象,可以通过在Condition上调用await()方法来挂起一个任务(线程), 通过在Condition上调用signal()来通知任务,从而唤醒一个任务,或者调用signalAll()来唤醒所有在这个Condition上被其自身挂起的任务。 另外,如果使用了公平锁,signalAll()的与Condition关联的所有任务将以FIFO队列的形式获取锁,如果没有使用公平锁,则获取锁的任务是随 机的,这样我们便可以更好地控制处在await状态的任务获取锁的顺序。与notifyAll()相比,signalAll()是更安全的方式。另外,它可以指定唤 醒与自身Condition对象绑定在一起的任务。 下面将生产者——消费者模型一文中的代码改为用条件变量实现,如下: import java.util.concurrent.*; import java.util.concurrent.locks.*; class Info{ // 定义信息类 private String name = "name";//定义name属性,为了与下面set的name属性区别开 private String content = "content" ;// 定义content属性,为了与下面set的content属性区别开 private boolean flag = true ; // 设置标志位,初始时先生产 private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); //产生一个Condition对象 public void set(String name,String content){ lock.lock(); try{ while(!flag){ condition.await() ; } this.setName(name) ; // 设置名称 Thread.sleep(300) ; this.setContent(content) ; // 设置内容 flag = false ; // 改变标志位,表示可以取走 condition.signal(); }catch(InterruptedException e){ e.printStackTrace() ; }finally{ lock.unlock(); } } public void get(){ lock.lock(); try{ while(flag){ condition.await() ; } Thread.sleep(300) ; System.out.println(this.getName() + " --> " + this.getContent()) ; flag = true ; // 改变标志位,表示可以生产 condition.signal(); }catch(InterruptedException e){ e.printStackTrace() ; }finally{ lock.unlock(); } } public void setName(String name){ this.name = name ; } public void setContent(String content){ this.content = content ; } public String getName(){ return this.name ; } public String getContent(){ return this.content ; } } class Producer implements Runnable{ // 通过Runnable实现多线程 private Info info = null ; // 保存Info引用 public Producer(Info info){ this.info = info ; } public void run(){ boolean flag = true ; // 定义标记位 for(int i=0;i<10;i++){ if(flag){ this.info.set("姓名--1","内容--1") ; // 设置名称 flag = false ; }else{ this.info.set("姓名--2","内容--2") ; // 设置名称 flag = true ; } } } } class Consumer implements Runnable{ private Info info = null ; public Consumer(Info info){ this.info = info ; } public void run(){ for(int i=0;i<10;i++){ this.info.get() ; } } } public class ThreadCaseDemo{ public static void main(String args[]){ Info info = new Info(); // 实例化Info对象 Producer pro = new Producer(info) ; // 生产者 Consumer con = new Consumer(info) ; // 消费者 new Thread(pro).start() ; //启动了生产者线程后,再启动消费者线程 try{ Thread.sleep(500) ; }catch(InterruptedException e){ e.printStackTrace() ; } new Thread(con).start() ; } } 执行后,同样可以得到如下的结果: 姓名--1 --> 内容--1 姓名--2 --> 内容--2 姓名--1 --> 内容--1 姓名--2 --> 内容--2 姓名--1 --> 内容--1 姓名--2 --> 内容--2 姓名--1 --> 内容--1 姓名--2 --> 内容--2 姓名--1 --> 内容--1 姓名--2 --> 内容--2 从以上并不能看出用条件变量的await()、signal()、signalAll()方法比用Object对象的wait()、notify()、notifyAll()方法实现线 程间协作有多少优点,但它在处理更复杂的多线程问题时,会有明显的优势。所以,Lock和Condition对象只有在更加困难的多线程问题中才是必 须的。 读写锁读写锁 另外,synchronized获取的互斥锁不仅互斥读写操作、写写操作,还互斥读读操作,而读读操作时不会带来数据竞争的,因此对对读读操作也互斥 的话,会降低性能。Java 5中提供了读写锁,它将读锁和写锁分离,使得读读操作不互斥,获取读锁和写锁的一般形式如下: ReadWriteLock rwl = new ReentrantReadWriteLock(); rwl.writeLock().lock() //获取写锁 rwl.readLock().lock() //获取读锁 用读锁来锁定读操作,用写锁来锁定写操作,这样写操作和写操作之间会互斥,读操作和写操作之间会互斥,但读操作和读操作就不会互斥。 《Java并发编程实践》一书给出了使用 ReentrantLock的最佳时机: 当你需要以下高级特性时,才应该使用:可定时的、可轮询的与可中断的锁获取操作,公平队列,或者非块结构的锁。否则,请使 用synchronized。 【Java并发编程】之二十三:并发新特性—信号量Semaphore(含 转载请注明出处:转载请注明出处:http://blog.csdn.net/ns_code/article/details/17524153 在操作系统中,信号量是个很重要的概念,它在控制进程间的协作方面有着非常重要的作用,通过对信号量的不同操作,可以分别实现进程间的互 斥与同步。当然它也可以用于多线程的控制,我们完全可以通过使用信号量来自定义实现类似Java中的synchronized、wait、notify机制。 Java并发包中的信号量Semaphore实际上是一个功能完毕的计数信号量,从概念上讲,它维护了一个许可集合,对控制一定资源的消费与回收有 着很重要的意义。Semaphore可以控制某个资源被同时访问的任务数,它通过acquire()获取一个许可,release()释放一个许可。如果被同时访 问的任务数已满,则其他acquire的任务进入等待状态,直到有一个任务被release掉,它才能得到许可。 下面给出一个采用Semaphore控制并发访问数量的示例程序: import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; public class SemaphoreTest{ public static void main(String[] args) { //采用新特性来启动和管理线程——内部使用线程池 ExecutorService exec = Executors.newCachedThreadPool(); //只允许5个线程同时访问 final Semaphore semp = new Semaphore(5); //模拟10个客户端访问 for (int index = 0; index < 10; index++){ final int num = index; Runnable run = new Runnable() { public void run() { try { //获取许可 semp.acquire(); System.out.println("线程" + Thread.currentThread().getName() + "获得许可:" + num); //模拟耗时的任务 for (int i = 0; i < 999999; i++) ; //释放许可 semp.release(); System.out.println("线程" + Thread.currentThread().getName() + "释放许可:" + num); System.out.println("当前允许进入的任务个数:" + semp.availablePermits()); }catch(InterruptedException e){ e.printStackTrace(); } } }; exec.execute(run); } //关闭线程池 exec.shutdown(); } } 某次执行的结果如下: 线程pool-1-thread-1获得许可:0 线程pool-1-thread-1释放许可:0 当前允许进入的任务个数:5 线程pool-1-thread-2获得许可:1 线程pool-1-thread-6获得许可:5 线程pool-1-thread-4获得许可:3 线程pool-1-thread-8获得许可:7 线程pool-1-thread-2释放许可:1 当前允许进入的任务个数:2 线程pool-1-thread-5获得许可:4 线程pool-1-thread-8释放许可:7 线程pool-1-thread-3获得许可:2 线程pool-1-thread-4释放许可:3 线程pool-1-thread-10获得许可:9 线程pool-1-thread-6释放许可:5 线程pool-1-thread-10释放许可:9 当前允许进入的任务个数:2 线程pool-1-thread-3释放许可:2 当前允许进入的任务个数:1 线程pool-1-thread-5释放许可:4 当前允许进入的任务个数:3 线程pool-1-thread-7获得许可:6 线程pool-1-thread-9获得许可:8 线程pool-1-thread-7释放许可:6 当前允许进入的任务个数:5 当前允许进入的任务个数:3 当前允许进入的任务个数:3 当前允许进入的任务个数:3 线程pool-1-thread-9释放许可:8 当前允许进入的任务个数:5 可以看出,Semaphore允许并发访问的任务数一直为5,当然,这里还很容易看出一点,就是Semaphore仅仅是对资源的并发访问的任务数进行 监控,而不会保证线程安全,因此,在访问的时候,要自己控制线程的安全访问。 【Java并发编程】之二十二:并发新特性—障碍器CyclicBarri 转载请注明出处:转载请注明出处:http://blog.csdn.net/ns_code/article/details/17512983 CyclicBarrier(又叫障碍器)同样是Java 5中加入的新特性,使用时需要导入java.util.concurrent.CylicBarrier。它适用于这样一种情况:你希望 创建一组任务,它们并发地执行工作,另外的一个任务在这一组任务并发执行结束前一直阻塞等待,直到该组任务全部执行结束,这个任务才得以 执行。这非常像CountDownLatch,只是CountDownLatch是只触发一次的事件,而CyclicBarrier可以多次重用。 下面给出一个简单的实例来说明其用法: import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; public class CyclicBarrierTest { public static void main(String[] args) { //创建CyclicBarrier对象, //并设置执行完一组5个线程的并发任务后,再执行MainTask任务 CyclicBarrier cb = new CyclicBarrier(5, new MainTask()); new SubTask("A", cb).start(); new SubTask("B", cb).start(); new SubTask("C", cb).start(); new SubTask("D", cb).start(); new SubTask("E", cb).start(); } } /** * 最后执行的任务 */ class MainTask implements Runnable { public void run() { System.out.println("......终于要执行最后的任务了......"); } } /** * 一组并发任务 */ class SubTask extends Thread { private String name; private CyclicBarrier cb; SubTask(String name, CyclicBarrier cb) { this.name = name; this.cb = cb; } public void run() { System.out.println("[并发任务" + name + "] 开始执行"); for (int i = 0; i < 999999; i++) ; //模拟耗时的任务 System.out.println("[并发任务" + name + "] 开始执行完毕,通知障碍器"); try { //每执行完一项任务就通知障碍器 cb.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } } } 某次执行的结果如下: [并发任务A] 开始执行 [并发任务B] 开始执行 [并发任务D] 开始执行 [并发任务E] 开始执行 [并发任务A] 开始执行完毕,通知障碍器 [并发任务E] 开始执行完毕,通知障碍器 [并发任务D] 开始执行完毕,通知障碍器 [并发任务C] 开始执行 [并发任务B] 开始执行完毕,通知障碍器 [并发任务C] 开始执行完毕,通知障碍器 ......终于要执行最后的任务了...... 从结果可以看出:MainTask任务在一组中的5个任务执行完后才开始执行。 【Java并发编程】并发编程大合集 转载请注明出处:转载请注明出处:http://blog.csdn.net/ns_code/article/details/17539599 为了方便各位网友学习以及方便自己复习之用,将Java并发编程系列内容系列内容按照由浅入深的学习顺序总结 如下,点击相应的标题即可跳转到对应的文章 【【JavaJava并发编程】实现多线程的两种方法并发编程】实现多线程的两种方法 【【JavaJava并发编程】线程的中断并发编程】线程的中断 【【JavaJava并发编程】正确挂起、恢复、终止线程并发编程】正确挂起、恢复、终止线程 【【JavaJava并发编程】守护线程和线程阻塞并发编程】守护线程和线程阻塞 【【JavaJava并发编程】并发编程】VolatileVolatile关键字(上)关键字(上) 【【JavaJava并发编程】并发编程】VolatileVolatile关键字(下)关键字(下) 【【JavaJava并发编程】并发编程】synchronizedsynchronized关键字关键字 【【JavaJava并发编程】并发编程】synchronizedsynchronized的另个一重要作用:内存可见性的另个一重要作用:内存可见性 【【JavaJava并发编程】实现内存可见性的两种方法比较:并发编程】实现内存可见性的两种方法比较:synchronizedsynchronized和和VolatileVolatile 【【JavaJava并发编程】多线程环境下安全使用集合并发编程】多线程环境下安全使用集合APIAPI 【【JavaJava并发编程】死锁并发编程】死锁 【【JavaJava并发编程】可重入内置锁并发编程】可重入内置锁 【【JavaJava并发编程】线程间协作:并发编程】线程间协作:waitwait、、notifynotify、、notifyAllnotifyAll 【【JavaJava并发编程】并发编程】notifynotify通知的遗漏通知的遗漏 【【JavaJava并发编程】并发编程】notifyAllnotifyAll造成的早期通知问题造成的早期通知问题 【【JavaJava并发编程】生产者并发编程】生产者——消费者模型消费者模型 【【JavaJava并发编程】深入并发编程】深入JavaJava内存模型(内存模型(11))——happen—before——happen—before规则及其对规则及其对DCLDCL问题的分析问题的分析 【【JavaJava并发编程】深入并发编程】深入JavaJava内存模型(内存模型(22))————内存操作规则总结内存操作规则总结 【【JavaJava并发编程】并发新特性并发编程】并发新特性—Executor—Executor框架与线程池框架与线程池 【【JavaJava并发编程】并发新特性并发编程】并发新特性—Lock—Lock锁与条件变量锁与条件变量 【【JavaJava并发编程】并发新特性并发编程】并发新特性——阻塞队列与阻塞栈阻塞队列与阻塞栈 【【JavaJava并发编程】并发新特性并发编程】并发新特性——障碍器障碍器CyclicBarrierCyclicBarrier 【【JavaJava并发编程】并发新特性并发编程】并发新特性——信号量信号量SemaphoreSemaphore
还剩69页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享pdf获得金币 ] 2 人已下载

下载pdf

pdf贡献者

dragonvsmm

贡献于2015-06-01

下载需要 10 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf