目录
- 引出
 - 创建多线程的方法
 - Thread类创建多线程
 - Runnable接口创建多线程
 - Thread类的常用方法
 - 两者的对比
 
- 线程池
 - 是啥?为啥用?
 - 结合案例理解常用的线程池
 - 继承Runnable接口的工人实体类
 - newSingleThreadExecutor(单线程)
 - newFixedThreadPool(加锁)
 - newCachedThreadPool
 - newScheduledThreadPool(定时任务)
 - newSingleThreadScheduledExecutor
 
- 总结
 
引出
1.创建多线程两种方法,继承thread,实现runnable,常用runnable;
 2.线程池,管理线程,控制并发;
 3.常用的线程池,newFixedThreadPool定容量;
 4.newCachedThreadPool容量大,Integer.MAX_VALUE;
 5.newScheduledThreadPool(定时任务),newSingleThreadScheduledExecutor单线程的线程池的定时任务;
 6.newSingleThreadExecutor(单线程),单线程的线程池;


创建多线程的方法
JDK提供了Thread类和Runnable接口来让我们实现自己的“线程”类。
- 继承
Thread类,并重写run方法; - 实现
Runnable接口的run方法; 
Thread类创建多线程
继承
Thread类:
package com.tianju.book.jpa.syn;
public class Demo1 {
    public static class MyThread extends Thread{
        @Override
        public void run() {
            System.out.println("通过继承Thread创建多线程");
        }
    }
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
    }
}
 
注意要调用start()方法后,该线程才算启动!
我们在程序里面调用了start()方法后,虚拟机会先为我们创建一个线程,然后等到这个线程第一次得到时间片时再调用run()方法。
注意不可多次调用start()方法。在第一次调用start()方法后,再次调用start()方法会抛出IllegalThreadStateException异常。

Runnable接口创建多线程

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}
 

package com.tianju.book.jpa.syn;
public class Demo2 {
    public static class MyThread implements Runnable{
        @Override
        public void run() {
            System.out.println("继承runnable实现多线程");
        }
    }
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            System.out.println("匿名内部类");
            new MyThread().run();
        });
        thread.start();
    }
}
 
Thread类的常用方法
这里介绍一下Thread类的几个常用的方法:
- currentThread():静态方法,返回对当前正在执行的线程对象的引用;
 - start():开始执行线程的方法,java虚拟机会调用线程内的run()方法;
 - yield():yield在英语里有放弃的意思,同样,这里的yield()指的是当前线程愿意让出对当前处理器的占用。这里需要注意的是,就算当前线程调用了yield()方法,程序在调度的时候,也还有可能继续运行这个线程的;
 - sleep():静态方法,使当前线程睡眠一段时间;
 - join():使当前线程等待另一个线程执行完毕之后再继续执行,内部调用的是Object类的wait方法实现的;
 
两者的对比
实现一个自定义的线程类,可以有继承Thread类或者实现Runnable接口这两种方式,它们之间有什么优劣呢?
- 由于Java“单继承,多实现”的特性,Runnable接口使用起来比Thread更灵活。
 - Runnable接口出现更符合面向对象,将线程单独进行对象的封装。
 - Runnable接口出现,降低了线程对象和线程任务的耦合性。
 - 如果使用线程时不需要使用Thread类的诸多方法,显然使用Runnable接口更为轻量。
 
所以,我们通常优先使用“实现Runnable接口”这种方式来自定义线程类。
线程池
是啥?为啥用?
线程池是一种用于管理和复用线程的机制。它是一种线程管理的抽象概念,可以有效地管理线程的创建、执行和销毁,以提高应用程序的性能和资源利用率。
线程池中包含一组预先创建的线程,这些线程可以被重复使用来执行任务。当有任务需要执行时,线程池中的线程会被分配给任务,并在任务完成后返回线程池,以便可以被其他任务复用。这样可以避免频繁地创建和销毁线程,减少了线程创建和销毁的开销,提高了系统的响应速度和资源利用率。
使用线程池主要有以下三个原因:
- 创建/销毁线程需要消耗系统资源,线程池可以复用已创建的线程。
 - 控制并发的数量。并发数量过多,可能会导致资源消耗过多,从而造成服务器崩溃。(主要原因)
 - 可以对线程做统一管理。
 
结合案例理解常用的线程池
Executors类中提供的几个静态方法来创建线程池
继承Runnable接口的工人实体类
name:工人名字,
workTime:工作时间,用java的毫秒模拟现实中的1分钟
materials:物料的数量,假设一共有100个物料;
package com.tianju.book.jpa.syn;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
 * 工厂仿真的工人
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Worker implements Runnable{
    private String name;
    private Integer workTime;
    private static Integer materials=100; // 物料数量
    @Override
    public void run() {
        System.out.println(name+"开始工作");
        try {
            Thread.sleep(workTime); // 模拟工作时间
            materials--; // 每次消耗1个物料
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(name+"工作完成,耗时"+workTime+"分钟,"+"当前剩余物料:"+materials);
    }
}
 
newSingleThreadExecutor(单线程)
创建一个单线程的线程池,此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}
 
有且仅有一个核心线程( corePoolSize == maximumPoolSize=1),使用了LinkedBlockingQueue(容量很大),所以,不会创建非核心线程。所有任务按照先来先执行的顺序执行。如果这个唯一的线程不空闲,那么新来的任务会存储在任务队列里等待执行。
package com.tianju.book.jpa.syn;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
 * 进行工厂的仿真
 */
public class FactorySimulationSingle {
    public static void main(String[] args) {
        // 工人池,单线程的线程池
        ExecutorService workerPool = Executors.newSingleThreadExecutor();
        for (int i= 1;i<10;i++){
            Worker worker = new Worker("worker" + i, i * 10);
            workerPool.execute(worker); // 默认调用run方法,提交任务给线程池执行
        }
        Thread thread2 = new Thread(() -> {
            for (int i = 1; i < 10; i++) {
                Worker worker = new Worker("TH-worker" + i, i * 10);
                workerPool.execute(worker); // 默认调用run方法,提交任务给线程池执行
            }
        });
        thread2.start();
        try {
            Thread.sleep(20000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        workerPool.shutdown();
    }
}
 

newFixedThreadPool(加锁)
创建固定大小的线程池,每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
}
 
核心线程数量和总线程数量相等,都是传入的参数nThreads,所以只能创建核心线程,不能创建非核心线程。因为LinkedBlockingQueue的默认大小是Integer.MAX_VALUE,故如果核心线程空闲,则交给核心线程处理;如果核心线程不空闲,则入列等待,直到核心线程空闲。
与CachedThreadPool的区别:
- 因为 corePoolSize == maximumPoolSize ,所以FixedThreadPool只会创建核心线程。 而CachedThreadPool因为corePoolSize=0,所以只会创建非核心线程。
 - 在 getTask() 方法,如果队列里没有任务可取,线程会一直阻塞在 LinkedBlockingQueue.take() ,线程不会被回收。 CachedThreadPool会在60s后收回。
 - 由于线程不会被回收,会一直卡在阻塞,所以没有任务的情况下, FixedThreadPool占用资源更多。
 - 都几乎不会触发拒绝策略,但是原理不同。FixedThreadPool是因为阻塞队列可以很大(最大为Integer最大值),故几乎不会触发拒绝策略;CachedThreadPool是因为线程池很大(最大为Integer最大值),几乎不会导致线程数量大于最大线程数,故几乎不会触发拒绝策略。
 
ExecutorService workerPool = Executors.newFixedThreadPool(1);
设置线程池中线程数量为1,此时就等同于newSingleThreadExecutor

package com.tianju.book.jpa.syn;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
 * 进行工厂的仿真
 */
public class FactorySimulation {
    public static void main(String[] args) {
        // 工人池,固定大小为5
        ExecutorService workerPool = Executors.newFixedThreadPool(5);
        for (int i= 1;i<10;i++){
            Worker worker = new Worker("worker" + i, 1);
            workerPool.execute(worker); // 默认调用run方法,提交任务给线程池执行
        }
        Thread thread1 = new Thread(() -> {
            for (int i = 1; i < 10; i++) {
                Worker worker = new Worker("TH1-worker" + i, i * 10);
                workerPool.execute(worker); // 默认调用run方法,提交任务给线程池执行
            }
        });
        Thread thread2 = new Thread(() -> {
            for (int i = 1; i < 10; i++) {
                Worker worker = new Worker("TH2-worker" + i, i * 10);
                workerPool.execute(worker); // 默认调用run方法,提交任务给线程池执行
            }
        });
        thread1.start();
        thread2.start();
        try {
            Thread.sleep(20000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        workerPool.shutdown();
    }
}
 

newCachedThreadPool
创建一个可缓存的线程池,此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说VM)能够创建的最大线程大小。
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}
 
CacheThreadPool的运行流程如下:
- 提交任务进线程池。
 - 因为corePoolSize为0的关系,不创建核心线程,线程池最大为Integer.MAX_VALUE。
 - 尝试将任务添加到SynchronousQueue队列。
 - 如果SynchronousQueue入列成功,等待被当前运行的线程空闲后拉取执行。如果当前没有空闲线程,那么就创建一个非核心线程,然后从SynchronousQueue拉取任务并在当前线程执行。
 - 如果SynchronousQueue已有任务在等待,入列操作将会阻塞。
 
当需要执行很多短时间的任务时,CacheThreadPool的线程复用率比较高, 会显著的提高性能。而且线程60s后会回收,意味着即使没有任务进来,CacheThreadPool并不会占用很多资源。
package com.tianju.book.jpa.syn;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
 * 进行工厂的仿真
 */
public class FactorySimulationCached {
    public static void main(String[] args) {
        // 工人池,固定大小为5 
        ExecutorService workerPool = Executors.newCachedThreadPool();
        Thread thread1 = new Thread(() -> {
            for (int i = 1; i < 10; i++) {
                Worker worker = new Worker("TH1_worker" + i, i * 10);
                workerPool.execute(worker); // 默认调用run方法,提交任务给线程池执行
            }
        });
        Thread thread2 = new Thread(() -> {
            for (int i = 1; i < 10; i++) {
                Worker worker = new Worker("TH2_worker" + i, i * 10);
                workerPool.execute(worker); // 默认调用run方法,提交任务给线程池执行
            }
        });
        thread1.start();
        thread2.start();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        workerPool.shutdown();
    }
}
 

newScheduledThreadPool(定时任务)
创建一个定长线程池,支持定时及周期性任务执行。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}
//ScheduledThreadPoolExecutor():
public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE,
          DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
          new DelayedWorkQueue());
}
 

package com.tianju.book.jpa.syn;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
 * 进行工厂的仿真
 */
public class FactorySimulationScheduled {
    public static void main(String[] args) {
        // 工人池,固定大小为5  创建大小为20的定时线程池
        ScheduledExecutorService workerPool = Executors.newScheduledThreadPool(20);
        workerPool.schedule(new Worker("worker1,延迟1秒",10) ,1, TimeUnit.SECONDS);
        workerPool.scheduleAtFixedRate(new Worker("worker2,延迟2秒,后续每3秒执行1次",10), 2,3,TimeUnit.SECONDS);
        workerPool.scheduleAtFixedRate(new Worker("worker2,延迟2秒,后续每5秒执行1次",10), 3,5,TimeUnit.SECONDS);
        try {
            Thread.sleep(20000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        workerPool.shutdown();
    }
}
 
newSingleThreadScheduledExecutor
创建一个单线程的线程池。此线程池支持定时以及周期性执行任务的需求。

package com.tianju.book.jpa.syn;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
 * 进行工厂的仿真
 */
public class FactorySimulationSingleS {
    public static void main(String[] args) {
        // 工人池,固定大小为5
        ScheduledExecutorService workerPool = Executors.newSingleThreadScheduledExecutor();
        Thread thread1 = new Thread(() -> {
            for (int i =1;i<10;i++){
                workerPool.scheduleAtFixedRate(
                        new Worker("worker1-"+i, 3),
                        1, 3, TimeUnit.SECONDS);
            }
        });
        Thread thread2 = new Thread(() -> {
            for (int i=1;i<10;i++){
                workerPool.scheduleAtFixedRate(
                        new Worker("worker2-"+i, 3),
                        1, 3, TimeUnit.SECONDS);
            }
        });
        thread1.start();
        thread2.start();
        try {
            Thread.sleep(20000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        workerPool.shutdown();
    }
}
 
总结
1.创建多线程两种方法,继承thread,实现runnable,常用runnable;
 2.线程池,管理线程,控制并发;
 3.常用的线程池,newFixedThreadPool定容量;
 4.newCachedThreadPool容量大,Integer.MAX_VALUE;
 5.newScheduledThreadPool(定时任务),newSingleThreadScheduledExecutor单线程的线程池的定时任务;
 6.newSingleThreadExecutor(单线程),单线程的线程池;



















