2.使用synchronized关键字实现多线程的同步和互斥(不同线程同时读写同一数据)

利用能量守恒定律实现多线程的同步和互斥
- EnergySystem.java:能量类
- EnergySystemTest.java:测试Main类
- EnergyTransferTask.java:任务线程线程


synchronized

synchronized关键字,代表这个方法加锁,相当于不管哪一个线程(例如线程A),运行到这个方法时,都要检查有没有其它线程B(或者C、D等)正在用这个方法(或者该类的其他同步方法),有的话要等正在使用synchronized方法的线程B(或者C、D)运行完这个方法后再运行此线程A,没有的话,锁定调用者,然后直接运行。它包括两种用法:synchronized 方法和 synchronized块。
Java语言的关键字,可用来给对象和方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码。当两个并发线程访问同一个对象object中的这个加锁同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。然而,当一个线程访问object的一个加锁代码块时,另一个线程仍然可以访问该object中的非加锁代码块。

不加锁(synchronized )的情况

/** * 宇宙的能量系统 遵循能量守恒定律: 能量不会凭空创生或消失,只会从一处转移到另一处 */public class EnergySystem {    // 能量盒子,能量存贮的地方    private final double[] energyBoxes;    private final Object lockObj = new Object();    /**     *      * @param n     *            能量盒子的数量     * @param initialEnergy     *            每个能量盒子初始含有的能量值     */    public EnergySystem(int n, double initialEnergy) {        energyBoxes = new double[n];        for (int i = 0; i < energyBoxes.length; i++)            energyBoxes[i] = initialEnergy;    }    /**     * 能量的转移,从一个盒子到另一个盒子     *      * @param from     *            能量源     * @param to     *            能量终点     * @param amount     *            能量值     */    public void transfer(int from, int to, double amount) {        if (energyBoxes[from] > amount) {            System.out.print(Thread.currentThread().getName());            energyBoxes[from] -= amount;            System.out.printf("从%d转移%10.2f单位能量到%d", from, amount, to);            energyBoxes[to] += amount;            System.out.printf(" 能量总和:%10.2f%n", getTotalEnergies());        } else {            return;        }    }    /**     * 获取能量世界的能量总和     */    public double getTotalEnergies() {        double sum = 0;        for (double amount : energyBoxes)            sum += amount;        return sum;    }    /**     * 返回能量盒子的长度     */    public int getBoxAmount() {        return energyBoxes.length;    }}
public class EnergyTransferTask implements Runnable{    //共享的能量世界    private EnergySystem energySystem;    //能量转移的源能量盒子下标    private int fromBox;    //单次能量转移最大单元    private double maxAmount;    //最大休眠时间(毫秒)    private int DELAY = 10;    public EnergyTransferTask(EnergySystem energySystem, int from, double max){        this.energySystem = energySystem;        this.fromBox = from;        this.maxAmount = max;    }    public void run() {        int c = 3;        try{            while (c > 0){                c--;                int toBox = (int) (energySystem.getBoxAmount()* Math.random());                double amount = maxAmount * Math.random();                energySystem.transfer(fromBox, toBox, amount);                Thread.sleep((int) (DELAY * MansferTask(eng, i, INITIAL_ENERGY);            Thread t = new Thread(task,"TransferThread_"+i);            t.start();        }    }}
public class EnergySystemTest {    //将要构建的能量世界中能量盒子数量    public static final int BOX_AMOUNT = 10;    //每个盒子初始能量    public static final double INITIAL_ENERGY = 100;    public static void main(String[] args){        EnergySystem eng = new EnergySystem(BOX_AMOUNT, INITIAL_ENERGY);        for (int i = 0; i < BOX_AMOUNT; i++){            EnergyTransferTask task = new EnergyTransferTask(eng, i, INITIAL_ENERGY);            Thread t = new Thread(task,"TransferThread_"+i);            t.start();        }    }}

代码功能

现在的代码描述的是有10个能量盒子,然后启动10个线程,每个线程会执行三次操作:将A盒子中能量转移到B盒子中,但是我们发现,能量总和减少啦,为什么呢?

举个例子:线程1读取盒子A的数据5000,将其加500,此时线程2读取盒子A的数据5000(因为还有写入,所以盒子A还是5000),将其加900,再将其写入,此时盒子A为5900,但此时线程A也要进行写操作,这时盒子A为5500,所以……

输出样例

TransferThread_3TransferThread_7TransferThread_2从2转移 53.56单位能量到3TransferThread_6从6转移 40.02单位能量到5 能量总和: 937.16
TransferThread_1从1转移 2.30单位能量到8 能量总和: 937.16
TransferThread_0从0转移 27.61单位能量到9TransferThread_9TransferThread_4从4转移 82.48单位能量到7 能量总和: 927.92
TransferThread_8从8转移 68.90单位能量到3 能量总和: 927.92
TransferThread_5从5转移 33.59单位能量到3 能量总和: 927.92
TransferThread_1从9转移 9.24单位能量到3 能量总和: 866.17
能量总和: 937.16
能量总和: 937.16
从7转移 43.20单位能量到3 能量总和: 909.37
从3转移 19.64单位能量到7 能量总和: 929.01
TransferThread_0从0转移 7.07单位能量到7 能量总和: 929.01
从1转移 70.99单位能量到2 能量总和: 1000.00
TransferThread_7从7转移 94.67单位能量到3 能量总和: 1000.00
TransferThread_5从5转移 66.16单位能量到3 能量总和: 1000.00
TransferThread_7从7转移 59.33单位能量到4 能量总和: 1000.00
TransferThread_4TransferThread_6从6转移 12.24单位能量到8 能量总和: 997.95
从4转移 2.05单位能量到4 能量总和: 1000.00
TransferThread_3从3转移 61.92单位能量到4 能量总和: 1000.00
TransferThread_2从2转移 65.65单位能量到5 能量总和: 1000.00
TransferThread_8TransferThread_9从9转移 44.34单位能量到3 能量总和: 977.95
从8转移 22.05单位能量到4 能量总和: 1000.00
TransferThread_1从1转移 1.77单位能量到8 能量总和: 1000.00
TransferThread_4从4转移 25.64单位能量到5 能量总和: 1000.00
TransferThread_3从3转移 83.27单位能量到6 能量总和: 1000.00

加锁操作,修改EnergySystem.java

/** * 宇宙的能量系统 遵循能量守恒定律: 能量不会凭空创生或消失,只会从一处转移到另一处 */public class EnergySystem {    // 能量盒子,能量存贮的地方    private final double[] energyBoxes;    private final Object lockObj = new Object();    /**     *      * @param n     *            能量盒子的数量     * @param initialEnergy     *            每个能量盒子初始含有的能量值     */    public EnergySystem(int n, double initialEnergy) {        energyBoxes = new double[n];        for (int i = 0; i < energyBoxes.length; i++)            energyBoxes[i] = initialEnergy;    }    /**     * 能量的转移,从一个盒子到另一个盒子     *      * @param from     *            能量源     * @param to     *            能量终点     * @param amount     *            能量值     */    public void transfer(int from, int to, double amount) {        synchronized (lockObj) {            // while循环,保证条件不满足时任务都会被条件阻挡            // 而不是继续竞争CPU资源            while (energyBoxes[from] < amount) {                try {                    // 条件不满足, 将当前线程放入Wait Set                    lockObj.wait();                } catch (InterruptedException e) {                    e.printStackTrace();                }            }            System.out.print(Thread.currentThread().getName());            energyBoxes[from] -= amount;            System.out.printf("从%d转移%10.2f单位能量到%d", from, amount, to);            energyBoxes[to]  amount;            System.out.printf(" 能量总和:%10.2f%n", getTotalEnergies());            // 唤醒所有在lockObj对象上等待的线程            lockObj.notifyAll();        }    }    /**     * 获取能量世界的能量总和     */    public double getTotalEnergies() {        double sum = 0;        for (double amount : energyBoxes)            sum += amount;        return sum;    }    /**     * 返回能量盒子的长度     */    public int getBoxAmount() {        return energyBoxes.length;    }}

锁的应用

当程序执行到transfer方法时,通过synchronized (lockObj)会将当前资源(盒子A)锁住,其他线程无法获取次资源,当energyBoxes[from]小于amount时,即当前数据不符合操作要求,执行lockObj.wait()方法,将资源放开,此线程进入Wait Set中等待其他线程唤醒(notifyAll(),notify()),当数据符合操作且操作结束后,会调用notifyAll方法释放线程,这是其他线程就可以获取当前资源了,达到总能量始终是一定的。

notify()和notifyAll()都是Object对象用于通知处在等待该对象的线程的方法。两者的最大区别在于:
notifyAll使所有原来在该对象上等待被notify的线程统统退出wait的状态,变成等待该对象上的锁,一旦该对象被解锁,他们就会去竞争。
notify则文明得多他只是选择一个wait状态线程进行通知,并使它获得该对象上的锁,但不惊动其他同样在等待被该对象notify的线程们,当第一个线程运行完毕以后释放对象上的锁此时如果该对象没有再次使用notify语句,则即便该对象已经空闲,其他wait状态等待的线程由于没有得到该对象的通知,继续处在wait状态,直到这个对象发出一个notify或notifyAll,它们等待的是被notify或notifyAll,而不是锁。

输出样例

TransferThread_6从6转移 62.69单位能量到1 能量总和: 1000.00
TransferThread_1从1转移 88.95单位能量到2 能量总和: 1000.00
TransferThread_3从3转移 15.16单位能量到1 能量总和: 1000.00
TransferThread_4从4转移 37.62单位能量到2 能量总和: 1000.00
TransferThread_5从5转移 88.83单位能量到6 能量总和: 1000.00
TransferThread_0从0转移 57.21单位能量到7 能量总和: 1000.00
TransferThread_2从2转移 67.80单位能量到0 能量总和: 1000.00
TransferThread_7从7转移 55.19单位能量到8 能量总和: 1000.00
TransferThread_8从8转移 75.56单位能量到8 能量总和: 1000.00
TransferThread_9从9转移 21.01单位能量到6 能量总和: 1000.00
TransferThread_3从3转移 75.10单位能量到8 能量总和: 1000.00
TransferThread_6从6转移 17.85单位能量到4 能量总和: 1000.00
TransferThread_7从7转移 54.41单位能量到4 能量总和: 1000.00
TransferThread_0从0转移 65.67单位能量到6 能量总和: 1000.00
TransferThread_9从9转移 11.83单位能量到7 能量总和: 1000.00
TransferThread_2从2转移 59.20单位能量到8 能量总和: 1000.00
TransferThread_9从9转移 32.79单位能量到3 能量总和: 1000.00
TransferThread_3从3转移 11.00单位能量到9 能量总和: 1000.00
TransferThread_4从4转移 41.82单位能量到4 能量总和: 1000.00
TransferThread_8从8转移 53.70单位能量到3 能量总和: 1000.00
TransferThread_6从6转移 81.85单位能量到8 能量总和: 1000.00
TransferThread_2从2转移 15.96单位能量到0 能量总和: 1000.00
TransferThread_4从4转移 32.41单位能量到9 能量总和: 1000.00
TransferThread_0从0转移 59.02单位能量到0 能量总和: 1000.00
TransferThread_8从8转移 67.92单位能量到2 能量总和: 1000.00

(0)

相关推荐

  • 打造有温度的人间-----苦涩的坚守

    2021年1月10号 封闭第五天 正常的生活秩序被打乱 食物也出现了匮乏 粉条成了主要的菜肴 五天没吃水果 肠道也提出了严重的抗议 我正在经历 好多的人曾经经历过的事情 2020上半年的武汉 物质的相 ...

  • 辐射计量单位

    辐射计量单位 辐射就是指高能粒子流(射线).每单位物质质量所接受的辐射能量称为剂量,常用拉德(rad)或者戈瑞(Gray)作为计量单位,它们与其它常用能量单位之间的关系为:1rad = 100erg/ ...

  • 统计力学(29):能量均分定律 (简体字版)

    5.7 能量均分定律 我们再看一个示范例子,即「能量均分定律」,这定律的內容是:  如果全能函数是个平方项之和 是坐标或动量.为正值常数,则总能量 和温度的关系是 也就是说,平均每一变数的运动,分配到 ...

  • 精编:高三一轮复习基础巩固-12化学反应与能量变化(高一可用

    人教版高中化学专用:化学反应与能量变化,此文档包括学生版和教师版,适用于高一知识强化,高三复习巩固.下面展示为教师版. 需要高清无水印电子文档的亲们记得关注.收藏和转发哦! 关注后请私聊我:037 第 ...

  • 17张图带你秒杀synchronized关键字

    来自公众号:一只自动编码机 引子 小艾和小牛在路上相遇,小艾一脸沮丧. 小牛:小艾小艾,发生甚么事了? 小艾:别提了,昨天有个面试官问了我好几个关于 synchronized 关键字的问题,没答上来. ...

  • 红皮杂志:首项低分割质子治疗同步化疗局部晚期非小细胞肺癌的疗效数据

    含度伐单抗(Durvalumab)的辅助放化疗(放疗剂量:60-66 Gy)是局部晚期非小细胞肺癌(LA-NSCLC)的标准治疗方法.Durvalumab的应用减少了肿瘤的远处转移,提高了患者总生存率 ...

  • linux之多任务的同步与互斥

    linux之多任务的同步与互斥

  • Java多线程访问Synchronized同步方法的八种使用场景

    简介 本文将介绍7种同步方法的访问场景,我们来看看这七种情况下,多线程访问同步方法是否还是线程安全的.这些场景是多线程编程中经常遇到的,而且也是面试时高频被问到的问题,所以不管是理论还是实践,这些都是 ...

  • Linux C 实现多线程同步的四种方式(超级详细)

    背景问题:在特定的应用场景下,多线程不进行同步会造成什么问题? 通过多线程模拟多窗口售票为例: #include <iostream> #include<pthread.h> ...

  • 多线程(二)线程的同步

    举例:创建三个卖票窗口,共有一百张票,完成售票 问题:卖票的过程中,出现了重票.错票 --> 出现了线程的安全问题 问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来, ...

  • 榨干CPU:同步多线程(超线程)浅谈

    超线程 在2021年,应该已经没有人会问出为什么我购买了一颗四核心的i3-10100,却能在任务管理器看到八个框框这种问题了,随着牙膏厂在初代酷睿I系列开始重新加入超线程技术,超线程已经不知不觉在消费 ...

  • Java之多线程里面的锁理解以及synchronized与Lock的区别

    一.宏观的说下锁的分类 1)锁分为乐观锁.悲观锁 悲观锁认为对于同一个数据的并发操作,一定是会发生修改的,哪怕没有修改,也会认为修改.因此对于同一个数据的并发操作,悲观锁采取加锁的形式.悲观的认为,不 ...

  • 流言终结者系列:第三代锐龙关同步多线程能增加游戏帧数?

    关于AMD锐龙处理器玩游戏要关掉同步多线程(SMT)这传言其实已经流传已久,说真的这话放到以前可能还真的有这可能,但是随着Windows 10升到1903版本优化了CPU的调度之后再来传新锐龙处理器玩 ...