SingleThreadPool

2022-03-09  本文已影响0人  程序员札记

SingleThreadPool 的构造函数如下

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()));
    }

newSingleThreadExecutor() 这是一个单线程池,至始至终都由一个线程来执行。它是FxiedThreadPool的极端方式。 作用:该方法返回一个只有一个线程的线程池,即每次只能执行一个线程任务,多余的任务会保存到一个任务 。FixedThreadPool和newSingleThreadExecutor:都有的问题申请解决队列可能会消耗十分大的内存,甚至OOM。

singleThreadExecutor的意义

Java中的singleThreadExecutor表示单线程池,就是这个线程池里面只有一个线程,这个对象存在的意义是什么?跟直接起一个线程有什么区别?

我的理解是不是单线程池的好处就是线程可以复用,将单线程池singleThreadExecutor声明成一个静态变量,完后在不同的A类,B类,C类中都可以在singleThreadExecutor的execute方法中去启动线程


singleThreadExecutor.execute(new Runnable() {
         
                @Override
                public void run() {
                    
                }

}

这里虽然每次都用了new Runnable关键字,但这几个线程其实都是同一个线程?是这样理解吗?

首先要弄情况一个Runnable并非是一个线程,一个Thread才是一个线程。 singleThreadExecutor内部会创建一个Thread,这个Thread的工作就是从一个队列中取出用户提交的任务进行执行,如果执行过程中发生未受检的异常,singleThreadExecutor会自动重新启动一个线程再继续工作,这一点比用户自己创建一个线程自己管理轻松多了。同时自己维护一个任务队列也不是件简单的事,所以singleThreadExecutor的意义还是很大的。

一个线程的创建方式,可以自己通过直接new Thread的方式创建并启动。也可能通过new ThreadPool的方式间接创建。具体采用哪种,跟实际应用场景有关。 如对于满足以下场景可以直接用new Thread的方式。而不需要采用ThreadPool 1、线程执行任务内容已确定 2、线程不需要频繁创建和销毁 3、线程不需要重用(跟第2点有点类似) 场景举例:某项目中,需要提供一个定时记录用户在线数量的功能,记录操作异步独立进行。抽象代码如下:

public class Test {
    public static int userNum = 10;
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while(true) {
                    System.out.println("记录用户数:" + userNum);
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
 
        //do other something.....
 
    }
}

该线程会一直存在,异步独立运行着。这个场景下功能比较简单,是没必要采用singleThreadExecutor的。singleThreadExecutor是通过创建一个线程池来管理。内部做了大量处理,如自定义很多处理类、建立容器接收任务、处理任务时的加锁解锁操作、线程的状态判断等等。采用后者相关于做了很多没必要的工作。且内存占用也比前者多。 现在在换一种场景:某项目中,需要提供当有用户登录或下线时记录用户在线数量的功能。记录操作异步独立进行。上述代码可以调整为:

public class Test {
    public static int userNum = 10;
    public static void main(String[] args) {
        //do other something.....
        login();
        //do other something.....
        logout();
    }
 
    private static void logout() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("记录用户数:" + userNum);
            }
        }).start();
    }
 
    private static void login() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("记录用户数:" + userNum);
            }
        }).start();
    }
}


这种场景下,你会发现该实现操作会出现大量线程的频繁创建的销毁。所以我们肯定要做一些优化。如优化成下面这种:


public class Test {
    public static int userNum = 10;
    static RecordThread th;
    static {
        th = new RecordThread();
        th.start();
        System.out.println("记录线程已启动");
    }
    public static void main(String[] args) {
        System.out.println("do something.....");
        login();
        System.out.println("do something.....");
        logout();
    }
 
    private static void logout() {
        th.addTask(new Runnable() {
            @Override
            public void run() {
                System.out.println("记录用户数:" + userNum);
            }
        });
    }
 
    private static void login() {
        th.addTask(new Runnable() {
            @Override
            public void run() {
                System.out.println("记录用户数:" + userNum);
            }
        });
    }
}
 
class RecordThread extends Thread {
    List<Runnable> taskList = new ArrayList<>();
    @Override
    public void run() {
        while (true) {
            if (taskList.size() > 0) {
                Runnable task = taskList.remove(0);
                task.run();
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public void addTask(Runnable task) {
        taskList.add(task);
    }
}

优化后我们又会发现新的问题:线程并发安全问题没处理(ArrayList)、任务执行异常将导致线程终止,如下代码:

private static void logout() {
      th.addTask(new Runnable() {
          @Override
          public void run() {
              throw new NullPointerException();
          }
      });
  }

所以后续代码你会继续优化。最终随着你的优化,你会发现,最终代码相当于是你自己写了一个类似ThreadPool的管理类。 所以二者如何使用。楼主需要先了解二者的所拥有的功能。再实际场景中再去做选择。
综上所述,在全局需要要一个thread 的时候,就需要SingleThreadPool

SingleThreadPool 和 FixThreadPool(1) 有啥区别

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }


FinalizableDelegatedExecutorService 这个类不可以造型成 ThreadPool进行coresize的修改了。所以和FixThreadPool 最大的区别就是SingleThreadExecutor 不可以修改CoreSize。

上一篇下一篇

猜你喜欢

热点阅读