写点什么

4 种比常见的线程池和线程同步买票问题

  • 2025-06-09
    福建
  • 本文字数:4943 字

    阅读完需:约 16 分钟

线程池


所谓的线程池:其实就是线程对象的容器。可以根据需要,在启动时,创建 1 个或者多个线程对象。java 中有 4 种比较常见的线程池。1.固定数量的线程对象。2.根据需求动态创建线程:动态创建线程:根据需求来创建线程的个数,会自动给我们分配合适的线程个数来完成任务。3.单一线程。4.定时调度线程。


固定数量的线程对象



通过上面的图片,我们可以看见创建了 3 个线程对象。最初:user1 提交了一个任务 submit1,此时这 3 个线程对象都是空闲。都是可以去执行的。我们这里为了好理解,交给了 T1(当然 T2,T3 也是可以的)去执行。此时就空闲了 2 个线程(T2,T3),T1 在执行 submit1 然后 user2 也提交了一个任务 submit2,我们交给了 T2 去执行,此时空闲了 T3。然后 user3 也提交了一个任务 submit3,我们交给了 T3 去执行。此时没有空闲的了。T1,T2,T3 都在干活。然后来了一个 user4,它也提交给了一个任务 submit4。这个时候 3 个线程对象都在忙,submit4 这个任务只有等待 T1 或 T2 或 T3 谁先完成手上的工作。假设是 T3 已经完成了 submit3 的任务,submit4 这个任务由 T3 开始干活。如果后面又来了一个 user5,提交了 submit5。submit5 这个任务只有等待 T1 或 T2 或 T3 谁先完成手上的工作。假设是 T2 已经完成了 submit2 的任务,submit5 这个任务由 T2 开始干活。以此类推...,这个就是线程池对象帮我们实现的功能。


创建固定数量的线程对象执行多个任务,看线程池对象时如何分配任务的。


package part;
import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;
public class Java01 { public static void main(String[] args) { // 创建固定数量的线程对象 // ExecutorService是线程服务对象,这里我创建了3个线程对象 ExecutorService executor = Executors.newFixedThreadPool(3); //我提交5次,看下线程对象的执行情况,他是如何分配任务的 for (int i = 0; i < 5; i++) { //提交任务,这里我写的是一个匿名类 executor.submit(new Runnable() { // 重写了run方法 @Override public void run() {// 名称:pool-1-thread-1// 名称:pool-1-thread-3// 名称:pool-1-thread-2// 名称:pool-1-thread-3// 名称:pool-1-thread-1// 我们发现 线程 pool-1-thread-1和pool-1-thread-3 执行了2次任务,线程pool-1-thread-2执行了1次任务 System.out.println("名称:"+ Thread.currentThread().getName()); } }); } }}
复制代码


根据需求动态创建线程


package part;
import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;
public class Java01 { public static void main(String[] args) { // 根据需求动态创建线程 ExecutorService executor = Executors.newCachedThreadPool(); for (int i = 0; i < 5; i++) { // 提交任务,这里我写的是一个匿名类 executor.submit(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()); } }); } }}//pool-1-thread-2//pool-1-thread-4//pool-1-thread-1//pool-1-thread-3//pool-1-thread-5// 我们创建了5个线程
复制代码


根据需求动态创建线程


package part;
import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;
public class Java01 { public static void main(String[] args) { // 根据需求动态创建线程 ExecutorService executor = Executors.newCachedThreadPool(); // 这里循环5次,有5个线程,等会看下有几个线程,按理说:有几个线程,就会产生几个线程 for (int i = 0; i < 20; i++) { // 提交任务,这里我写的是一个匿名类 executor.submit(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()); } }); } }}//pool-1-thread-1//pool-1-thread-4//pool-1-thread-5//pool-1-thread-6//pool-1-thread-3//pool-1-thread-8//pool-1-thread-12//pool-1-thread-2//pool-1-thread-10//pool-1-thread-4//pool-1-thread-13//pool-1-thread-14//pool-1-thread-9//pool-1-thread-7//pool-1-thread-15//pool-1-thread-4//pool-1-thread-10//pool-1-thread-13//pool-1-thread-6//pool-1-thread-11// 我们发现 线程 pool-1-thread-4 干了3次活。 线程pool-1-thread-10干了2次活。// 动态创建线程:根据需求来创建线程的个数,会自动给我们分配合适的线程个数来完成任务
复制代码


单一线程


package part;
import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;
public class Java01 { public static void main(String[] args) { // 单一线程 ExecutorService executor = Executors.newSingleThreadExecutor(); // 虽然创建了3个线程需要主要,但是我只有1个线程,执行完一个在继续下一个线程 for (int i = 0; i < 3; i++) { executor.submit(new Runnable(){ @Override public void run() { System.out.println(Thread.currentThread().getName()); } }); } }}//pool-1-thread-1//pool-1-thread-1//pool-1-thread-1// 输出来的都是:pool-1-thread-1。说明这个线程干了3次活。
复制代码


线程同步:synchronized


synchronized 是一个同步关键字,使用 synchronized 修饰的方法。只能一个一个的访问(A 访问完了之后,B 才能进来),同步操作。synchronized 它还可以修饰代码块,称之为同步代码块。使用 synchronized 修饰的类型,变成了同步,访问效率低。Hashtable 类型的就是同步修饰的。因此访问效率低。语法:synchronized (用于同步的对象){处理逻辑}


线程异步出现的问题


现在我们来模拟用户买票的行为。假设现在有 3 张票,3 个用户来买,我们等下会发生写什么?


package part;
public class Java01 { public static void main(String[] args) { // Hashtable类型的就是同步修饰的。访问修饰的效率低。 // synchronized 是一个同步关键字,它还可以修饰代码块,称之为同步代码块 // 多个线程访问同步方法,只能一个一个的访问(A访问完了之后,B才能进来),同步操作 TicketSeller user1 = new TicketSeller(); for (int i=0;i<4;i++){ int userId = i + 1; new Thread(new Runnable() { @Override public void run() { // 为啥这里不能直接使用变量i ??? user1.sellTicket("用户" + userId); System.out.println(); } }).start(); } }}
class TicketSeller { private int tickets = 3; // 假设有3张票 synchronized public void sellTicket(String name) { if (tickets > 0) { --tickets; try { // 模拟买票花费的时候 Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(name+ "买了1张,现在还剩下: " + tickets); } else { System.out.println("没有票拉"); } }}
复制代码


我们发信买了一张票之后,就输出无票了。原因是:线程是异步的。在某个时刻大家都抢到了。



使用同步 synchronized 来解决这个问题


package part;
public class Java01 { public static void main(String[] args) { // Hashtable类型的就是同步修饰的。访问修饰的效率低。 // synchronized 是一个同步关键字,它还可以修饰代码块,称之为同步代码块 // 多个线程访问同步方法,只能一个一个的访问(A访问完了之后,B才能进来),同步操作 TicketSeller user1 = new TicketSeller(); for (int i=0;i<10;i++){ int userId = i + 1; new Thread(new Runnable() { @Override public void run() { // 为啥这里不能直接使用变量i ??? user1.sellTicket("用户" + userId); System.out.println(); } }).start(); } }}
class TicketSeller { private int tickets = 3; // 假设有3张票 public void sellTicket(String name) { if (tickets > 0) { --tickets; System.out.println(name+ "买了1张,现在还剩下: " + tickets); } else { System.out.println("没有票拉"); } }}
复制代码



为什么在匿名内部类里不能直接使用外部的循环变量 i


public class Java01 {    public static void main(String[] args) {        TicketSeller user1 = new TicketSeller();        for (int i=0;i<10;i++){            int userId = i + 1;            new Thread(new Runnable() {                @Override                public void run() {                    // 为啥这里不能直接使用变量 i ?                    // 报错提示:Variable 'i' is accessed from within inner class, needs to be final or effectively final                    user1.sellTicket("用户" + i);                    System.out.println();                }            }).start();        }    }}
复制代码


原因:首先在匿名内部类中,比如这里的 Runnable 实现。如果它访问了外部方法的局部变量,那么这个变量必须是 final 或者等效 final 的。也就是说,变量在初始化之后不能被修改。原来的代码中,循环变量 i 在每次迭代的时候都会被改变。上面从 0 增加到 3。这时候如果直接在内部类里使用 i,因为 i 的值在变化,就会导致问题。解决办法:新增一个变量,每次迭代的时候,这新变量都是一个最新的值,且每次迭代中新变量都是独立的。也就是说:我们这里把 i 的值赋给了一个新的局部变量 userId。每次循环迭代的时候,这个 userId 都是一个新变量。且每个迭代中的 userId 都是独立的,所以每个内部类实例都会有自己的 userId,这个值在创建的时候被赋值之后就不再改变(也就是等效 final 了)这样每个线程都能正确获取到对应的 userId,而不会受到后续循环改变 i 的影响了。


启动线程链式调用


Thread t1 = new Thread(new Runnable() {    @Override    public void run() {        user1.sellTicket("用户1");    }});// 要执行线程中的代码,需要执行start方法哈t1.start();等价与下面的代码new Thread(new Runnable() {    @Override    public void run() {        user1.sellTicket("用户1");    }}).start();
复制代码


使用 Lambda 简化代码


Lambda 表达式是一种匿名函数,可以作为参数传递或存储在变量中。


for (int i=0;i<4;i++){    int userId = i + 1;    new Thread(new Runnable() {        @Override        public void run() {            user1.sellTicket("用户" + userId);        }    }).start();}
复制代码


for (int i = 0; i < 4; i++) {    int userId = i + 1;    // ()-> {} 等价 new Runnable() { @Override public void run() { user1.sellTicket("用户" + userId); } }    new Thread(() -> {        user1.sellTicket("用户" + userId);    }).start();}
复制代码


文章转载自:何人陪我共长生

原文链接:https://www.cnblogs.com/ishoulgodo/p/18704966

体验地址:http://www.jnpfsoft.com/?from=001YH

用户头像

还未添加个人签名 2025-04-01 加入

还未添加个人简介

评论

发布
暂无评论
4种比常见的线程池和线程同步买票问题_Java_量贩潮汐·WholesaleTide_InfoQ写作社区