Java多线程编程(同步、死锁、生产消费):

关于线程同步以及死锁问题:

线程同步概念:是指若干个线程对象并行进行资源的访问时实现的资源处理保护操作;

线程死锁概念:是指两个线程都在等待对方先完成,造成程序的停止的状态;

先了解相应的概念,后面深入理解。

同步:

举个例子:还是卖票问题(经典️)

  1. 不存在同步

  2. 开启三个线程(售票员)测试

 package com.xbhog;
 class MyThread implements Runnable {// 定义线程执行类
     private int ticket = 3;// 总票数为6张
     @Override
     public void run() {
         while (true) { // 持续卖票
             if (this.ticket > 0) { // 还有剩余票
                 try {
                     Thread.sleep(100); // 模拟网络延迟
                } catch (InterruptedException e) {
                     e.printStackTrace();
                }
                 //获取当前线程的名字
                 System.out.println(Thread.currentThread().getName() +
                         "卖票,ticket = " + this.ticket--);
            } else {
                 System.out.println("***** 票已经卖光了 *****");
                 break;// 跳出循环
            }
        }
    }
 }
 public class Java多线程核心 {
     public static void main(String[] args) throws Exception {
         MyThread mt = new MyThread();
         new Thread(mt, "售票员A").start(); // 开启卖票线程
         new Thread(mt, "售票员B").start(); // 开启卖票线程
         new Thread(mt, "售票员C").start(); // 开启卖票线程
    }
 }

结果:

第一次随机运行: 第二次随机运行:
售票员B卖票,ticket = 2 售票员C卖票,ticket = 3 售票员A卖票,ticket = 3 售票员A卖票,ticket = 1 售票员B卖票,ticket = -1 * 票已经卖光了 * 售票员C卖票,ticket = 0 * 票已经卖光了 * * 票已经卖光了 * 售票员B卖票,ticket = 1 * 票已经卖光了 * 售票员A卖票,ticket = 3 * 票已经卖光了 * 售票员C卖票,ticket = 2 * 票已经卖光了 *

存在上述原因是因为在代码中两个地方存在多线程访问时出现模糊的问题:

  1. this.ticket>0;

  2. this,ticket--;

假设现在剩余的票数为1张;当第一个线程满足售票的条件的时候(此时还未减少票数),其他的线程也可能同时满足售票的条件,这样同时进行自减减就可能造成负数!

解决上述问题就需要采用线程同步技术实现;

首先需要明确,在Java中实现线程同步(synchronized)的方法有两个:

  1. 同步代码块(同步策略加在方法内部)

     package com.xbhog.多线程1;
     class MyThread implements Runnable { // 定义线程执行类
         private int ticket = 3; // 总票数为6张
         @Override
         public void run() {
             while (true) { // 持续卖票
                 synchronized(this) { // 同步代码块
                     if (this.ticket > 0) { // 还有剩余票
                         try {
                             Thread.sleep(100); // 模拟网络延迟
                        } catch (InterruptedException e) {
                             e.printStackTrace();
                        }
                         System.out.println(Thread.currentThread().getName() +
                                 "卖票,ticket = " + this.ticket--);
                    } else {
                         System.out.println("***** 票已经卖光了 *****");
                         break; // 跳出循环
                    }
                }
            }
        }
     }
     public class Java多线程同步代码块 {
         public static void main(String[] args) {
             MyThread mt = new MyThread();
             new Thread(mt, "售票员A").start(); // 开启卖票线程
             new Thread(mt, "售票员B").start(); // 开启卖票线程
             new Thread(mt, "售票员C").start(); // 开启卖票线程
        }
     }
     售票员A卖票,ticket = 3
     售票员C卖票,ticket = 2
     售票员B卖票,ticket = 1
     ***** 票已经卖光了 *****
     ***** 票已经卖光了 *****
     ***** 票已经卖光了 *****
  2. 同步方法(同步策略加在方法上)

     class MyThread implements Runnable {                        // 定义线程执行类
      private int ticket = 3; // 总票数为6张
      @Override
      public void run() {
      while (this.sale()) { // 调用同步方法
      ;
      }
      }
      public synchronized boolean sale() { // 售票操作
      if (this.ticket > 0) {
      try {
      Thread.sleep(100); // 模拟网络延迟
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      System.out.println(Thread.currentThread().getName() +
      "卖票,ticket = " + this.ticket--);
      return true;
      } else {
      System.out.println("***** 票已经卖光了 *****");
      return false;
      }
      }
     }
     public class ThreadDemo {
      public static void main(String[] args) throws Exception {
      MyThread mt = new MyThread();
      new Thread(mt, "售票员A").start(); // 开启卖票线程
      new Thread(mt, "售票员B").start(); // 开启卖票线程
      new Thread(mt, "售票员C").start(); // 开启卖票线程
      }
     }
     售票员A卖票,ticket = 3
     售票员C卖票,ticket = 2
     售票员B卖票,ticket = 1
     ***** 票已经卖光了 *****
     ***** 票已经卖光了 *****
     ***** 票已经卖光了 *****

同步的本质:在同一个时间段只允许有一个线程执行资源,所以在此线程对象未执行完的过程中其他线程对象将处于等待的状态。

同步的优点与缺点:

  1. 可以保证数据的准确性

  2. 数据线程的访问安全


  3. 程序的处理性能下降

死锁:

实例:

假如现在又张三想要李四的画,李四想要张三的书,那么张三对李四说:把你的画给我,我就给你书;

李四对张三说:把你的书给我,我就给你画;

此时:张三在等待李四,李四在等待张三,两人一直等待下去形成死锁;

观察线程的死锁:(实现张三李四)

 package com.xbhog.死锁;
 ​
 class Book {
     public synchronized void tell(Painting paint) { // 同步方法
         System.out.println("张三对李四说:把你的画给我,我就给你书,不给画不给书!");
         paint.get();
    }
     public synchronized void get() { // 同步方法
         System.out.println("张三得到了李四的画开始认真欣赏。");
    }
 }
 class Painting {
     public synchronized void tell(Book book) { // 同步方法
         System.out.println("李四对张三说:把你的书给我,我就给你画,不给书不给画!");
         book.get();
    }
     public synchronized void get() { // 同步方法
         System.out.println("李四得到了张三的书开始认真阅读。");
    }
 }
 public class DeadLock implements Runnable{
     private Book book = new Book();
     private Painting paint = new Painting();
     public DeadLock() {
         new Thread(this).start();
         book.tell(paint);
    }
     @Override
     public void run() {
         paint.tell(book);
    }
     public static void main(String[] args) {
         new DeadLock() ;
    }
 }

由于现在电脑的配置问题,该代码有可能在一次运行中展示不出效果来,需要多次运行观察效果;

效果图:

由此引申出了生产者与消费者模型。

生产者与消费者问题:

首先需要明确生产者与消费者为两个线程对象,是对同一资源进行数据的保存与读取;

基本操作是:生产者生产一个资源,消费者则取走一个资源,一一对应。

对应类关系图:

我们需要设想一个问题,如果不加任何操作的话,会出现什么问题?

  1. 数据错位:当生产者线程只是开辟了一个栈空间保存信息名称,在想存数据但是还没存数据的时候切换到了消费者线程上,那么消费者线程将会把这个信息名称与上个信息的内容进行结合联系,这样就造成了数据的错位。

  2. 重复数据:当生产者放了若干次的数据,消费者才开始取数据,或者消费者取完,但生产者还没生产新数据时又取了直接已经取过得数据。

解决以上两个问题需要涉及到以下两个知识点:

  1. 设置同步代码块或设置同步方法>>>解决数据错误问题

  2. Object线程等待与唤醒>>>解决数据重复设置以及重复取出的问题

增加数据同步方法或同步代码块:

在本程序中,生产者与消费者代表的都是线程对象,所以同步操作只能在Message类中,可以将set与get方法设置为单独的同步方法。

 class Message {
  private String title ; // 保存信息的标题
  private String content ; // 保存信息的内容
  public synchronized void set(String title, String content) {
  this.title = title;
  try {
  Thread.sleep(200);
  } catch (InterruptedException e) {
  e.printStackTrace();
  }
  this.content = content;
  }
  public synchronized String get() {
  try {
  Thread.sleep(100);
  } catch (InterruptedException e) {
  e.printStackTrace();
  }
  return this.title + " --> " + this.content;
  }
  // setter、getter略
 }
 class Producer implements Runnable { // 定义生产者
  private Message msg = null ;
  public Producer(Message msg) {
  this.msg = msg ;
  }
  @Override
  public void run() {
  for (int x = 0; x < 50; x++) { // 生产50次数据
  if (x % 2 == 0) {
  this.msg.set("xbhog","22") ; // 设置属性
  } else {
  this.msg.set("xbhog","www.cnblog.cn/xbhog") ; // 设置属性
  }
  }
  }
 }
 class Consumer implements Runnable { // 定义消费者
  private Message msg = null ;
  public Consumer (Message msg) {
  this.msg = msg ;
  }
  @Override
  public void run() {
  for (int x = 0; x < 50; x++) { // 取走50次数据
  System.out.println(this.msg.get()); // 取得属性
  }
  }
 }
 public class ThreadDemo {
  public static void main(String[] args) throws Exception {
  Message msg = new Message() ; // 定义Message对象,用于保存和取出数据
  new Thread(new Producer(msg)).start() ; // 启动生产者线程
  new Thread(new Consumer(msg)).start() ; // 取得消费者线程
  }
 }

Object线程等待与唤醒机制:

线程的等待与唤醒只能依靠Object来完成,如果想要让生产者与消费者一个一个拿,一个一个取,那么需要加入标志位来确定线程的当前状态;

由图所示:

当生产者线程与消费者线程进入时,判断当前的标志位是否为true,

  1. true:表示生产者可以生产资源,但是消费者不能取走资源

  2. false:表示生产者不能生产资源,但是消费者需要取走资源

 class Message {
  private String title ;
  private String content ;
  private boolean flag = true; // 表示生产或消费的形式
  // flag = true:允许生产,但是不允许消费
  // flag = false:允许消费,不允许生产
  public synchronized void set(String title,String content) {
  if (this.flag == false) { // 无法进行生产,等待被消费
  try {
  super.wait();
  } catch (InterruptedException e) {
  e.printStackTrace();
  }
  }
  this.title = title ;
  try {
  Thread.sleep(100);
  } catch (InterruptedException e) {
  e.printStackTrace();
  }
  this.content = content ;
  this.flag = false ; // 已经生产过了
  super.notify(); // 唤醒等待的线程
  }
  public synchronized String get() {
  if (this.flag == true) { // 还未生产,需要等待
  try {
  super.wait();
  } catch (InterruptedException e) {
  e.printStackTrace();
  }
  }
  try {
  Thread.sleep(10);
  } catch (InterruptedException e) {
  e.printStackTrace();
  }
  try {
  return this.title + " - " + this.content ;
  } finally { // 不管如何都要执行
  this.flag = true ; // 继续生产
  super.notify(); // 唤醒等待线程
  }
  }
 }

在本程序中追加一个数据产生与消费的控制逻辑成员属性,通过此程序的值控制实现线程的等待与唤醒处理操作,从而解决线程重复操作的问题。

Java多线程编程(同步、死锁、生产消费者问题)的更多相关文章

  1. Java 多线程 -- 协作模型:生产消费者实现方式一:管程法

    多线程通过管程法实现生产消费者模式需要借助中间容器作为换从区,还包括生产者.消费者.下面以蒸馒头为列,写一个demo. 中间容器: 为了防止数据错乱,还需要给生产和消费方法加锁 并且生产者在容器写满的 ...

  2. Java 多线程 -- 协作模型:生产消费者实现方式二:信号灯法

    使用信号灯法实现生产消费者模式需要借助标志位. 下面以演员表演,观众观看电视为列,写一个demo 同一资源 电视: //同一资源 电视 class Tv { String voice; // 信号灯 ...

  3. Java多线程编程(4)--线程同步机制

    一.锁 1.锁的概念   线程安全问题的产生是因为多个线程并发访问共享数据造成的,如果能将多个线程对共享数据的并发访问改为串行访问,即一个共享数据同一时刻只能被一个线程访问,就可以避免线程安全问题.锁 ...

  4. Java多线程编程——进阶篇二

    一.线程的交互 a.线程交互的基础知识 线程交互知识点需要从java.lang.Object的类的三个方法来学习:    void notify()           唤醒在此对象监视器上等待的单个 ...

  5. Java多线程编程详解

    转自:http://programming.iteye.com/blog/158568 线程的同步 由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题.Ja ...

  6. Java多线程编程核心技术

    Java多线程编程核心技术 这本书有利于对Java多线程API的理解,但不容易从中总结规律. JDK文档 1. Thread类 部分源码: public class Thread implements ...

  7. Java多线程编程核心技术(三)多线程通信

    线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体.线程间的通信就是成为整体的必用方案之一,可以说,使线程间进行通信后,系统之间的交互性会更强大,在大大提高CPU利用率的同时 ...

  8. 《Java多线程编程核心技术》知识梳理

    <Java多线程编程核心技术> @author ergwang https://www.cnblogs.com/ergwang/ 文章末尾附pdf和png下载链接 第1章 Java多线程技 ...

  9. Java多线程编程总结(精华)

    Java多线程编程总结 2007-05-17 11:21:59 标签:多线程 java 原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http ...

随机推荐

  1. 2021 年学习 React 的所需要的 JavaScript 基础

    在理想的情况中,您可以先了解所有有关 JavaScript 和 web 开发的知识,然后再深入了解React. 但是,我们没有办法这样,如果等你把所有 JavaScript 的知识都掌握了再去学习 R ...

  2. Information:java: javacTask: 源发行版 8 需要目标发行版 1.8

    原文链接:https://blog.csdn.net/idiot_qi/article/details/105149846 创建新maven项目跑main,出现这个编译异常 网上找了找,记录一下以备不 ...

  3. js引入jquery问题

    写jsp的时候明明已经引入了jquery组件,但是总是报错如下图 jsp代码如下 <%@ page language="java" contentType="tex ...

  4. Vue学习笔记-API调试工具--->国产apipost按装(比postman好按装好用)

    一  使用环境: windows 7 64位操作系统 二   Vue学习笔记-API调试工具--->apipost按装 1.下载: https://www.apipost.cn/ (比postm ...

  5. 【Notes_1】现代图形学入门——计算机图形学概述

    跟着闫令琪老师的课程学习,总结自己学习到的知识点 课程网址GAMES101 B站课程地址GAMES101 课程资料百度网盘[提取码:0000] 计算机图形学概述 计算机图形学是一门将模型转化到屏幕上图 ...

  6. Prism.WPF -- Prism框架使用(上)

    本文参考Prism官方示例 创建Prism项目 将App.xaml中的WPF标准Application替换为PrismApplication,移除StartupUri属性: 将App.xaml.cs中 ...

  7. Python 学习笔记(2)

    python 引号 Python 可以使用引号( ' ).双引号( " ).三引号( ''' 或 """ ) 来表示字符串,引号的开始与结束必须是相同类型的. ...

  8. 浮动引发的高度塌陷问题及其解决方法(BFC相关概念及性质)

    浮动引发的高度塌陷问题 高度塌陷问题的产生 BFC(Block Formatting Context)的引入 元素开启BFC后的特点 开启BFC的元素不会被其他浮动元素所覆盖 开启BFC的元素不会发生 ...

  9. Win命令行切换Python版本

    目录 安装2.x 和 3.x 的python 设置系统环境变量 pip的使用 参考 安装2.x 和 3.x 的python 我这里使用anaconda来安装两个版本的python包. conda cr ...

  10. springboot的4种属性注入

    1.Autowired注入 2.构造方法注入 3.@Bean方法形参注入 4.直接在@Bean方法上使用注解@ConfigurationProperties(prefix="jdbc&quo ...