该博客时多线程编程第二部分,其中主要是关于线程的同步以及线程死锁相关的实现,还有经典的生产者与消费者问题分析等。

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

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

效果图:

image-20210422104049966

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

生产者与消费者问题:

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

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

对应类关系图:

image-20210421234308037

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

  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 {
版权声明:本文为xbhog原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/xbhog/p/14689101.html