Java 多线程学习笔记二十(同步方法及同步块)

内容来自B站【狂神说Java】多线程详解

同步方法

概念

  • 由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是synchronized关键字,它包括两种写法:synchronized方法和synchronized块。同步方法:public synchronized void method(int args) {}
  • synchronized方法控制对“对象”访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行,缺陷:若将一个大的方法申明为synchronized将会影响效率。

弊端

加入一个方法里面有只读代码和可修改代码,方法里面需要修改的内容才需要锁,锁的太多,浪费资源。

同步块

  • 同步块:synchronized (Obj) {}
  • Obj 称之为同步监视器
    • Obj 可以是任何对象,但是推荐使用共享资源作为同步监视器
    • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是 this,就是这个对象本身,或者是 class [反射中讲解]
  • 同步监视器的执行过程
    1. 第一个线程访问,锁定同步监视器,执行其中代码;
    2. 第二个线程访问,发现同步监视器被锁定,无法访问;
    3. 第一个线程执行完毕,解锁同步监视器;
    4. 第二个线程访问,发现同步监视器没有锁,然后锁定并访问。

代码演示

演示代码和老师的不一样。

一、安全的购票

package com.example.demo.thread.sync;

public class SafeTicket {

    public static void main(String[] args) {
        BuyTicket buyTicket = new BuyTicket();
        for (int i = 0; i < 20;) {
            new Thread(buyTicket, "用户" + ++i).start();
        }
    }

}

class BuyTicket implements Runnable {

    private int ticketNums = 10;

    @Override
    public void run() {
        try {
            this.buy();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private synchronized void buy() throws InterruptedException {
        // 模拟查票耗时
        Thread.sleep(100);
        if (this.ticketNums <= 0) {
            System.out.println("倒霉的" + Thread.currentThread().getName() + "未抢到票,已售罄! --> " + this.ticketNums);
            return;
        }
        // 模拟售票后修改数据耗时
        Thread.sleep(100);
        System.out.println("幸运的" + Thread.currentThread().getName() + "抢到了第 " + (this.ticketNums--) + " 张票!");
    }

}

输出

幸运的用户1抢到了第 10 张票!
幸运的用户20抢到了第 9 张票!
幸运的用户19抢到了第 8 张票!
幸运的用户18抢到了第 7 张票!
幸运的用户17抢到了第 6 张票!
幸运的用户16抢到了第 5 张票!
幸运的用户15抢到了第 4 张票!
幸运的用户14抢到了第 3 张票!
幸运的用户13抢到了第 2 张票!
幸运的用户12抢到了第 1 张票!
倒霉的用户11未抢到票,已售罄! --> 0
倒霉的用户10未抢到票,已售罄! --> 0
倒霉的用户9未抢到票,已售罄! --> 0
倒霉的用户8未抢到票,已售罄! --> 0
倒霉的用户7未抢到票,已售罄! --> 0
倒霉的用户6未抢到票,已售罄! --> 0
倒霉的用户5未抢到票,已售罄! --> 0
倒霉的用户4未抢到票,已售罄! --> 0
倒霉的用户3未抢到票,已售罄! --> 0
倒霉的用户2未抢到票,已售罄! --> 0

二、安全的取钱

package com.example.demo.thread.sync;

public class SafeBank {

    public static void main(String[] args) {
        Account account = new Account(100);
        new Thread(new DrawMoney(account, 50), "别小楼").start();
        new Thread(new DrawMoney(account, 100), "李剑诗").start();
    }

}

class Account {
    private int money;

    public Account() {
    }

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

    public int getMoney() {
        return money;
    }

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

class DrawMoney implements Runnable {

    private Account account;
    private int money;

    public DrawMoney() {
    }

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

    public Account getAccount() {
        return account;
    }

    public void setAccount(Account account) {
        this.account = account;
    }

    public int getMoney() {
        return money;
    }

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

    @Override
    public void run() {
        try {
            this.draw();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void draw() throws InterruptedException {
        synchronized (this.account) {
            // 模拟查询余额
            Thread.sleep(100);
            if (this.account.getMoney() < this.money) {
                System.out.println(Thread.currentThread().getName() + "取钱失败,余额不足 " + this.money + ",当前只有 " + this.account.getMoney());
                return;
            }
            // 模拟取钱修改数据库数据
            Thread.sleep(100);
            this.account.setMoney(this.account.getMoney() - this.money);
            System.out.println(Thread.currentThread().getName() + "取出了 " + this.money +",剩余 " + this.account.getMoney());
        }
    }

}

输出

别小楼取出了 50,剩余 50
李剑诗取钱失败,余额不足 100,当前只有 50

三、安全的集合

package com.example.demo.thread.sync;

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

public class SafeList {

    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 20000; i++) {
            new Thread(() -> {
                synchronized (list) {
                    list.add(Thread.currentThread().getName());
                }
            }, "元素" + i).start();
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("集合的元素数量:" + list.size());
    }

}

输出

集合的元素数量:20000
posted @ 2022-01-01 18:19  君子键  阅读(43)  评论(0)    收藏  举报