关于Java多线程的线程同步和线程通信的一些小问题(顺便分享几篇高质量的博文)

时间:2022-10-31 13:49:26

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,这时就出现了负票
}
}
}
}

关于Java多线程的线程同步和线程通信的一些小问题(顺便分享几篇高质量的博文)

      • 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();
}
}
}
}

关于Java多线程的线程同步和线程通信的一些小问题(顺便分享几篇高质量的博文)

      • 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();
}
}
}
}
}

关于Java多线程的线程同步和线程通信的一些小问题(顺便分享几篇高质量的博文)

      • 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();
}
}
}
}
}

关于Java多线程的线程同步和线程通信的一些小问题(顺便分享几篇高质量的博文)

      • 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();
}
}
}
}

关于Java多线程的线程同步和线程通信的一些小问题(顺便分享几篇高质量的博文)

      • 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();
}
}
}
}
}

关于Java多线程的线程同步和线程通信的一些小问题(顺便分享几篇高质量的博文)

      • 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();
}
}
}

关于Java多线程的线程同步和线程通信的一些小问题(顺便分享几篇高质量的博文)

      • 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();
}
}
}

关于Java多线程的线程同步和线程通信的一些小问题(顺便分享几篇高质量的博文)

      • C 这里进行下原因分析,为什么会出现这样的情况?首先wait方法在同步代码块里被调用了,那么此时调用者直接在wait处等待了,然后等待下次被notify或者notifyAll唤醒。而if选择结构在判断一次之后就顺序执行了,当线程被唤醒时,我们希望的是再次判断一次条件看能够继续进行,但是if无法做到,因为上次已经判断正确了,它只会向下继续执行此时就会又出现随意无规律交替运行,但是while是循环判断,即使判断过一次了,但是每次执行完它会再次判断,这时就会让三个线程的运行结果有规律了。

关于Java多线程的线程同步和线程通信的一些小问题(顺便分享几篇高质量的博文)的更多相关文章

  1. Java 多线程基础(五)线程同步

    Java 多线程基础(五)线程同步 当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题. 要解决上述多线程并发访问一个资源的安全性问题,Java中提供了同步机制 ...

  2. Java多线程基础:进程和线程之由来

    转载: Java多线程基础:进程和线程之由来 在前面,已经介绍了Java的基础知识,现在我们来讨论一点稍微难一点的问题:Java并发编程.当然,Java并发编程涉及到很多方面的内容,不是一朝一夕就能够 ...

  3. Java多线程之~~~使用Exchanger在线程之间交换数据&lbrack;这个结合多线程并行会有解决很多问题&rsqb;生产者消费者模型

    http://blog.csdn.net/a352193394/article/details/39503857  Java多线程之~~~使用Exchanger在线程之间交换数据[这个结合多线程并行会 ...

  4. Java多线程-两种常用的线程计数器CountDownLatch和循环屏障CyclicBarrier

    Java多线程编程-(1)-线程安全和锁Synchronized概念 Java多线程编程-(2)-可重入锁以及Synchronized的其他基本特性 Java多线程编程-(3)-从一个错误的双重校验锁 ...

  5. &OpenCurlyDoubleQuote;全栈2019”Java多线程第十三章:线程组ThreadGroup详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  6. &OpenCurlyDoubleQuote;全栈2019”Java多线程第十一章:线程优先级详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  7. &OpenCurlyDoubleQuote;全栈2019”Java多线程第九章:判断线程是否存活isAlive&lpar;&rpar;详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  8. &OpenCurlyDoubleQuote;全栈2019”Java多线程第五章:线程睡眠sleep&lpar;&rpar;方法详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  9. 1、Java多线程基础:进程和线程之由来

    Java多线程基础:进程和线程之由来 在前面,已经介绍了Java的基础知识,现在我们来讨论一点稍微难一点的问题:Java并发编程.当然,Java并发编程涉及到很多方面的内容,不是一朝一夕就能够融会贯通 ...

随机推荐

  1. Openxml入门---Openxm读取Excel数据

    Openxml读取Excel数据: 有些问题,如果当Cell 里面是 日期和浮点型的话,对应的Cell.DataType==Null,对应的时间会转换为一个浮点型,对于这块可以通过DateTime.F ...

  2. label标签跳出循环

    出场: 首先我们来说说为什么需要label标签,虽然我们已经知道有break,continue跳出循环,但如果是多重循环那么它们就显的无能为力了,所以就出现了label这个标签来为我们服务. 我们先来 ...

  3. winston日志管理1

    Usage There are two different ways to use winston: directly via the default logger, or by instantiat ...

  4. 常用http请求状态码含义

    1**  ----临时响应 2**  ----成功响应 3**  ----重定向 4**  ----请求错误 5**  ----服务器错误 常用的几个如下: 200---服务器成功返回网页 301-- ...

  5. 使用RMAN从磁带库恢复归档文件

      最近用RMAN对部分归档日志进行了恢复,在此记录恢复过程 由于不能透漏数据库信息,故举例如下: ORACLE_SID=hrdb 恢复目标路径:/NewRmanbak/restore_archive ...

  6. 谈数据库索引和Sqlite中索引的使用

    要使用索引对数据库的数据操作进行优化,那必须明确几个问题:1.什么是索引2.索引的原理3.索引的优缺点4.什么时候需要使用索引,如何使用围绕这几个问题,来探究索引在数据库操作中所起到的作用. 1.数据 ...

  7. 让c像python一样可以在命令行写代码并且编译

    在你亲爱的.bashrc/.zshrc中添加 ###C###go_libs="-lm"go_flags="-g -Wall -include allheads.h -O3 ...

  8. 树形dp-CF-337D&period; Book of Evil

    题目链接: http://codeforces.com/problemset/problem/337/D 题目大意: 给一棵树,m个点,一个距离d,求有多少个点A,使得A到所有的m个点距离都不超过d. ...

  9. 九度oj题目1207:质因数的个数

    题目描述: 求正整数N(N>1)的质因数的个数. 相同的质因数需要重复计算.如120=2*2*2*3*5,共有5个质因数. 输入: 可能有多组测试数据,每组测试数据的输入是一个正整数N,(1&l ...

  10. &lbrack;TCP&rsqb; 网络协议流程图

    之前在跟别人讲协议的时候总是找不到类似的图,这次再看python网络编程书籍的时候找到了一个,留存一份. 清晰的看到不同协议在不同层的传输过程!