关于Java多线程的线程同步和线程通信的一些小问题(顺便分享几篇高质量的博文)
Java多线程的线程同步和线程通信的一些小问题(顺便分享几篇质量高的博文)
- 前言:在学习多线程时,遇到了一些问题,这里我将这些问题都分享出来,同时也分享了几篇其他博客主的博客,并且将我个人的理解也分享给大家,本人的水平有限,如果我的分析或者结论有错误希望大家一定要帮我指出来,我好能改正和提高。
一、对于线程同步和同步锁的理解(注:分享了三篇高质量的博客)
以下我精心的挑选了几篇博文,分别是关于对线程同步的理解和如何选择线程锁以及了解线程锁的作用范围。
<一>线程同步锁的选择
1. 这里我推荐下陆先生的Java代码质量改进之:同步对象的选择这篇博文。
2. 以上推荐的博文是以卖火车票为例,引出了非同步会导致的错误以及同步锁(监视器)应该如果选择,应该能够帮助大家理解同步锁。
<二>线程同伴锁用法及同步锁的作用范围
1. 这里我推荐下Java中synchronized同步锁用法及作用范围这篇博文。
2. 以上的博文将静态锁(字节码文件锁)和非静态锁(this)进行了对比,以及将线程非同步和线程同步下进行了对比,对大家了解线程锁的用法和作用范围有很大的帮助。
<三>对线程同步的理解
1. 这里我推荐下java中线程同步的理解(非常通俗易懂)这篇博文。
2. 以上推荐的博文以非常通俗易懂的观点解释了到时什么同步,将同步理解成了线程同步就是线程排队,而且举了一些日常生活中的例子来让大家理解到底什么是同伴。
<四>同步的作用场景
1. 并不是说同步在什么情况下都是好的,因为线程的同步会带来较低效率,因为线程同步就代表着线程要排队,即线程同步锁会带来的同步阻塞状态。
2. 因为CPU是随意切换线程的,当我们想让当前线程执行之后CPU不随意切换到其他线程,或者我们想要让某个线程的代码能够在完全执行之前不会被抢夺执行权,不会导致从而无法连续执行,那么我们就需要线程的帮助。
二、线程同步和线程通信的几个小细节
以下是我在学习线程同步时,遇到的小问题和我的小感悟
<一>线程sleep方法的基本用法和注意细节
1.sleep方法的基本用法
Thread.sleep(long millis),传入毫秒数(1秒 = 1000毫秒),在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。该线程不丢失任何监视器的所属权。(~注:Java技术文档的意思就是该线程休眠指定的毫秒数,而且休眠状态暂时失去CPU执行权,而且线程醒来后,该线程不会释放锁。)
/**
*
* Thread.sleep的计时器用法
*
*/
public class ThreadSleepTest { public static void main(String[] args) {
new Thread() {
@Override
public void run() {
int timeCount = 10;
while (timeCount >= 0) {
if (timeCount == 0) {
System.out.println("新年快乐!~");
break;
}
System.out.println("还剩" + timeCount-- + "秒");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
} }
2.sleep方法使用的位置选择
我在使用sleep方法时发现,当sleep的位置不一致所放的位置不同时,线程所运行的结果也是大不相同的,以下的代码是为了举例子,并不是说这个同步代码块就是应这样写(其实这段代码这么写是有很大的问题的,因为同步资源的选择不准确),至于同步资源的选择我在第二个大问题会讲到。
- A 以下的代码是sleep方法出现在了售票的代码块之前,这时出现了负票。(可能时间上也会导致差异,但是这里先不考虑时间时间因素,时间因素等下讲。)
package javase.week4; public class SellTrainTickets { public static void main(String[] args) {
new MyThread("窗口1").start();
new MyThread("窗口2").start();
new MyThread("窗口3").start();
new MyThread("窗口4").start();
} } class MyThread extends Thread { static int tickets = 100; public MyThread(String name) {
super(name);
} @Override
public void run() {
while (tickets > 0) {//假设这已经减到了1 1>0 然后窗口1 窗口2 窗口3 窗口4 都进入循环
try {
Thread.sleep(20); } catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (MyThread.class) {
System.out.println(getName() + "卖出了第" + tickets-- + "张票!");//然后0 -1 -2 -3,这时就出现了负票
}
}
}
}
- B 以下的代码是sleep方法出现在了售票的代码块之后,这里没有出现负票了,在票数100的情况下,而且时间是20毫秒的情况下,该段代码正好保证了时间点上的合理性,但是相同情况下sleep方法出现在输出售票之前的代码就会出现错误,即使改变时间和票数其sleep方法出现的位置错误,还是会导致了在票数为负的情况。(其实如果票数更改或者时间的改变也可能导致sleep方法出现在售票代码块之后的情况下负票的出现)
public class SellTrainTickets { public static void main(String[] args) {
new MyThread("窗口1").start();
new MyThread("窗口2").start();
new MyThread("窗口3").start();
new MyThread("窗口4").start();
} } class MyThread extends Thread { static int tickets = 100; public MyThread(String name) {
super(name);
} @Override
public void run() {
while (tickets > 0) {//假设这已经减到了0 5>0 然后窗口1 窗口2 窗口3 窗口4 都进入循环
synchronized (MyThread.class) {
System.out.println(getName() + "卖出了第" + tickets-- + "张票!");//然后 3 2 1 0
}
try {
Thread.sleep(20);//此时票数等于0,这时窗口1 窗口2 窗口3 窗口4 都处于休眠,然后如果这里的时间合理的话,再次判断的话,正好在等于0的时候,都没有线程再次进入循环,也就不会出现负票了 } catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
- C 总结下其实sleep方法出现的位置可能会影响到线程的结果,但是其实一般情况下是不会这么样去使用的,这里只是为了演示下sleep方法在位置不同的情况下出现的不同的结果,目的是为了让大家注意编程的细节。
3.sleep方法的传入参数的选择
sleep方法的传入的毫秒数对于线程的运行结果是有较大的影响的,最直接简单的影响就是让运行延迟了,但是除了这个以外其实也让线程的运行结果发生了变化,顺便分享一篇一篇高质量的博文Sleep(0)的妙用。
- A 当传入的参数为100时,以下是代码演示和执行结果,几乎每一个窗口(线程)都可以抢夺到运行权,而且比较分散。
public class TicketsThreadTest { public static void main(String[] args) {
new TicketThread("窗口1").start();
new TicketThread("窗口2").start();
new TicketThread("窗口3").start();
new TicketThread("窗口4").start();
} } class TicketThread extends Thread { public TicketThread(String name) {
super(name);
} private static int ticket = 100; public void run() {
while (true) {
synchronized (TicketThread.class) {
if (ticket <= 0) {
break;
}
System.out.println(getName() + "卖出了第" + ticket-- + "张票!");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
- B 当传入的参数为1时,以下是代码演示和执行结果,这次执行的效果就不是很好,并不是每一个线程都能很好的执行到,或者执行得不是很分散。
public class TicketsThreadTest { public static void main(String[] args) {
new TicketThread("窗口1").start();
new TicketThread("窗口2").start();
new TicketThread("窗口3").start();
new TicketThread("窗口4").start();
} } class TicketThread extends Thread { public TicketThread(String name) {
super(name);
} private static int ticket = 100; public void run() {
while (true) {
synchronized (TicketThread.class) {
if (ticket <= 0) {
break;
}
System.out.println(getName() + "卖出了第" + ticket-- + "张票!");
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
- C 总结以下时间参数对线程的结果的影响,以卖火车票为例,当我们在sleep方法中输入不同的参数,那么线程的运行结果就发生了变化,因为当我们给定的休眠期长了,那么线程的抢夺CPU执行权的速度就放缓了,此时运行的结果就变得比较分散,如果几乎没有休眠期那么抢到执行权的窗口(线程)可能还是处于领先优势,sleep方法其实让处于优先地位的暂时休眠让出了CPU执行权,然后sleep醒来又处于就绪状态来抢夺资源。这样不会让其他线程变成无法执行的尴尬境遇。当然后续可以使用wait和notify以及notifyAll的方法,让线程进行有规律地交替运行。
<二>明确需要同步的共享资源
如果这里同步的是代码块不是代码方法,那么这里需要对要同步的共享资源的选择要准确,如果选择得不准确会导致结果不理想。
- A 以下代码表示选择的共享代码块为售票的单个输出语句,此时可以看出结果,结果出现了负票
public class TicketsThreadTest { public static void main(String[] args) {
new TicketThread("窗口1").start();
new TicketThread("窗口2").start();
new TicketThread("窗口3").start();
new TicketThread("窗口4").start();
} } class TicketThread extends Thread { public TicketThread(String name) {
super(name);
} private static int ticket = 100; public void run() {
while (true) {
if (ticket <= 0) {
break;
}
synchronized (TicketThread.class) {
System.out.println(getName() + "卖出了第" + ticket-- + "张票!");
}
try {
Thread.sleep(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
- B 以下代码表示选择的共享代码块是while循环的整个代码块,此时可以看出结果,结果没有出现负票,而且经过了多次尝试也没有出现
public class TicketsThreadTest { public static void main(String[] args) {
new TicketThread("窗口1").start();
new TicketThread("窗口2").start();
new TicketThread("窗口3").start();
new TicketThread("窗口4").start();
} } class TicketThread extends Thread { public TicketThread(String name) {
super(name);
} private static int ticket = 100; public void run() {
while (true) {
synchronized (TicketThread.class) {
if (ticket <= 0) {
break;
}
System.out.println(getName() + "卖出了第" + ticket-- + "张票!");
try {
Thread.sleep(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
- C 总结在选择需要同步的代码块是一定要注意哪些代码块(资源是需要共享的),这里就要判断下这些代码是否是需要共享,将需要共享的资源用synchronized代码块包起来
<三>线程通信之while和if的选择
- A 以下的代码使用的是if选择结构进行线程通信之间的判断,可以发现三个线程之间没有有规律地交替进行。
package javase.week4; /**
*
* 三个线程之间的通信使用if选择语句
*
*/
public class ComunicatedThreadTest {
public static void main(String[] args) {
Printer1121 p = new Printer1121();
new Thread() {
@Override
public void run() {
while (true) {
try {
p.print1();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}.start();
new Thread() {
@Override
public void run() {
while (true) {
try {
p.print2();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}.start(); new Thread() {
@Override
public void run() {
while (true) {
try {
p.print3();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}.start();
} } class Printer1121 {
private int flag = 1; public void print1() throws Exception {
synchronized (this) {
if (flag != 1) {
this.wait();
}
Thread.sleep(100);
System.out.print(1);
System.out.print(2);
System.out.print(3);
System.out.print(4);
System.out.print(5);
System.out.println();
flag = 2;
this.notifyAll();
}
} public void print2() throws Exception {
synchronized (this) {
if (flag != 2) {
this.wait();
}
Thread.sleep(100);
System.out.print("a");
System.out.print("b");
System.out.print("c");
System.out.print("d");
System.out.print("e");
System.out.println();
flag = 3;
this.notifyAll();
}
} public void print3() throws Exception {
synchronized (this) {
if (flag != 3) {
this.wait();
}
Thread.sleep(100);
System.out.print("A");
System.out.print("B");
System.out.print("C");
System.out.print("D");
System.out.print("E");
System.out.println();
flag = 1;
this.notifyAll();
}
}
}
- B 以下是用while对通信条件进行循环判断的,可以发现三个线程是有规律地循环进行运行的。
package javase.week4;
/**
*
* 三个线程之间的通信使用while循环判断语句
*
*/
public class ComunicatedThreadTest {
public static void main(String[] args) {
Printer1121 p = new Printer1121();
new Thread() {
@Override
public void run() {
while (true) {
try {
p.print1();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}.start();
new Thread() {
@Override
public void run() {
while (true) {
try {
p.print2();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}.start(); new Thread() {
@Override
public void run() {
while (true) {
try {
p.print3();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}.start();
} } class Printer1121 {
private int flag = 1; public void print1() throws Exception {
synchronized (this) {
while (flag != 1) {
this.wait();
}
Thread.sleep(100);
System.out.print(1);
System.out.print(2);
System.out.print(3);
System.out.print(4);
System.out.print(5);
System.out.println();
flag = 2;
this.notifyAll();
}
} public void print2() throws Exception {
synchronized (this) {
while (flag != 2) {
this.wait();
}
Thread.sleep(100);
System.out.print("a");
System.out.print("b");
System.out.print("c");
System.out.print("d");
System.out.print("e");
System.out.println();
flag = 3;
this.notifyAll();
}
} public void print3() throws Exception {
synchronized (this) {
while (flag != 3) {
this.wait();
}
Thread.sleep(100);
System.out.print("A");
System.out.print("B");
System.out.print("C");
System.out.print("D");
System.out.print("E");
System.out.println();
flag = 1;
this.notifyAll();
}
}
}
- C 这里进行下原因分析,为什么会出现这样的情况?首先wait方法在同步代码块里被调用了,那么此时调用者直接在wait处等待了,然后等待下次被notify或者notifyAll唤醒。而if选择结构在判断一次之后就顺序执行了,当线程被唤醒时,我们希望的是再次判断一次条件看能够继续进行,但是if无法做到,因为上次已经判断正确了,它只会向下继续执行此时就会又出现随意无规律交替运行,但是while是循环判断,即使判断过一次了,但是每次执行完它会再次判断,这时就会让三个线程的运行结果有规律了。
关于Java多线程的线程同步和线程通信的一些小问题(顺便分享几篇高质量的博文)的更多相关文章
- Java 多线程基础(五)线程同步
Java 多线程基础(五)线程同步 当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题. 要解决上述多线程并发访问一个资源的安全性问题,Java中提供了同步机制 ...
- Java多线程基础:进程和线程之由来
转载: Java多线程基础:进程和线程之由来 在前面,已经介绍了Java的基础知识,现在我们来讨论一点稍微难一点的问题:Java并发编程.当然,Java并发编程涉及到很多方面的内容,不是一朝一夕就能够 ...
- Java多线程之~~~使用Exchanger在线程之间交换数据[这个结合多线程并行会有解决很多问题]生产者消费者模型
http://blog.csdn.net/a352193394/article/details/39503857 Java多线程之~~~使用Exchanger在线程之间交换数据[这个结合多线程并行会 ...
- Java多线程-两种常用的线程计数器CountDownLatch和循环屏障CyclicBarrier
Java多线程编程-(1)-线程安全和锁Synchronized概念 Java多线程编程-(2)-可重入锁以及Synchronized的其他基本特性 Java多线程编程-(3)-从一个错误的双重校验锁 ...
- “全栈2019”Java多线程第十三章:线程组ThreadGroup详解
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...
- “全栈2019”Java多线程第十一章:线程优先级详解
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...
- “全栈2019”Java多线程第九章:判断线程是否存活isAlive()详解
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...
- “全栈2019”Java多线程第五章:线程睡眠sleep()方法详解
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...
- 1、Java多线程基础:进程和线程之由来
Java多线程基础:进程和线程之由来 在前面,已经介绍了Java的基础知识,现在我们来讨论一点稍微难一点的问题:Java并发编程.当然,Java并发编程涉及到很多方面的内容,不是一朝一夕就能够融会贯通 ...
随机推荐
- VB开发类似IIS简易的WebServer,代码不到100行
最近遇到三个人问关于VB写网页服务器的问题,所以今天抽时间写一下,演示其实没有多复杂. 代码里自定义的方法只有四个,没有公共变量绕来绕去,该注释的也都注释了. 想扩展更复杂的功能,就需要自己补脑HTT ...
- web测试注意点
关于网页测试我们需要注意的地方有: 1.每次测试之前都需要代码更新.清理缓存,测试数据使用新数据. 2.各模块的信息归类是否正确.比如进入一级栏目或二级栏目的列表页,查看左侧栏目名称,右侧文章标题及内 ...
- Java课程课后作业190315之最大连续子数组(二维数组版)
,, 在本周的课堂上,老师再一次提高了要求,将一维数组升级成为了二维数组,然后求出块状的连续子数组. 一开始还想着借鉴之前球一维数组的O(n)的算法,后来还是没有找到头绪,舍友讲了自己的办法,但是没有 ...
- Spring-Boot 使用 Jedis 操作 Redis
背景: 1.Redis 之前学了个皮毛 还忘的差不多了,感觉公司项目中的Redis用的真的牛逼,so 需要深造. 2.有个同事在搞Jedis,勾起了我对知识的向往,不会用,但是很渴望. 过程: 1.改 ...
- bootstrap-treeview分级展示列表树的实现
html页面: 要引用 "/webapp/common/css/bootstrap-treeview.css" "/webapp/common/js/bootstrap- ...
- DocX Xceed.Words.NET操作Word,插入特殊符号
x 传送门,我们走... DocX的Github传送门 介绍一 介绍二 写入特殊符号 开始... 自己做一个工具,要导出Word的,当时刚开始想使用Xceed.Words.NET.dll第三方插件进行 ...
- 为Vue.js添加友好日志
const isDebugEnabled = process.env.NODE_ENV !== "production"; const isInfoEnabled = true; ...
- 2018-2019-2 网络对抗技术 20165317 Exp3 免杀原理与实践
2018-2019-2 网络对抗技术 20165317 Exp3 免杀原理与实践 实验内容 任务一:正确使用msf编码器,msfvenom生成如jar之类的其他文件,veil-evasion,自己利用 ...
- Mac上配置GTK环境
Mac上配置GTK环境 安装command line工具, 如果安装了Xcode, 就直接跳过该步骤 安装Homebrew 使用brew install pkg-config 使用brew insta ...
- 新建的小程序没有app.js,app.json等文件
因为在创建的时候没有勾选 建立普通快速启动模板,而我在创建的时候没有发现有这个选项可以选择. 解决办法:把之前创建过的文件夹整个删掉,不能只删内容.然后再重新新建项目,就会出现 建立普通快速启动模板 ...