Java 多线程学习笔记二十(同步方法及同步块)
内容来自B站【狂神说Java】多线程详解
同步方法
概念
- 由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是synchronized关键字,它包括两种写法:synchronized方法和synchronized块。同步方法:
public synchronized void method(int args) {}
- synchronized方法控制对“对象”访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行,缺陷:若将一个大的方法申明为synchronized将会影响效率。
弊端
加入一个方法里面有只读代码和可修改代码,方法里面需要修改的内容才需要锁,锁的太多,浪费资源。
同步块
- 同步块:synchronized (Obj) {}
- Obj 称之为同步监视器
- Obj 可以是任何对象,但是推荐使用共享资源作为同步监视器
- 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是 this,就是这个对象本身,或者是 class [反射中讲解]
- 同步监视器的执行过程
- 第一个线程访问,锁定同步监视器,执行其中代码;
- 第二个线程访问,发现同步监视器被锁定,无法访问;
- 第一个线程执行完毕,解锁同步监视器;
- 第二个线程访问,发现同步监视器没有锁,然后锁定并访问。
代码演示
演示代码和老师的不一样。
一、安全的购票
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