在JAVA中,是没有类似于PV操作、进程互斥等相关的方法的。JAVA的进程同步是通过synchronized()来实现的,需要说明的是,JAVA的synchronized()方法类似于操作系统概念中的互斥内存块,在JAVA中的Object类型中,都是带有一个内存锁的,在有线程获取该内存锁后,其它线程无法访问该内存,从而实现JAVA中简单的同步、互斥操作。明白这个原理,就能理解为什么synchronized(this)与synchronized(static XXX)的区别了,synchronized就是针对内存区块申请内存锁,this关键字代表类的一个对象,所以其内存锁是针对相同对象的互斥操作,而static成员属于类专有,其内存空间为该类所有成员共有,这就导致synchronized()对static成员加锁,相当于对类加锁,也就是在该类的所有成员间实现互斥,在同一时间只有一个线程可访问该类的实例。如果只是简单的想要实现在JAVA中的线程互斥,明白这些基本就已经够了。但如果需要在线程间相互唤醒的话就需要借助Object.wait(), Object.nofity()了。

Obj.wait(),与Obj.notify()必须要与synchronized(Obj)一起使用,也就是wait,与notify是针对已经获取了Obj锁进行操作,从语法角度来说就是Obj.wait(),Obj.notify必须在synchronized(Obj){...}语句块内。从功能上来说wait就是说线程在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程调用对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行。相应的notify()就是对对象锁的唤醒操作。但有一点需要注意的是notify()调用后,并不是马上就释放对象锁的,而是在相应的synchronized(){}语句块执行结束,自动释放锁后,JVM会在wait()对象锁的线程中随机选取一线程,赋予其对象锁,唤醒线程,继续执行。这样就提供了在线程间同步、唤醒的操作。Thread.sleep()与Object.wait()二者都可以暂停当前线程,释放CPU控制权,主要的区别在于Object.wait()在释放CPU同时,释放了对象锁的控制。

单单在概念上理解清楚了还不够,需要在实际的例子中进行测试才能更好的理解。对Object.wait(),Object.notify()的应用最经典的例子,应该是三线程打印ABC的问题了吧,这是一道比较经典的面试题,题目要求如下:

建立三个线程,A线程打印10次A,B线程打印10次B,C线程打印10次C,要求线程同时运行,交替打印10次ABC。这个问题用Object的wait(),notify()就可以很方便的解决。代码如下:

[java] view plaincopy
  1. public class MyThreadPrinter2 implements Runnable {
  2. private String name;
  3. private Object prev;
  4. private Object self;
  5. private MyThreadPrinter2(String name, Object prev, Object self) {
  6. this.name = name;
  7. this.prev = prev;
  8. this.self = self;
  9. }
  10. @Override
  11. public void run() {
  12. int count = 10;
  13. while (count > 0) {
  14. synchronized (prev) {
  15. synchronized (self) {
  16. System.out.print(name);
  17. count--;
  18. self.notify();
  19. }
  20. try {
  21. prev.wait();
  22. } catch (InterruptedException e) {
  23. e.printStackTrace();
  24. }
  25. }
  26. }
  27. }
  28. public static void main(String[] args) throws Exception {
  29. Object a = new Object();
  30. Object b = new Object();
  31. Object c = new Object();
  32. MyThreadPrinter2 pa = new MyThreadPrinter2("A", c, a);
  33. MyThreadPrinter2 pb = new MyThreadPrinter2("B", a, b);
  34. MyThreadPrinter2 pc = new MyThreadPrinter2("C", b, c);
  35. new Thread(pa).start();
  36. new Thread(pb).start();
  37. new Thread(pc).start();    }
  38. }

先来解释一下其整体思路,从大的方向上来讲,该问题为三线程间的同步唤醒操作,主要的目的就是ThreadA->ThreadB->ThreadC->ThreadA循环执行三个线程。为了控制线程执行的顺序,那么就必须要确定唤醒、等待的顺序,所以每一个线程必须同时持有两个对象锁,才能继续执行。一个对象锁是prev,就是前一个线程所持有的对象锁。还有一个就是自身对象锁。主要的思想就是,为了控制执行的顺序,必须要先持有prev锁,也就前一个线程要释放自身对象锁,再去申请自身对象锁,两者兼备时打印,之后首先调用self.notify()释放自身对象锁,唤醒下一个等待线程,再调用prev.wait()释放prev对象锁,终止当前线程,等待循环结束后再次被唤醒。运行上述代码,可以发现三个线程循环打印ABC,共10次。程序运行的主要过程就是A线程最先运行,持有C,A对象锁,后释放A,C锁,唤醒B。线程B等待A锁,再申请B锁,后打印B,再释放B,A锁,唤醒C,线程C等待B锁,再申请C锁,后打印C,再释放C,B锁,唤醒A。看起来似乎没什么问题,但如果你仔细想一下,就会发现有问题,就是初始条件,三个线程按照A,B,C的顺序来启动,按照前面的思考,A唤醒B,B唤醒C,C再唤醒A。但是这种假设依赖于JVM中线程调度、执行的顺序。具体来说就是,在main主线程启动ThreadA后,需要在ThreadA执行完,在prev.wait()等待时,再切回线程启动ThreadB,ThreadB执行完,在prev.wait()等待时,再切回主线程,启动ThreadC,只有JVM按照这个线程运行顺序执行,才能保证输出的结果是正确的。而这依赖于JVM的具体实现。考虑一种情况,如下:如果主线程在启动A后,执行A,过程中又切回主线程,启动了ThreadB,ThreadC,之后,由于A线程尚未释放self.notify,也就是B需要在synchronized(prev)处等待,而这时C却调用synchronized(prev)获取了对b的对象锁。这样,在A调用完后,同时ThreadB获取了prev也就是a的对象锁,ThreadC的执行条件就已经满足了,会打印C,之后释放c,及b的对象锁,这时ThreadB具备了运行条件,会打印B,也就是循环变成了ACBACB了。这种情况,可以通过在run中主动释放CPU,来进行模拟。代码如下:

[java] view plaincopy
  1. public void run() {
  2. int count = 10;
  3. while (count > 0) {
  4. synchronized (prev) {
  5. synchronized (self) {
  6. System.out.print(name);
  7. count--;
  8. try{
  9. Thread.sleep(1);
  10. }
  11. catch (InterruptedException e){
  12. e.printStackTrace();
  13. }
  14. self.notify();
  15. }
  16. try {
  17. prev.wait();
  18. } catch (InterruptedException e) {
  19. e.printStackTrace();
  20. }
  21. }
  22. }
  23. }

运行后的打印结果就变成了ACBACB了。为了避免这种与JVM调度有关的不确定性。需要让A,B,C三个线程以确定的顺序启动,最终代码如下:

[java] view plaincopy
  1. public class MyThreadPrinter2 implements Runnable {
  2. private String name;
  3. private Object prev;
  4. private Object self;
  5. private MyThreadPrinter2(String name, Object prev, Object self) {
  6. this.name = name;
  7. this.prev = prev;
  8. this.self = self;
  9. }
  10. @Override
  11. public void run() {
  12. int count = 10;
  13. while (count > 0) {
  14. synchronized (prev) {
  15. synchronized (self) {
  16. System.out.print(name);
  17. count--;
  18. try{
  19. Thread.sleep(1);
  20. }
  21. catch (InterruptedException e){
  22. e.printStackTrace();
  23. }
  24. self.notify();
  25. }
  26. try {
  27. prev.wait();
  28. } catch (InterruptedException e) {
  29. e.printStackTrace();
  30. }
  31. }
  32. }
  33. }
  34. public static void main(String[] args) throws Exception {
  35. Object a = new Object();
  36. Object b = new Object();
  37. Object c = new Object();
  38. MyThreadPrinter2 pa = new MyThreadPrinter2("A", c, a);
  39. MyThreadPrinter2 pb = new MyThreadPrinter2("B", a, b);
  40. MyThreadPrinter2 pc = new MyThreadPrinter2("C", b, c);
  41. new Thread(pa).start();
  42. Thread.sleep(10);
  43. new Thread(pb).start();
  44. Thread.sleep(10);
  45. new Thread(pc).start();
  46. Thread.sleep(10);
  47. }
  48. }

这样才可以完美的解决该问题。通过这个例子也是想说明一下,很多理论、概念如Obj.wait(),Obj.notify()等,理解起来,比较简单,但是在实际的应用当中,这里却是往往出现问题的地方。需要更加深入的理解。并在解决问题的过程中不断加深对概念的掌握。

——欢迎转载,请注明出处 http://blog.csdn.net/zyplus——

JAVA wait(), notify(),sleep详解相关推荐

  1. Java高并发编程详解系列-Java线程入门

    根据自己学的知识加上从各个网站上收集的资料分享一下关于java高并发编程的知识点.对于代码示例会以Maven工程的形式分享到个人的GitHub上面.   首先介绍一下这个系列的东西是什么,这个系列自己 ...

  2. Java JUC并发编程详解

    Java JUC并发编程详解 1. JUC概述 1.1 JUC简介 1.2 进程与线程 1.2 并发与并行 1.3 用户线程和守护线程 2. Lock接口 2.1 Synchronized 2.2 什 ...

  3. JAVA 多线程并发超详解

    JAVA 多线程并发超详解(未完,下一篇文章还有) 1. JAVA 多线程并发 1.1.1. JAVA 并发知识库 1.1.2. JAVA 线程实现/创建方式 1.1.2.1. 继承 Thread 类 ...

  4. Java单元测试之JUnit4详解

    2019独角兽企业重金招聘Python工程师标准>>> Java单元测试之JUnit4详解 与JUnit3不同,JUnit4通过注解的方式来识别测试方法.目前支持的主要注解有: @B ...

  5. java -jar 和 -cp详解

    java -jar 和 -cp详解 命令行执行程序 假如我们有一个程序,把它打包成Test.jar,如何运行才能成功输出Hello World package com.test; public cla ...

  6. java访问修饰符详解——学java,零基础不怕,不只要理论,更要实践+项目,a href=http://www.bjweixin.com太原维信科技提供 /a...

    java访问修饰符详解--学java,零基础不怕,不只要理论,更要实践+项目 <a href=http://www.bjweixin.com>太原维信科技提供 </a> pub ...

  7. Java编程配置思路详解

    Java编程配置思路详解 SpringBoot虽然提供了很多优秀的starter帮助我们快速开发,可实际生产环境的特殊性,我们依然需要对默认整合配置做自定义操作,提高程序的可控性,虽然你配的不一定比官 ...

  8. Java 8 Stream API详解--转

    原文地址:http://blog.csdn.net/chszs/article/details/47038607 Java 8 Stream API详解 一.Stream API介绍 Java 8引入 ...

  9. 你真的弄明白了吗?Java并发之AQS详解

    你真的弄明白了吗?Java并发之AQS详解 带着问题阅读 1.什么是AQS,它有什么作用,核心思想是什么 2.AQS中的独占锁和共享锁原理是什么,AQS提供的锁机制是公平锁还是非公平锁 3.AQS在J ...

  10. java定时任务框架elasticjob详解

    这篇文章主要介绍了java定时任务框架elasticjob详解,Elastic-Job是ddframe中dd-job的作业模块中分离出来的分布式弹性作业框架.该项目基于成熟的开源产品Quartz和Zo ...

最新文章

  1. mac 查看端口_如何重置mac上的系统管理控制器smc教程
  2. DNN开发中的一些细节(3)
  3. 模板:Link Cut Tree(LCT)
  4. Eclipse插件中的SLF4J登录
  5. php实现url伪静态化,全过程详解
  6. Python:通过命令行发送新浪微博
  7. 为什么企业宁愿花 15K 重新招人,也不愿意花 10K 留住老测试员?
  8. ACM-ICPC 2018 南京赛区网络预赛 B The writing on the wall(暴力)
  9. 阶段1 语言基础+高级_1-3-Java语言高级_09-基础加强_第3节 注解_16_注解_自定义注解_元注解...
  10. html直播源怎么转码,直播源列表格式转换 - 黑鸟博客
  11. EndNote X7集成到office word2016(原有EndNote选项卡,却突然消失)
  12. 世界500强榜单出炉:中国公司首进三强 沃尔玛居首
  13. CAD画图软件测试初学者,CAD制图初学入门:基础操作 平面绘图练习100题,新手必备...
  14. 915M无线通讯模块 数传 SI4463 SX1278
  15. 怎么查询英语四六级证书编号
  16. 老祖宗的绝招对治腰疼,别说你没听过
  17. Windows系统_ARM处理器无法打开客户端问题解决
  18. gis统计百分比_[ARCGIS]地统计分析(二)
  19. 移动端开发基本知识之touch.js,FastClick.js源码分析
  20. 2019阿里校招测评题 光明小学完全图最短路径问题

热门文章

  1. Common Lisp
  2. 真正的善良,在于收钱办事
  3. 使用K-Means算法将图像压缩6倍!
  4. 职高c语言补充程序,江苏省2013年职高对口升学《C语言程序设计》冲刺模拟试题 免费.doc...
  5. MySQL表设计(表的关联关系)
  6. 什么是restful api
  7. java-程序流程控制
  8. ORACLE创建表空间、用户语句
  9. NestedScrollView嵌套ListView可行性总结
  10. 4.24口语习语 听力复习