Java之线程和线程池

 

 

 

 

 

 

 

 

 

 

 

 

 

 

创建线程的四种方法以及区别

1.线程是什么?

        线程被称为轻量级进程,是程序执行的最小单位,它是指在程序执行过程中,能够执行代码的一个执行单位。每个程序程序都至少有一个线程,也即是程序本身

2.线程状态

        Java语言定义了5种线程状态,在任意一个时间点,一个线程只能有且只有其中一个状态。,这5种状态如下:

(1)新建(New):创建后尚未启动的线程处于这种状态

(2)运行(Runable):Runable包括了操作系统线程状态的Running和Ready,也就是处于此状态的线程有可能正在执行,也有可能正在等待着CPU为它分配执行时间。

(3)等待(Wating):处于这种状态的线程不会被分配CPU执行时间。等待状态又分为无限期等待和有限期等待,处于无限期等待的线程需要被其他线程显示地唤醒,没有设置Timeout参数的Object.wait()、没有设置Timeout参数的Thread.join()方法都会使线程进入无限期等待状态;有限期等待状态无须等待被其他线程显示地唤醒,在一定时间之后它们会由系统自动唤醒,Thread.sleep()、设置了Timeout参数的Object.wait()、设置了Timeout参数的Thread.join()方法都会使线程进入有限期等待状态。

(4)阻塞(Blocked):线程被阻塞了,“阻塞状态”与”等待状态“的区别是:”阻塞状态“在等待着获取到一个排他锁,这个时间将在另外一个线程放弃这个锁的时候发生;而”等待状态“则是在等待一段时间或者唤醒动作的发生。在程序等待进入同步区域的时候,线程将进入这种状态。

(5)结束(Terminated):已终止线程的线程状态,线程已经结束执行。

下图是5种状态转换图:

 

                

3.线程同步方法

线程有4中同步方法,分别为wait()、sleep()、notify()和notifyAll()。

wait():使线程处于一种等待状态,释放所持有的对象锁。

sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用它时要捕获InterruptedException异常,不释放对象锁。

notify():唤醒一个正在等待状态的线程。注意调用此方法时,并不能确切知道唤醒的是哪一个等待状态的线程,是由JVM来决定唤醒哪个线程,不是由线程优先级决定的。

notifyAll():唤醒所有等待状态的线程,注意并不是给所有唤醒线程一个对象锁,而是让它们竞争。

4.创建线程的方式

在JDK1.5之前,创建线程就只有两种方式,即继承java.lang.Thread类和实现java.lang.Runnable接口;而在JDK1.5以后,增加了两个创建线程的方式,即实现java.util.concurrent.Callable接口和线程池。下面是这4种方式创建线程的代码实现。

1)继承Thread类创建线程

class MyThread extends Thread{
    @Override
    public void run() {
        // 重写run方法
    }
}

2)实现Runnable接口创建线程

public class ThreadTest {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        Thread tt = new Thread(thread);
    }
}

class MyThread implements Runnable{
    @Override
    public void run() {
        // 重写run方法
    }
}

3)使用Callable和Future创建线程

public class ThreadTest {

    public static void main(String[] args) throws Exception {
        // 创建Callable
        MyCallable myCallable = new MyCallable();
        // 创建FutureTask
        FutureTask<Integer> futureTask = new FutureTask<Integer>(myCallable);
        // 配置到Thread
        Thread thread = new Thread(futureTask, "AAA");
        // 启动线程
        thread.start();
        // 获取返回值
        int result01 = 100;

        while(!futureTask.isDone()){
            // 等待计算结果
        }
        
        // 阻塞计算,如果超过时间 抛出java.util.concurrent.TimeoutException异常
        int result02 = futureTask.get(2, TimeUnit.SECONDS);
        System.out.println("计算结果\t" + (result01 + result02));
    }
}

class MyCallable implements Callable<Integer>{

    @Override
    public Integer call() throws Exception {
        try { TimeUnit.MILLISECONDS.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); }
        return 1024;
    }
}

4)使用线程池例如用Executor框架

    public static void main(String[] args) {
//        ExecutorService threadPool = Executors.newFixedThreadPool(5);// 一池5个处理线程
//        ExecutorService threadPool = Executors.newSingleThreadExecutor();// 一池1个处理线程
        ExecutorService threadPool = Executors.newCachedThreadPool();//一池N处理线程

        // 模拟10个用户来办理业务,每个用户就是一个来自外部的请求线程
        try {
            for (int i = 1; i <= 50; i++) {
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName() + "\t办理业务");
                    try { TimeUnit.MILLISECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }
    }

为什么使用线程池?

线程池的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量之后超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行。

线程池主要特点:线程复用、控制最大并发数、管理线程

  1. 降低资源消耗,通过重复利用已创建的线程降低县城创建和销毁造成的消耗。
  2. 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
  3. 提高现成的可管理型,线程是稀缺资源,如果无限的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

线程池的种类:

  1. Executors.newScheduledThreadPool();  // 带调度的线程池
  2. Executors.newWorkStealingPool(int);    // JDK1.8新出
  3. Executors.newFixedThreadPool(int);
  4. Executors.newSingleThreadExecutor();
  5. Executors.newCachedThreadPool();

线程池源码解析:

1、定长线程池:

  1. 创建一个定长线程池,可控制现成最大并发数,超出的线程会在队列中等待
  2. newFixedThreadPool创建的线程池corePoolSize和maximumPoolSize值是相等的,它使用LinkedBlockingQueue;
// 定长线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
	return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS,
								  new LinkedBlockingQueue<Runnable>());
}

2、单个线程线程池:

  1. 创建一个线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序执行。
  2. newSingleThreadExecutor将corePoolSize和maximunPoolSize都设置为1,它使用的LinkedBlockingQueue。
// 单个线程的线程池
public static ExecutorService newSingleThreadExecutor() {
	return new FinalizableDelegatedExecutorService
		(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
								new LinkedBlockingQueue<Runnable>()));
}

3、可缓存线程池:

  1. 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
  2. newCachedThreadPool将corePoolSize设置为0,将maximumPoolSize设置为Integer.MAX_VALUE,它使用的SynchronousQueue,也就是说来了任务就创建线程运行,当线程空闲超过60秒,就销毁线程。
// 缓冲池线程
public static ExecutorService newCachedThreadPool() {
	return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,
								  new SynchronousQueue<Runnable>());
}

4、线程池的七大参数

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }
  1. int corePoolSize:线程池中的常驻核心线程数
  2. int maximumPoolSize:线程池能够容纳同时执行的最大线程数,此值必须大于等于1
  3. long keepAliveTime:多余的空闲线程的存活时间,当前线程池数量超过corePoolSize时,当空闲时间达到keepAliveTime值时,多余空闲线程会被销毁直到只剩下corePoolSize个线程为止
  4. TimeUnit unit:keepAliveTime的单位
  5. BlockingQueue<Runnable> workQueue:任务队列,被提交但尚未被执行的任务。
  6. ThreadFactory threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程一般用默认的即可
  7. RejectedExecutionHandler handler:拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数

 

 

  1. 在创建了线程池后,等待提交过来的任务请求。
  2. 档调用execute()方法添加一个请求任务时,线程池会做如下判断:
    1. 如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务
    2. 如果正在运行的线程数大于或等于corePoolSize,那么将这个任务放入队列
    3. 如果这时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务
    4. 如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会自动饱和拒绝策略来执行。
  3. 当一个线程完成任务时,它会从队列中取下一个任务来执行
  4. 当一个线程无事可做超过一定时间(KeepAliveTime)时,线程池会判断
    1. 如果当前运行的线程数大于codePoolSize,那么这个线程就被停掉
    2. 所以线程池的所有任务完成后它最终会收缩到corePoolSize的大小

 

线程池的拒绝策略

等待队列也已经排满了,再也塞不下新任务了,同时线程池中的max线程也达到了,无法继续为新任务服务。这时候我们就需要拒绝策略机制合理的处理这个问题。

  • AbortPolicy(默认):直接抛出java.util.concurrent.RejectedExecutionException异常阻止系统正常运行。
  • CallerRunsPolicy:"调用者运行"一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量。
  • DiscardOldestpolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交任务。
  • DiscardPolicy:直接丢弃任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种方案。
AbortPolicy:丢弃任务,抛出异常
    抛出java.util.concurrent.RejectedExecutionException异常

DiscardPolicy:丢弃任务,不抛出异常

CallerRunsPolicy:任务回退给调用者
    pool-1-thread-1	办理业务	1
    main	        办理业务	2

DiscardOldestPolicy:丢弃队列中排在首位的数据


class MyTask implements Runnable {

    private int taskId;
    private String taskName;

    public MyTask(int taskId, String taskName) {
        this.taskId = taskId;
        this.taskName = taskName;
    }
    public int getTaskId() {
        return taskId;
    }
    public void setTaskId(int taskId) {
        this.taskId = taskId;
    }
    public String getTaskName() {
        return taskName;
    }
    public void setTaskName(String taskName) {
        this.taskName = taskName;
    }

    @Override
    public void run() {
        try {
            System.out.println("run taskId =" + this.taskId);
            Thread.sleep(5 * 1000);
            //System.out.println("end taskId =" + this.taskId);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public String toString() {
        return Integer.toString(this.taskId);
    }

}

public class MyThreadDemo2 {

    public static void main(String[] args) {
        /**
         * 在使用有界队列时,若有新的任务需要执行,如果线程池实际线程数小于corePoolSize,则优先创建线程,
         * 若大于corePoolSize,则会将任务加入队列,
         * 若队列已满,则在总线程数不大于maximumPoolSize的前提下,创建新的线程,
         * 若线程数大于maximumPoolSize,则执行拒绝策略。或其他自定义方式。
         *
         */
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                1,              //coreSize
                2,              //MaxSize
                60,             //60
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<Runnable>(3),         //指定一种队列 (有界队列)
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.DiscardOldestPolicy()
        );

        MyTask mt1 = new MyTask(1, "任务1");
        MyTask mt2 = new MyTask(2, "任务2");
        MyTask mt3 = new MyTask(3, "任务3");
        MyTask mt4 = new MyTask(4, "任务4");
        MyTask mt5 = new MyTask(5, "任务5");
        MyTask mt6 = new MyTask(6, "任务6");

        pool.execute(mt1);
        pool.execute(mt2);
        pool.execute(mt3);
        pool.execute(mt4);
        pool.execute(mt5);
        pool.execute(mt6);

        pool.shutdown();
    }
}

问题2:你在工作中单一/固定数的/可变的三种创建线程池的方法,你用哪个多?超级大坑

回答:java自带的基本不用,高并发直接引起OOM,一般需要有一个管理者,如自己写个类来管理,或者交给Spring来管理,主要是控制线程数量避免引起OOM。

问题3:你在工作中是如何使用线程池的,是否自定义过线程池使用

ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
 1,
 10,
 1L,
 TimeUnit.SECONDS,
 new LinkedBlockingQueue<>(10),
 Executors.defaultThreadFactory(),
 new ThreadPoolExecutor.CallerRunsPolicy());

问题4:合理配置线程池是如何考虑的?

首先判断业务密度

// 查看计算机核心数
System.out.println(Runtime.getRuntime().availableProcessors());

CPU密集型:该任务需要大量的运算,而没有阻塞,CPU一直全力运行。 一般公式:CPU核心数+1个线程的线程池

IO密集型:任务线程并不是一直在执行任务,则应配置尽可能多的线程,如CPU核心数*2

             参考公式:CPU核心数 / (1 - 阻塞系数)    阻塞系数在0.8~0.9之间

             比如:8核CPU  8 / (1 -0.9) = 80 个线程数

问题5:线程池的submit和execute方法区别?

  1. 接收的参数不一样
  2. submit有返回值,而execute没有
  3. submit方便Exception处理

死锁

是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉那他们都将无法推进下去,如果系统资源充足,进程的资源请求能够得到满足,死锁出现的可能性就很低,否则会因争夺有限的资源而陷入死锁。

产生死锁的原因:

  • 系统资源不足
  • 进程运行推进的顺序不合适
  • 资源分配不当

问题排查:

  • jps命令定位进程号
  • jstack找到思索查看堆栈轨迹

 

 

 

delphi线程池单元文件uThreadPool.pas,用法如下 type TRecvCommDataWorkItem=class(TWorkItem) public // updatetime,addtime:TDateTime; // orderid,ordertype,urljson,loadcount,savepath:string; url,Filename:string; total,order:Integer; _orderid:string; failedcount:Integer; IFCoverFile:Boolean; // 线程处理请求时触发的事件 procedure DealwithCommRecvData(Sender: TThreadsPool; WorkItem: TWorkItem; aThread: TProcessorThread); // 线程初始化时触发的事件 procedure TProcessorThreadInitializing(Sender: TThreadsPool; aThread:TProcessorThread); // 线程结束时触发的事件 procedure TProcessorThreadFinalizing(Sender: TThreadsPool; aThread:TProcessorThread); //任务队列空时触发的事件 procedure TQueueEmpty(Sender: TThreadsPool; EmptyKind: TEmptyKind); end; 先声明一个类 然后用法 FThreadPool := TThreadsPool.Create(nil); // 创建线程池 FThreadPool.ThreadsMin := 10; // 初始工作线程数 FThreadPool.ThreadsMax := 100; // 最大允许工作线程数 AWorkItem := TRecvCommDataWorkItem.Create; ISAllOverLoad:=False; AWorkItem.url:=urljson; AWorkItem.order:=i; AWorkItem.total:=JA.Count; AWorkItem.Filename:=savefilepath; AWorkItem._orderid:=orderid; AWorkItem.IFCoverFile:=IFCoverFile; FThreadPool.AddRequest(AWorkItem,True); // 向线程池分配一个任务 FThreadPool.OnProcessRequest := AWorkItem.DealwithCommRecvData; FThreadPool.OnThreadInitializing := AWorkItem.TProcessorThreadInitializing; FThreadPool.OnThreadFinalizing := AWorkItem.TProcessorThreadFinalizing; FThreadPool.OnQueueEmpty := AWorkItem.TQueueEmpty; 仔细看下线程池单元的函数说明轻松搞定。 procedure TRecvCommDataWorkItem.TQueueEmpty(Sender: TThreadsPool; EmptyKind: TEmptyKind); begin if EmptyKind=ekProcessingFinished then begin try if Assigned(geturl) then //存在的bug 如果下载文件存在的不行 begin //Sleep(200); //激活线程可能会发生在 休眠之前!! ISAl
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值