智能配送设备 发表于 2025-2-8 18:40:10

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

线程池

所谓的线程池:其实就是线程对象的容器。
可以根据需要,在启动时,创建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张票synchronizedpublic   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张票    publicvoid 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();}尾声

准备开始学习java了。
今天学习的第八天,每天都会发文章,我要卷起来。
请小伙伴们监督我,奥利给
页: [1]
查看完整版本: 4种比常见的线程池和线程同步买票问题