吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1496|回复: 0
收起左侧

[Java 转载] 多线程同步,通信知识详解

[复制链接]
黑白客 发表于 2021-3-8 16:04
本帖最后由 黑白客 于 2021-3-8 16:08 编辑

多线程同步,通信知识详解
线程同步及同步块
同步块
lock锁
程通信问题
生产者和消费者模式(这个模式并不是23种设计模式中的)
管程法
信号灯法
线程池

线程同步及同步块

结合上篇发布的文章,线程同步就算是讲完了。因为篇幅过长,所以这块被迫分成了两块

  • 同步方法

    由于private关键字来保证数据对象只能被方法所访问。所以我们只需要针对方法提出一套机制

    synchronized关键字,包括synchronize方法和synchronized块

    public synchronized void method(int args){}

    • synchronized方法控制对对象的访问,每个对象对应一把锁,每个对象都必须获得调用该方法的对象的锁才能执行,否则就进入阻塞。方法一旦执行,就独占该锁。

    • 缺陷:若将一个大的方法声明为synchronized将会影响效率。

    对于方法内的只读代码,是不需要锁的。修改内容才需要锁,锁太多了,会影响效率

    • 修改为安全买票
    package com.wang.syn1;
    
    /**
    * @author: 王海新
    * @Date: 2021/2/28 16:40
    * @Description:  修改为安全的买票,
    * 添加synchronized 锁 在buy方法上。就等于在buy的对象上设置了锁。
    * 因为buy里面有延时,所以会让先进来的线程一直调用buy。如果票少,其它的线程就没有机会
    *可以将延时放到run方法中,这样其它在buy执行完
    */
    public class SafeBuyTicket {
      public static void main(String[] args) {
          BuyTicket buyTicket = new BuyTicket();
    
          new Thread(buyTicket,"小明").start();
          new Thread(buyTicket,"小红").start();
          new Thread(buyTicket,"小芳").start();
          new Thread(buyTicket,"小蓝").start();
      }
    }
    
    class BuyTicket implements Runnable{
    
      //票
      private int ticketNums = 10;
      boolean flage = true;//外部停止方法
    
      @Override
      public void run() {
          //买票
          while (flage) {
              try {
                  Thread.sleep(1000);
                  buy();
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
      }
    //添加synchronized 锁
      private synchronized void buy() throws InterruptedException {
          //判断是否有票
          if (ticketNums <= 0) {
              flage = false;
              return;
          }
          //模拟延时
          //Thread.sleep(1000);
          //买票
          System.out.println(Thread.currentThread().getName() + "拿到了第" + ticketNums -- +"张");
      }
    }
    • 修改为安全取钱(这里的我也理解不了了,求大佬指导吧)
    package com.wang.syn1;
    
    /**
    * @author: 王海新
    * @Date: 2021/2/28 16:59
    * @Description: 不安全的取钱,两个人去银行取钱
    *
    */
    public class SafeBank {
      public static void main(String[] args) {
          Account account = new Account(100,"结婚基金");
          Drawing you = new Drawing(account, 50, "你");
          Drawing girlFriend = new Drawing(account, 100, "grilFriend");
    
          you.start();
          girlFriend.start();
    
      }
    
    }
    
    /*********************************************************************************************************************
    * @Author:  王海新
    * @Date:  17:05  2021/2/28
    * @Version:  1.0.0
    * @Description:  账户
    */
    class Account{
       String name;
      int money;
    
      public Account( int money,String name) {
          this.name = name;
          this.money = money;
      }
    }
    
    /*********************************************************************************************************************
    * @Author:  王海新
    * @Date:  17:05  2021/2/28
    * @Version:  1.0.0
    * @Description:  银行 模拟取款
    */
    class Drawing extends Thread{
      //账户
      Account account;
      //取了多少钱
      int DrawingMoney;
      //现在手里有多少钱
      int nowMoney;
      //构造器,将变量初始化
      Drawing(Account account,int DrawingMoney,String name){
          super(name);
          this.account = account;
          this.DrawingMoney = DrawingMoney;
      }
    
      @Override
      public synchronized void run() {
          if (account.money - DrawingMoney < 0) {//判断账户中的钱是否够取
              System.out.println(this.getName() + "钱不够,取不到");
              return;
          }
          try {
              Thread.sleep(1000);
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
          //更新取钱后账户钱的剩余
          account.money = account.money - DrawingMoney;
          //更新用户手中的钱
          nowMoney = nowMoney + DrawingMoney;
    
          System.out.println(account.name + "账户中的钱为" + account.money);
          //Thread.currentThread()就是返回一个Thread对象,所以我们可以用this
          System.out.println(this.getName()+ "手里的钱为" + nowMoney);
    
      }
    }

    在run上加了锁,但是依然没有锁住。还是出现了负数,这里求解答?

同步块

  • 同步块:synchronized(Obj){}

  • Obj称为同步监视器

    • Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
    • 同步方法中,无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class(反射中讲解)
  • 同步监视器的执行过程

    1. 第一个线程访问,锁定同步监视器,执行其中代码
    2. 第二个线程访问,发现同步监视器被锁定,无法访问
    3. 第一个线程访问完毕,解锁同步监视器
    4. 第二个线程访问,发现同步监视器没有锁,然后锁定并访问。

    这里利用同步块,解决银行取钱问题

    package com.wang.syn1;
    
    /**
    * @author: 王海新
    * @Date: 2021/2/28 16:59
    * @Description: 安全的取钱,两个人去银行取钱
    *
    */
    public class SafeBank {
      public static void main(String[] args) {
          Account account = new Account(1000,"结婚基金");
          Drawing you = new Drawing(account, 50, "你");
          Drawing girlFriend = new Drawing(account, 100, "grilFriend");
    
          you.start();
          girlFriend.start();
    
      }
    
    }
    
    /*********************************************************************************************************************
    * @Author:  王海新
    * @Date:  17:05  2021/2/28
    * @Version:  1.0.0
    * @Description:  账户
    */
    class Account{
       String name;
      int money;
    
      public Account( int money,String name) {
          this.name = name;
          this.money = money;
      }
    }
    
    /*********************************************************************************************************************
    * @Author:  王海新
    * @Date:  17:05  2021/2/28
    * @Version:  1.0.0
    * @Description:  银行 模拟取款
    */
    class Drawing extends Thread{
      //账户
      Account account;
      //取了多少钱
      int DrawingMoney;
      //现在手里有多少钱
      int nowMoney;
      //构造器,将变量初始化
      Drawing(Account account,int DrawingMoney,String name){
          super(name);
          this.account = account;
          this.DrawingMoney = DrawingMoney;
      }
    
      @Override
      public  void run() {
          synchronized(account){
              if (account.money - DrawingMoney < 0) {//判断账户中的钱是否够取
                  System.out.println(this.getName() + "钱不够,取不到");
                  return;
              }
              try {
                  Thread.sleep(1000);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              //更新取钱后账户钱的剩余
              account.money = account.money - DrawingMoney;
              //更新用户手中的钱
              nowMoney = nowMoney + DrawingMoney;
    
              System.out.println(account.name + "账户中的钱为" + account.money);
              //Thread.currentThread()就是返回一个Thread对象,所以我们可以用this
              System.out.println(this.getName()+ "手里的钱为" + nowMoney);
          }
    
      }
    }

    课程上说。因为是账户执行的增删改,所以同步监视器要是account对象。(但是我还是不太动,有大佬可以详解一下吗?如果锁的是银行,那一直有一个人在里面取钱,岂不是也不会出错。当然结果是出错了。但是我真的不理解原因啊)

    • 总结,锁的对象是要增删改的对象。

    • 根据这个总结,我们将集合也该为安全的

    package com.wang.syn1;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
    * @author: 王海新
    * @Date: 2021/3/3 09:22
    * @Description: 线程安全的集合
    * 造成数据不是10000个的原因有两个
    *因循环已经跑完,程序结束,线程还没有将数据加入的情况。
    * 两个线程同时操作同一个位置,造成的数据覆盖。
    */
    public class SafeList {
      public static void main(String[] args) {
          //创建一个集合
          List<String> array = new ArrayList<String>();
          //利用for循环,创建1000个线程向集合里面添加数据
          for (int i = 0; i < 10000; i++) {
              new Thread( () -> {
                  synchronized(array){
                      array.add(Thread.currentThread().getName());
                  }
              }).start();
          }
          try {//利用阻塞。去除掉因循环已经跑完,程序结束,线程还没有将数据加入的情况。导致数据不一致的原因
              Thread.sleep(3000);
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
          System.out.println(array.size());
      }
    }
    • guc里面的一个安全类型的集合
    package com.wang.syn1;
    
    import java.util.concurrent.CopyOnWriteArrayList;
    
    /**
    * @author: 王海新
    * @Date: 2021/3/4 16:11
    * @Description: 测试JUC安全类型的集合
    */
    public class TestGUC {
      public static void main(String[] args) {
          CopyOnWriteArrayList<String> strings = new CopyOnWriteArrayList<>();
          for (int i = 0; i < 1000; i++) {
              new Thread( () -> {
                  strings.add(Thread.currentThread().getName());
              }).start();
          }
          try {
              Thread.sleep(3000);
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
          System.out.println(strings.size());
      }
    }

lock锁

package com.wang.syn2;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @author: 王海新
 * @Date: 2021/3/5 10:21
 * @Description: lock 锁
 */
public class TestLock {
    public static void main(String[] args) {
        TestLock2 testLock2 = new TestLock2();

        new Thread(testLock2).start();
        new Thread(testLock2).start();
        new Thread(testLock2).start();
    }
}

class TestLock2 implements Runnable{

    int ticketNumber = 10;

    private final ReentrantLock reentrantLock = new ReentrantLock();

    @Override
    public void run() {
        while (true){
            //一般在加锁的代码块中,如果有异常抛出,就放到try{}finally{}中 在finally中将锁释放
            try{
            //显示的加锁
                reentrantLock.lock();
                if (ticketNumber > 0) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(ticketNumber --);
                }else {
                    break;
                }
            }
            finally {
                //显示的释放锁
                reentrantLock.unlock();
            }

        }

    }
}

程通信问题

生产者和消费者模式(这个模式并不是23种设计模式中的)

  • 这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件

  • 在这个问题中,synchronized可以阻止并发更新同一个资源

    但是不能用来实现不同线程之间的消息传递

  • java提供了几个方法解决线程之间的通信问题

    一下均是Object类的方法。都只能在同步方法,或者在同步代码块中使用,否者会抛出异常

    • wait() 表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁
    • wait(long timeout) 指定等待的毫秒数
    • notify()唤醒一个处于等待状态的线程
    • notifyAll()唤醒同一个对象上,所有调用wait方法的线程,优先级别高的线程,优先调用
  • 解决方法1 生产者消费者模式--管程法

    添加一个缓冲区,生产者生产的商品放到 这里。消费者从这里消费。从而实现协作

  • 解决方法2 并发协作模式,信号灯法。可以用一个标志位来控制 如true放行 false等待

管程法

package com.wang.syn2;

/**
 * @author: 王海新
 * @Date: 2021/3/6 11:36
 * @Description: 线程协作,管程法
 * 生产者 消费者 产品 缓冲区
 */
public class TestPC {
    public static void main(String[] args) {
        SynContainer container = new SynContainer();

        new Productor( container) .start();
        new Consumer(container).start();
    }
}

/*********************************************************************************************************************
 * @Author:  王海新
 * @Date:  11:37  2021/3/6
 * @Version:  1.0.0
 * @Description:  生产者
 */
class Productor extends Thread {
    SynContainer container;

    public Productor(SynContainer container){
        this.container = container;
    }
    //生产
    public void run(){
        for (int i = 0; i < 100; i++) {
            container.push(new Chicken(i));
            System.out.println("生产了"+ i + "只鸡。");
        }
    }
}

/*********************************************************************************************************************
 * @Author:  王海新
 * @Date:  11:38  2021/3/6
 * @Version:  1.0.0
 * @Description:  消费者
 */
class Consumer extends Thread {
    SynContainer container;

    public Consumer(SynContainer container){
        this.container = container;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("消费了--》" + container.pop().id+ "只鸡");
        }
    }
}

/*********************************************************************************************************************
 * @Author:  王海新
 * @Date:  11:38  2021/3/6
 * @Version:  1.0.0
 * @Description:  产品
 */
class Chicken {
    int id;//产品编号

    public Chicken(int id) {
        this.id = id;
    }
}

/*********************************************************************************************************************
 * @Author:  王海新
 * @Date:  11:39  2021/3/6
 * @Version:  1.0.0
 * @Description:  缓冲区
 */
class SynContainer{
//需要一个容器大小
    Chicken[] chickens = new Chicken[10];
    //容器计数器
    int count = 0;
    //生产者放入产品
    public synchronized void push(Chicken chicken){
        //如果容器满了,就等待消费者消费
        if (count == chickens.length){
            //通知消费者消费
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果没有满,我们就需要丢入产品
        chickens[count] = chicken;
        count ++;
        //可以通知消费者消费了
        this.notifyAll();

    }

    //消费者消费商品
    public synchronized  Chicken  pop(){
        //判断能否消费
        if (count == 0) {
            //等待生产者生产
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //如果可以消费
        count --;
        Chicken chicken = chickens[count];
        //吃完了,通知生产者生产
        this.notifyAll();
        return chicken;
    }
}

image.png

这是草图,我自己哪里理清思路的时候随手画的。

信号灯法

package com.wang.syn2;

/**
 * @author: 王海新
 * @Date: 2021/3/6 11:36
 * @Description: 线程协作,信号灯法
 * 生产者 消费者 烤鸡店
 */
public class TestPC2 {
    public static void main(String[] args) {
        Chicken2 chicken2 = new Chicken2();
        new Productor2(chicken2) .start();
        new Consumer2(chicken2).start();
    }
}

/*********************************************************************************************************************
 * @Author:  王海新
 * @Date:  11:37  2021/3/6
 * @Version:  1.0.0
 * @Description:  生产者
 */
class Productor2 extends Thread {
    Chicken2 chicken;
    public Productor2(Chicken2 chicken2 ){
        this.chicken = chicken2;
    }
    //生产
    public void run(){
        for (int i = 0; i < 10; i++) {
            chicken.pro(i);
        }
    }
}

/*********************************************************************************************************************
 * @Author:  王海新
 * @Date:  11:38  2021/3/6
 * @Version:  1.0.0
 * @Description:  消费者
 */
class Consumer2 extends Thread {
    Chicken2 chicken;
    public Consumer2(Chicken2 chicken2 ){
        this.chicken = chicken2;
    }
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            chicken.con();
        }
    }
}

/*********************************************************************************************************************
 * @Author:  王海新
 * @Date:  11:38  2021/3/6
 * @Version:  1.0.0
 * @Description:  烤鸡店
 */
class Chicken2 {
    int id;//产品编号
    //生产完成,通知消费 false
    //消费完成,通知生产 true
    boolean flag = true;
    //生产
    public synchronized void pro(int id){
        //判断是否生产
        if (!flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //生产了
        System.out.println("生产了第"+ id + "只鸡");
        //通知消费
        this.notifyAll();
        this.id = id;
        this.flag = !this.flag;
    }
    //消费
    public synchronized void con(){
        //判断是否消费
        if (flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //消费
        this.id = id;
        System.out.println("消费了"+ this.id + "只鸡。");
        this.flag = !this.flag;
        this.notifyAll();
    }
}

利用一个标志位,达到两个线程协同的效果

线程池

  • 经常创建和销毁特别大的资源,比如并发情况下的线程。对性能影响很大。
  • 提前创建好多个线程,放入线程池,使用时直接获取。使用完放回池子中。避免频繁的创建和销毁,实现重复利用。
  • 优点
    • 提高响应速度(减少创建的时间)
    • 降低资源消耗
    • 便于线程管理
    • corePoolSize:核心池大小
    • maximumPoolSize :最大线程数
    • KeepAliveTime :线程没有任务后,最多保存的少时间后销毁。
package com.wang.syn2;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author: 王海新
 * @Date: 2021/3/8 15:51
 * @Description: 线程池
 */
public class TestPool {
    public static void main(String[] args) {
        //1.创建服务,创建线程池
        //newFixedThreadPool 参数为线程池大小
        ExecutorService service = Executors.newFixedThreadPool(10);

        //执行
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());

        //关闭
        service.shutdown();

    }

}
/*********************************************************************************************************************
 * @Author:  王海新
 * @Date:  15:51  2021/3/8
 * @Version:  1.0.0
 * @Description:  
 */
class MyThread implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}
  • OK,到这里,多线程的学习文章就发完了。 完结散花

免费评分

参与人数 3吾爱币 +1 热心值 +3 收起 理由
origamiii + 1 用心讨论,共获提升!
hualonghongyan + 1 我很赞同!
gwszrs + 1 + 1 用心讨论,共获提升!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-25 19:38

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表