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

本文详细介绍了Java中的线程同步和通信,包括synchronized关键字、同步块、锁、线程池的应用,并通过实例解释了生产者消费者模式的管程法和信号灯法。此外,还探讨了线程池的优势,展示了如何创建和使用线程池。
摘要由CSDN通过智能技术生成

线程同步及同步块

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

  • 同步方法

    由于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;
    }
}

在这里插入图片描述

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

信号灯法

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,到这里,多线程的学习文章就发完了。 完结散花
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

黑白极客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值