黑白客 发表于 2021-3-8 16:04

多线程同步,通信知识详解

本帖最后由 黑白客 于 2021-3-8 16:08 编辑

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

## 线程同步及同步块

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

- 同步方法

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

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

public synchronized void method(int args){}

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

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

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

- 修改为安全买票

```java
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 -- +"张");
      }
}
```

- 修改为安全取钱(这里的我也理解不了了,求大佬指导吧)

```java
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:052021/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:052021/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. 第二个线程访问,发现同步监视器没有锁,然后锁定并访问。

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

```java
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:052021/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:052021/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
      publicvoid 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对象。(但是我还是不太动,有大佬可以详解一下吗?如果锁的是银行,那一直有一个人在里面取钱,岂不是也不会出错。当然结果是出错了。但是我真的不理解原因啊)

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

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

```java
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里面的一个安全类型的集合

```java
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锁

```java
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等待

## 管程法

```java
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:372021/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:382021/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:382021/3/6
* @Version:1.0.0
* @Description:产品
*/
class Chicken {
    int id;//产品编号

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

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

    }

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

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



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

## 信号灯法

```java
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:372021/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:382021/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:382021/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 :线程没有任务后,最多保存的少时间后销毁。

```java
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:512021/3/8
* @Version:1.0.0
* @Description:
*/
class MyThread implements Runnable{

    @Override
    public void run() {
      System.out.println(Thread.currentThread().getName());
    }
}
```

- OK,到这里,多线程的学习文章就发完了。 完结散花
页: [1]
查看完整版本: 多线程同步,通信知识详解