title: Java_JUC categories: - 后端 - Java tags: - 并发 - 多线程 author: - Jungle date: 2021-08-10 20:42:30
# Thread
火车票多线程抢票
1. 初始构思
思路:
用
static
修饰ticketNum
,使其唯一;并设置
volatile
,保证变量在多个线程之间的可见性。
package com.Jungle; /* * 遇到问题: * 1. 如何解决用户同时抢一张票的问题? * */ public class myThread implements Runnable { // 有多少张火车票 private volatile static int ticketNum = 10; @Override public void run() { while (true) { if (ticketNum <= 0) return; try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread() + " 拿到了第" + ticketNum-- + "张票!"); } } public static void main(String[] args) { myThread t1 = new myThread(); myThread t2 = new myThread(); myThread t3 = new myThread(); new Thread(t1, "小明").start(); new Thread(t2, "老师").start(); new Thread(t3, "黄牛党").start(); } }
失败了!
// 输出 Thread[小明,5,main] 拿到了第10张票! Thread[老师,5,main] 拿到了第8张票! Thread[黄牛党,5,main] 拿到了第9张票! Thread[黄牛党,5,main] 拿到了第7张票! Thread[小明,5,main] 拿到了第5张票! Thread[老师,5,main] 拿到了第6张票! Thread[黄牛党,5,main] 拿到了第4张票! Thread[老师,5,main] 拿到了第2张票! Thread[小明,5,main] 拿到了第3张票! Thread[黄牛党,5,main] 拿到了第1张票! Thread[小明,5,main] 拿到了第-1张票! Thread[老师,5,main] 拿到了第0张票! Process finished with exit code 0
2. 使用外部停止方式
思想:
flag == false
时停止!
// 改名 myThread --> BuyTicket, 放在新建的UnsafeBuyTicket类里 package com.Jungle; public class UnsafeBuyTicket { public static void main(String[] args) { //在同一个车站窗口买票 BuyTicket station = new BuyTicket(); new Thread(station, "小明").start(); new Thread(station, "老师").start(); new Thread(station, "黄牛党").start(); } } class BuyTicket implements Runnable { // 有多少张火车票 private volatile static int ticketNum = 10; boolean flag = true; // 外部停止方式 private void buy() throws InterruptedException { // 判断是否有票(无票立即结束) if (ticketNum <= 0) { flag = false; return; } // 模拟延时 Thread.sleep(100); // 买票 System.out.println(Thread.currentThread() + " 拿到了第" + ticketNum-- + "张票!"); } // 买票 @Override public void run() { // 买票 while (flag) { try { buy(); } catch (InterruptedException e) { e.printStackTrace(); } } } }
输出: 出现负数线程不安全!
Thread[老师,5,main] 拿到了第10张票! Thread[小明,5,main] 拿到了第9张票! Thread[黄牛党,5,main] 拿到了第8张票! Thread[小明,5,main] 拿到了第7张票! Thread[老师,5,main] 拿到了第6张票! Thread[黄牛党,5,main] 拿到了第5张票! Thread[小明,5,main] 拿到了第4张票! Thread[老师,5,main] 拿到了第3张票! Thread[黄牛党,5,main] 拿到了第2张票! Thread[小明,5,main] 拿到了第1张票! Thread[老师,5,main] 拿到了第-1张票! Thread[黄牛党,5,main] 拿到了第0张票! Process finished with exit code 0
为什么出现会出现负数?
每个线程在自己的工作内存交互,内存控制不当会造成数据不一致.
同时看到最后一张票, 判断通过(此时票数为1), 其中一人取票后变为负数, 其余人输出负数.
线程不安全的集合:
ArrayList
LinkedList
HashMap
3. 怎么解决线程不安全?
synchronize: 底层原理是同步+锁
案例分析:
对于火车票抢票, 多个人在同一个窗口进行, 可以直接在buy()方法里加
synchronized
关键字, 会对本类对象(BuyTicket station = new BuyTicket();
)进行锁定和排队.银行家案例, 见下分析
package com.Jungle; public class UnsafeBuyTicket { public static void main(String[] args) { //在同一个车站窗口买票 BuyTicket station = new BuyTicket(); new Thread(station, "小明").start(); new Thread(station, "老师").start(); new Thread(station, "黄牛党").start(); } } class BuyTicket implements Runnable { // 有多少张火车票 private volatile static int ticketNum = 10; boolean flag = true; // 外部停止方式 // 同步方法 private synchronized void buy() throws InterruptedException { // 判断是否有票(无票立即结束) if (ticketNum <= 0) { flag = false; return; } // 模拟延时 Thread.sleep(100); // 买票 System.out.println(Thread.currentThread() + " 拿到了第" + ticketNum-- + "张票!"); } // 买票 @Override public void run() { // 买票 while (flag) { try { buy(); } catch (InterruptedException e) { e.printStackTrace(); } } } }
输出:
Thread[小明,5,main] 拿到了第10张票! Thread[小明,5,main] 拿到了第9张票! Thread[黄牛党,5,main] 拿到了第8张票! Thread[老师,5,main] 拿到了第7张票! Thread[老师,5,main] 拿到了第6张票! Thread[黄牛党,5,main] 拿到了第5张票! Thread[黄牛党,5,main] 拿到了第4张票! Thread[黄牛党,5,main] 拿到了第3张票! Thread[黄牛党,5,main] 拿到了第2张票! Thread[小明,5,main] 拿到了第1张票! Process finished with exit code 0
若不是本类对象的同步,则需要使用共享资源作为同步监视器
案例分析:
对于双方对同一账户取钱来说, 启动线程的是双方, 共两个线程, 共享资源是银行账户, 所以需要用到同步代码块, 即锁定银行账户.
package com.Jungle; import java.nio.charset.StandardCharsets; public class UnsafeBank { public static void main(String[] args) { Account account = new Account(100, "结婚基金"); UserDrawing you = new UserDrawing(account, 50, "你"); UserDrawing yourGF = new UserDrawing(account, 100, "你的女朋友"); you.start(); yourGF.start(); } } // 银行账户 class Account { int balance; // 余额 String cardName; // 卡名 public Account(int balance, String cardName) { this.balance = balance; this.cardName = cardName; } } // 用户取钱 class UserDrawing extends Thread { Account account; // 账户 int drawingCount; // 取多少钱(单位:万) int cash; // 取钱后的手里的钱 public UserDrawing(Account account, int drawingCount, String userName) { super(userName); this.account = account; this.drawingCount = drawingCount; } // 取钱 @Override public void run() { // 锁账户 synchronized (account) { if (account.balance < drawingCount) { System.out.println(account.cardName + "余额不足!"); return; } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } account.balance -= drawingCount; // 账户里的钱减少 cash += drawingCount; // 手里的钱增多 System.out.println(new StringBuilder(). append(account.cardName).append("余额为: ").append(account.balance)); System.out.println(new StringBuilder(). append(this.getName()).append("手里的钱: ").append(cash)); } } }
输出:
结婚基金余额为: 50 你手里的钱: 50 结婚基金余额不足! Process finished with exit code 0
案例3: 不安全的集合
package com.Jungle; import java.util.ArrayList; import java.util.List; // 线程不安全的集合 public class UnsafeList { public static void main(String[] args) { List<String> list = new ArrayList<String>(); for (int i = 0; i < 10000; i++) { new Thread(()->{ synchronized (list) { list.add(Thread.currentThread().getName()); } }).start(); } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(list.size()); } }
输出:
10000 Process finished with exit code 0
CopyOnWriteArrayList
Copy On Write 也是一种重要的思想,在写少读多的场景下,为了保证集合的线程安全性,我们完全可以在当前线程中得到原始数据的一份拷贝,然后进行操作。JDK集合框架中为我们提供了 ArrayList 的这样一个实现:CopyOnWriteArrayList。但是如果不是写少读多的场景,使用 CopyOnWriteArrayList 开销比较大,因为每次对其更新操作(add/set/remove)都会做一次数组拷贝。
package com.Jungle; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; public class JUCTest { public static void main(String[] args) { List<String> list = new CopyOnWriteArrayList<String>(); for (int i = 0; i < 10000; i++) { new Thread(()->{ list.add(Thread.currentThread().getName()); }).start(); } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(list.size()); } }
涉及到的源码
上文对应的思路应该是: 先复制一份数据, 然后add, 最后再新的数组引用赋值回去.
所以, 针对本例不太适合!
适合: 写少读多
public CopyOnWriteArrayList() { setArray(new Object[0]); } final void setArray(Object[] a) { array = a; } final transient ReentrantLock lock = new ReentrantLock(); /** The array, accessed only via getArray/setArray. */ private transient volatile Object[] array; final Object[] getArray() { return array; } public boolean add(E e) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len + 1); newElements[len] = e; setArray(newElements); return true; } finally { lock.unlock(); } } // ReentrantLock: public void lock() { sync.lock(); } private final Sync sync; public void unlock() { sync.release(1); }
Collections.synchronizedList()
package com.Jungle; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; public class JUCTest { public static void main(String[] args) { // List<String> list = new CopyOnWriteArrayList<String>(); List<String> list = Collections.synchronizedList(new ArrayList<String>()); for (int i = 0; i < 10000; i++) { new Thread(()->{ list.add(Thread.currentThread().getName()); }).start(); } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(list.size()); } }
输出:
10000 Process finished with exit code 0
涉及到的源码
public static <T> List<T> synchronizedList(List<T> list) { return (list instanceof RandomAccess ? new SynchronizedRandomAccessList<>(list) : new SynchronizedList<>(list)); } static class SynchronizedRandomAccessList<E> extends SynchronizedList<E> implements RandomAccess { SynchronizedRandomAccessList(List<E> list) { super(list); } } static class SynchronizedList<E> extends SynchronizedCollection<E> implements List<E> { private static final long serialVersionUID = -7754090372962971524L; final List<E> list; SynchronizedList(List<E> list) { super(list); this.list = list; } } static class SynchronizedCollection<E> implements Collection<E>, Serializable { private static final long serialVersionUID = 3053995032091335093L; final Collection<E> c; // Backing Collection final Object mutex; // Object on which to synchronize ---> 加锁的对象 SynchronizedCollection(Collection<E> c) { this.c = Objects.requireNonNull(c); mutex = this; } SynchronizedCollection(Collection<E> c, Object mutex) { this.c = Objects.requireNonNull(c); this.mutex = Objects.requireNonNull(mutex); } public boolean add(E e) { synchronized (mutex) {return c.add(e);} } public boolean remove(Object o) { synchronized (mutex) {return c.remove(o);} } }
ThreadPoolExecutor
最终采用线程池方式实现!
使用阿里巴巴推荐的创建线程池的方式
package com.Jungle; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class JUCTest { private static final int CORE_POOL_SIZE = 5; private static final int MAX_POOL_SIZE = 10; private static final int QUEUE_CAPACITY = 100; private static final Long KEEP_ALIVE_TIME = 1L; public static void main(String[] args) { // List<String> list = new CopyOnWriteArrayList<String>(); List<String> list = Collections.synchronizedList(new ArrayList<String>()); //使用阿里巴巴推荐的创建线程池的方式 //通过ThreadPoolExecutor构造函数自定义参数创建 ThreadPoolExecutor executor = new ThreadPoolExecutor( CORE_POOL_SIZE, // 核心线程数为 5 MAX_POOL_SIZE, // 最大线程数 10 KEEP_ALIVE_TIME, // 大于核心线程数时,核心线程外的线程等待时间: 1L TimeUnit.SECONDS, // 等待时间的单位为 TimeUnit.SECONDS new ArrayBlockingQueue<>(QUEUE_CAPACITY), // workQueue: 任务队列为 ArrayBlockingQueue,并且容量为 100 new ThreadPoolExecutor.CallerRunsPolicy()); // handler: 饱和策略为 CallerRunsPolicy for (int i = 0; i < 10000; i++) { Runnable worker = ()->{ list.add(Thread.currentThread().getName()); }; //执行Runnable executor.execute(worker); } //终止线程池 executor.shutdown(); while (!executor.isTerminated()) { } System.out.println("Finished all threads"); System.out.println(list.size()); } }
输出
Finished all threads 10000 Process finished with exit code 0
目录
Collections.synchronizedList()