【JAVA并发编程-黑马】第二章

一、买票案例

1.1线程不安全方式

package com.learning.concurrent;

/**
 * ClassName:TicketWindow
 * Package:com.learning.concurrent
 * Desciption:
 *
 * @date:2020/5/18 22:33
 * @author:
 */
public class TicketWindow {

    private int tickets;
    public TicketWindow(int tickets) {
        this.tickets = tickets;
    }

    public void sell (int sells) {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (tickets - sells >=0) {
            tickets = tickets -sells;
        }
    }


    public int getTickets() {
        return tickets;
    }
}

import java.util.ArrayList;
import java.util.List;
import java.util.Vector;

/**
 * ClassName:SellTicketTest
 * Package:com.learning.concurrent
 * Desciption:
 *
 * @date:2020/5/18 22:38
 * @author:17611219021@sina.cn
 */
@Slf4j
public class SellTicketTest {


    public static void main(String[] args) throws Exception{
        //模拟多人买票窗口
        TicketWindow window=new TicketWindow(60000);
        //线程集合
        List<Thread> threadList = new ArrayList<Thread>();
        //统计一共卖出了多少张票
        Vector<Integer> sellCounts = new Vector<>();

        long start = System.currentTimeMillis();
        log.info("开始时间:{}",start);

        for (int i=0 ;i<1000; i++) {
            Thread t =new Thread(() ->{
                window.sell(6);
                sellCounts.add(6);

            });
            threadList.add(t);
            t.start();
        }

        //等待所有买票线程跑完,再执行main线程中的方法
        for ( Thread t: threadList) {
            t.join();
        }
        long end = System.currentTimeMillis();
        log.info("结束时间:{}",end);
        log.info("耗时:{}",end - start);
        log.info("卖出的票:{}",sellCounts.stream().mapToInt(i -> i).sum());
        log.info("剩余的票:{}",window.getTickets());

    }
}

执行结果:

23:34:30.523 [main] INFO com.learning.concurrent.SellTicketTest - 开始时间:1589816070521
23:34:31.612 [main] INFO com.learning.concurrent.SellTicketTest - 结束时间:1589816071612
23:34:31.612 [main] INFO com.learning.concurrent.SellTicketTest - 耗时:1091
23:34:31.615 [main] INFO com.learning.concurrent.SellTicketTest - 卖出的票:6000
23:34:31.615 [main] INFO com.learning.concurrent.SellTicketTest - 剩余的票:54582

线程不安全,因为票数加起来不等于60000.

1.2线程安全方式

package com.learning.concurrent;

/**
 * ClassName:TicketWindow
 * Package:com.learning.concurrent
 * Desciption:
 *
 * @date:2020/5/18 22:33
 * @author:
 */
public class TicketWindow {

    private int tickets;
    public TicketWindow(int tickets) {
        this.tickets = tickets;
    }

    public synchronized void sell (int sells) {
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (tickets - sells >=0) {
            tickets = tickets -sells;
        }
    }


    public int getTickets() {
        return tickets;
    }
}

这里在买票窗口中添加了synchronized ,并且休眠时间调成了10毫秒,执行结果如下:

23:38:35.179 [main] INFO com.learning.concurrent.SellTicketTest - 开始时间:1589816315178
23:38:45.940 [main] INFO com.learning.concurrent.SellTicketTest - 结束时间:1589816325940
23:38:45.940 [main] INFO com.learning.concurrent.SellTicketTest - 耗时:10762
23:38:45.944 [main] INFO com.learning.concurrent.SellTicketTest - 卖出的票:6000
23:38:45.944 [main] INFO com.learning.concurrent.SellTicketTest - 剩余的票:54000

重要结论:
这个是线程安全的,因为卖出的票+剩余的票等于60000.但是观察两种结果,第一种结果我睡眠1000毫秒,大约花了1秒时间执行完成,第二种结果我睡眠了仅仅10毫秒,却花了10s的时间执行完成,想象一下,当第1001个人来买票的时候,花了10s钟才进行响应,多可怕,如果10000个人同时访问呢?花费100s吗?这个太可怕了。

二、转账案例

2.1线程不安全的方式

package com.learning.concurrent;

import lombok.extern.slf4j.Slf4j;

import java.util.Random;

@Slf4j(topic = "c.ExerciseTransfer")
public class ExerciseTransfer {
    public static void main(String[] args) throws InterruptedException {
        Account a = new Account(1000);
        Account b = new Account(1000);
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                a.transfer(b, randomAmount());
            }
        }, "t1");
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                b.transfer(a, randomAmount());
            }
        }, "t2");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        // 查看转账2000次后的总金额
        log.debug("total:{}", (a.getMoney() + b.getMoney()));
    }

    // Random 为线程安全
    static Random random = new Random();

    // 随机 1~100
    public static int randomAmount() {
        return random.nextInt(100) + 1;
    }
}

// 账户
class Account {
    private int money;

    public Account(int money) {
        this.money = money;
    }

    public int getMoney() {
        return money;
    }

    public void setMoney(int money) {
        this.money = money;
    }

    // 转账
    public void transfer(Account target, int amount) {
        if (this.money >= amount) {
            this.setMoney(this.getMoney() - amount);
            target.setMoney(target.getMoney() + amount);
        }
    }
}

输出结果:

21:47:00.784 [main] DEBUG c.ExerciseTransfer - total:55

2.2线程安全的方式

上面的方式,共享变量有两个,一个是A账户的余额,一个是B账户的余额,如果说单纯的加synchronized关键字是不起作用的,因为只能锁住A对象或者B对象,而我们应该需要锁住A和B账户共享的对象即可解决,即锁住账户的class文件:

package com.learning.concurrent;

import lombok.extern.slf4j.Slf4j;

import java.util.Random;

@Slf4j(topic = "c.ExerciseTransfer")
public class ExerciseTransfer {
    public static void main(String[] args) throws InterruptedException {
        Account a = new Account(1000);
        Account b = new Account(1000);
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                a.transfer(b, randomAmount());
            }
        }, "t1");
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                b.transfer(a, randomAmount());
            }
        }, "t2");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        // 查看转账2000次后的总金额
        log.debug("total:{}", (a.getMoney() + b.getMoney()));
    }

    // Random 为线程安全
    static Random random = new Random();

    // 随机 1~100
    public static int randomAmount() {
        return random.nextInt(100) + 1;
    }
}

// 账户
class Account {
    private int money;

    public Account(int money) {
        this.money = money;
    }

    public int getMoney() {
        return money;
    }

    public void setMoney(int money) {
        this.money = money;
    }

    // 转账
    public void transfer(Account target, int amount) {
        synchronized(Account.class) {
            if (this.money >= amount) {
                this.setMoney(this.getMoney() - amount);
                target.setMoney(target.getMoney() + amount);
            }
        }
    }
}

输出结果:

21:47:42.392 [main] DEBUG c.ExerciseTransfer - total:2000

三、JAVA对象头

也可以参考 https://blog.csdn.net/SCDN_CP/article/details/86491792
以32位的虚拟机为例:
8个字节,其中4个字节是Mark Word。
普通对象
在这里插入图片描述
数组对象
在这里插入图片描述
其中Mark Word的结构为
在这里插入图片描述
64位虚拟机的Mark Word
在这里插入图片描述

四、Monitor原理

Monitor 被翻译为监视器管程

Monitor是操作系统提供的对象

每个 Java 对象都可以关联一个 Monitor 对象,如果使用 synchronized 给对象上锁(重量级)之后,该对象头的Mark Word 中就被设置指向 Monitor 对象的指针

Monitor 结构如下:
在这里插入图片描述

  • 刚开始 Monitor 中 Owner 为 null
  • 当 Thread-2 执行 synchronized(obj) 就会将 Monitor 的所有者 Owner 置为 Thread-2,Monitor中只能有一
    个 Owner
  • 在 Thread-2 上锁的过程中,如果 Thread-3,Thread-4,Thread-5 也来执行 synchronized(obj),就会进入
    EntryList BLOCKED
  • Thread-2 执行完同步代码块的内容,然后唤醒 EntryList 中等待的线程来竞争锁,竞争的时是非公平的
  • 图中 WaitSet 中的 Thread-0,Thread-1 是之前获得过锁,但条件不满足进入 WAITING 状态的线程,后面讲
    wait-notify 时会分析

注意:
synchronized 必须是进入同一个对象的 monitor 才有上述的效果
不加 synchronized 的对象不会关联监视器,不遵从以上规则

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值