上次分享了线程的常用方法,这次分享线程中的同步问题(即多个线程操作一个资源)
目录
一、线程同步概念
线程同步(多个线程操作同一个资源)
二、线程不安全案例
1.不安全的买票
/** * @author gc * @date 2022/11/22 11:26 * note : 模拟抢火车票 出现线程不安全问题 * synchronized 关键字 锁的是 this */ public class UnSafeBuyTicket { public static void main(String[] args) { BuyTicket buyTicket = new BuyTicket(); new Thread(buyTicket,"韩宁").start(); new Thread(buyTicket,"王科学").start(); new Thread(buyTicket,"桑高杰").start(); } } class BuyTicket implements Runnable{ //Lock lock = new ReentrantLock(); private int ticketNum = 10; Boolean flag = true; // 执行买票操作 @SneakyThrows @Override public void run() { while(flag){ buy(); } } private synchronized void buy() throws InterruptedException { // 判断是否有票 while(ticketNum <= 0){ flag = false; return; } //lock.lock(); Thread.sleep(100); // 买票 System.out.println(Thread.currentThread().getName() + "拿到" + ticketNum-- + "张票!"); //lock.unlock(); } }
2.不安全的取钱
/** * @author gc * @date 2022/11/22 16:52 * note : 两个同时用一个银行卡取钱 */ public class UnSafeBank { public static void main(String[] args) { Account account = new Account(150,"中国工商银行"); Drawing girlFriend = new Drawing(account, 100, "她"); Drawing you = new Drawing(account, 50, "你"); you.start(); girlFriend.start(); } } // 账户 class Account{ int money; // 余额 String name; // 卡名 public Account(int money, String name) { this.money = money; this.name = name; } } class Drawing extends Thread{ // 账户 Account account; // 取了多少钱 int drawingMoney; // 现在手里有多少钱 int nowMoney; public Drawing(Account account, int drawingMoney, String name) { super(name); // 父类的name 即线程的名字 this.account = account; this.drawingMoney = drawingMoney; } @SneakyThrows @Override public void run() { synchronized (account){ if (account.money - drawingMoney < 0){ System.out.println(Thread.currentThread().getName() + "取不了,因为" + account.name + "钱不够!"); return; } Thread.sleep(1000); // 卡内余额 account.money = account.money - drawingMoney; // 手里的钱 nowMoney = nowMoney + drawingMoney; //System.out.println(this.getName() + "取了" + drawingMoney); System.out.println(account.name + "余额为" + account.money); System.out.println(this.getName() + "手里的钱" + nowMoney); } } }
3.不安全的集合list
/** * @author gc * @date 2022/11/23 15:12 * note : list集合是不安全的 元素覆盖 */ public class UnSafeList { public static void main(String[] args) { List<String> list = new ArrayList<>(); for (int i = 0; i < 10000; i++) { new Thread(()->{ list.add(Thread.currentThread().getName()); }).start(); } try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(list.size()); } }
三、线程不安全解决方式
1.同步方法或同步块 (synchronized锁)
a、同步方法(note:用在方法上 控制这对象)
private synchronized void buy() throws InterruptedException { // 判断是否有票 while(ticketNum <= 0){ flag = false; return; } //lock.lock(); Thread.sleep(100); // 买票 System.out.println(Thread.currentThread().getName() + "拿到" + ticketNum-- + "张票!"); //lock.unlock(); }
b、同步块 (note:用在方法块中 被消费的对象)
@SneakyThrows @Override public void run() { synchronized (account){ if (account.money - drawingMoney < 0){ System.out.println(Thread.currentThread().getName() + "取不了,因为" + account.name + "钱不够!"); return; } Thread.sleep(1000); // 卡内余额 account.money = account.money - drawingMoney; // 手里的钱 nowMoney = nowMoney + drawingMoney; //System.out.println(this.getName() + "取了" + drawingMoney); System.out.println(account.name + "余额为" + account.money); System.out.println(this.getName() + "手里的钱" + nowMoney); } }
2.lock锁
/** * @author gc * @date 2022/11/23 17:44 * note : 演示lock锁的使用 */ public class LockThread { public static void main(String[] args) { TestLock testLock = new TestLock(); new Thread(testLock).start(); new Thread(testLock).start(); new Thread(testLock).start(); } } class TestLock implements Runnable{ int ticketNum = 10; private final Lock lock = new ReentrantLock(); @Override public void run() { while(true){ try { lock.lock(); if (ticketNum > 0){ try { // 这里的延时是为了展示 抢票是按着队列一个一个来的 只有第一个抢到票并释放锁,队列中的下一个线程才获得锁 并继续执行! Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(ticketNum--); } else { break; } } finally { lock.unlock(); // 必须手动解锁 } } } }