学习地址

1、进程与线程

1.1、进程

  • 当一个程序被运行,就开启了一个进程, 比如启动了qq,word
  • 程序由指令和数据组成,指令要运行,数据要加载,指令被cpu加载运行,数据被加载到内存,指令运行时可由cpu调度硬盘、网络等设备

1.2、线程

  • 一个进程内可分为多个线程
  • 一个线程就是一个指令流,cpu调度的最小单位,由cpu一条一条执行指令

2、并行与并发

并发:单核cpu运行多线程时,时间片进行很快的切换。线程轮流执行cpu
并行:多核cpu运行 多线程时,真正的在同一时刻运行
image.png

3、优点

  1. 程序运行的更快!快!快!
  2. 充分利用cpu资源,目前几乎没有线上的cpu是单核的,发挥多核cpu强大的能力

image.png

4、四种实现方式

  • 继承Thread类,重写run方法
  • 实现Runnable接口,重写run方法,实现Runnable接口的实现类的实例对象作为Thread构造函数的target
  • 通过Callable和FutureTask创建线程
  • 通过线程池创建线程

前面两种可以归结为一类:无返回值,原因很简单,通过重写run方法,run方式的返回值是void,所以没有办法返回结果
后面两种可以归结成一类:有返回值,通过Callable接口,就要实现call方法,这个方法的返回值是Object,所以返回的结果可以放在Object对象中

4.1、继承Thread类的线程实现

  1. public class ThreadDemo01 extends Thread{
  2. public ThreadDemo01(){
  3. //编写子类的构造方法,可缺省
  4. }
  5. public void run(){
  6. //编写自己的线程代码
  7. System.out.println(Thread.currentThread().getName());
  8. }
  9. public static void main(String[] args){
  10. ThreadDemo01 threadDemo01 = new ThreadDemo01();
  11. threadDemo01.setName("我是自定义的线程1");
  12. threadDemo01.start();
  13. System.out.println(Thread.currentThread().toString());
  14. }
  15. }

程序结果:
image.png

4.2、Runnable接口

通过实现Runnable接口,实现run方法,接口的实现类的实例作为Thread的target作为参数传入带参的Thread构造函数,通过调用start()方法启动线程

  1. public class ThreadDemo02 {
  2. public static void main(String[] args){
  3. System.out.println(Thread.currentThread().getName());
  4. Thread t1 = new Thread(new MyThread());
  5. t1.start();
  6. }
  7. }
  8. class MyThread implements Runnable{
  9. @Override
  10. public void run() {
  11. // TODO Auto-generated method stub
  12. System.out.println(Thread.currentThread().getName()+"-->我是通过实现接口的线程实现方式!");
  13. }
  14. }

程序运行结果:
image.png

4.3、通过Callable和FutureTask创建线程

  • 创建Callable接口的实现类 ,并实现Call方法
  • 创建Callable实现类的实现,使用FutureTask类包装Callable对象,该FutureTask对象封装了Callable对象的Call方法的返回值
  • 使用FutureTask对象作为Thread对象的target创建并启动线程
  • 调用FutureTask对象的get()来获取子线程执行结束的返回值 ```java public class ThreadDemo03 {

    /**

    • @param args */ public static void main(String[] args) { // TODO Auto-generated method stub

      Callable oneCallable = new Tickets(); FutureTask oneTask = new FutureTask(oneCallable);

      Thread t = new Thread(oneTask);

      System.out.println(Thread.currentThread().getName());

      t.start();

      }

      }

      class Tickets implements Callable{

      1. //重写call方法
      2. @Override
      3. public Object call() throws Exception {
      4. // TODO Auto-generated method stub
      5. System.out.println(Thread.currentThread().getName()+"-->我是通过实现Callable接口通过FutureTask包装器来实现的线程");
      6. return null;
      7. }

      }

      1. 程序运行结果:<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/22731784/1638241891688-14be5609-cb7a-4506-9eb5-662f4aaff809.png#clientId=u0d8ccafd-e245-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=47&id=u514f9571&margin=%5Bobject%20Object%5D&name=image.png&originHeight=80&originWidth=673&originalType=binary&ratio=1&rotation=0&showTitle=false&size=8949&status=done&style=none&taskId=u8626f8a7-b312-4907-a0d2-7b44bddfaec&title=&width=396.5)
      2. <a name="oEt55"></a>
      3. ### 4.4、线程池
      4. ```java
      5. public class ThreadDemo05{
      6. private static int POOL_NUM = 10; //线程池数量
      7. /**
      8. * @param args
      9. * @throws InterruptedException
      10. */
      11. public static void main(String[] args) throws InterruptedException {
      12. ExecutorService executorService = Executors.newFixedThreadPool(5);
      13. for(int i = 0; i<POOL_NUM; i++)
      14. {
      15. RunnableThread thread = new RunnableThread();
      16. //Thread.sleep(1000);
      17. executorService.execute(thread);
      18. }
      19. //关闭线程池
      20. executorService.shutdown();
      21. }
      22. }
      23. class RunnableThread implements Runnable
      24. {
      25. @Override
      26. public void run()
      27. {
      28. System.out.println("通过线程池方式创建的线程:" + Thread.currentThread().getName() + " ");
      29. }
      30. }

      程序运行结果:
      image.png
      ExecutorService、Callable都是属于Executor框架。返回结果的线程是在JDK1.5中引入的新特征,还有Future接口也是属于这个框架,有了这种特征得到返回值就很方便了。
      通过分析可以知道,他同样也是实现了Callable接口,实现了Call方法,所以有返回值。这也就是正好符合了前面所说的两种分类

      执行Callable任务后,可以获取一个Future的对象,在该对象上调用get就可以获取到Callable任务返回的Object了。get方法是阻塞的,即:线程无返回结果,get方法会一直等待。

      再介绍Executors类:提供了一系列工厂方法用于创建线程池,返回的线程池都实现了ExecutorService接口。

      public static ExecutorService newFixedThreadPool(int nThreads)
      创建固定数目线程的线程池。
      public static ExecutorService newCachedThreadPool()
      创建一个可缓存的线程池,调用execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。
      public static ExecutorService newSingleThreadExecutor()
      创建一个单线程化的Executor。
      public static ScheduledExecutorService newScheduledThreadPool(int
      corePoolSize)
      创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。
      ExecutoreService提供了submit()方法,传递一个Callable,或Runnable,返回Future。如果Executor后台线程池还没有完成Callable的计算,这调用返回Future对象的get()方法,会阻塞直到计算完成。
      [

      ](https://blog.csdn.net/u011480603/article/details/75332435)

      5、线程的生命周期

      1、新建状态(New):新创建了一个线程对象。
      2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
      3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
      4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
      (一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
      (二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
      (三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
      5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

      image.png

      6、方法

      • yield() 方法会让运行中的线程切换到就绪状态,重新争抢cpu的时间片,争抢时是否获取到时间片看cpu的分配。
      • sleep() 使线程休眠,会将运行中的线程进入阻塞状态。当休眠时间结束后,重新争抢cpu的时间片继续运行
      • join() 是指调用该方法的线程进入阻塞状态,等待某线程执行完成后恢复运行
      • wait() 将线程进入阻塞状态,
      • notify() 将线程唤醒

      7、线程池

      7.1、定义

      • 线程的资源很宝贵,不可能无限的创建,必须要有管理线程的工具,线程池就是一种管理线程的工具,java开发中经常有池化的思想,
      • 如 数据库连接池、Redis连接池等。
      • 预先创建好一些线程,任务提交时直接执行,既可以节约创建线程的时间,又可以控制线程的数量。

      线程池的好处

      1. 降低资源消耗,通过池化思想,减少创建线程和销毁线程的消耗,控制资源
      2. 提高响应速度,任务到达时,无需创建线程即可运行
      3. 提供更多更强大的功能,可扩展性高

      image.png

      7.2、构造方法

      1. public ThreadPoolExecutor(int corePoolSize,
      2. int maximumPoolSize,
      3. long keepAliveTime,
      4. TimeUnit unit,
      5. BlockingQueue<Runnable> workQueue,
      6. ThreadFactory threadFactory,
      7. RejectedExecutionHandler handler) {
      8. }

      构造器参数的意义

      参数名 参数意义
      corePoolSize 核心线程数
      maximumPoolSize 最大线程数
      keepAliveTime 救急线程的空闲时间
      unit 救急线程的空闲时间单位
      workQueue 阻塞队列
      threadFactory 创建线程的工厂,主要定义线程名
      handler 拒绝策略

      7.3、线程池案例

      如上图 银行办理业务。

      1. 客户到银行时,开启柜台进行办理,柜台相当于线程,客户相当于任务,有两个是常开的柜台,三个是临时柜台。2就是核心线程数,5是最大线程数。即有两个核心线程
      2. 当柜台开到第二个后,都还在处理业务。客户再来就到排队大厅排队。排队大厅只有三个座位。
      3. 排队大厅坐满时,再来客户就继续开柜台处理,目前最大有三个临时柜台,也就是三个救急线程
      4. 此时再来客户,就无法正常为其 提供业务,采用拒绝策略来处理它们
      5. 当柜台处理完业务,就会从排队大厅取任务,当柜台隔一段空闲时间都取不到任务时,如果当前线程数大于核心线程数时,就会回收线程。即撤销该柜台。

      image.png

      7.4、线程池的状态

      线程池通过一个int变量的高3位来表示线程池的状态,低29位来存储线程池的数量

      状态名称 高三位 接收新任务 处理阻塞队列任务 说明
      Running 111 Y Y 正常接收任务,正常处理任务
      Shutdown 000 N Y 不会接收任务,会执行完正在执行的任务,也会处理阻塞队列里的任务
      stop 001 N N 不会接收任务,会中断正在执行的任务,会放弃处理阻塞队列里的任务
      Tidying 010 N N 任务全部执行完毕,当前活动线程是0,即将进入终结
      Termitted 011 N N 终结状态
      1. // runState is stored in the high-order bits
      2. private static final int RUNNING = -1 << COUNT_BITS;
      3. private static final int SHUTDOWN = 0 << COUNT_BITS;
      4. private static final int STOP = 1 << COUNT_BITS;
      5. private static final int TIDYING = 2 << COUNT_BITS;
      6. private static final int TERMINATED = 3 << COUNT_BITS;

      7.5、主要流程

      线程池创建、接收任务、执行任务、回收线程的步骤

      1. 创建线程池后,线程池的状态是Running,该状态下才能有下面的步骤
      2. 提交任务时,线程池会创建线程去处理任务
      3. 当线程池的工作线程数达到corePoolSize时,继续提交任务会进入阻塞队列
      4. 当阻塞队列装满时,继续提交任务,会创建救急线程来处理
      5. 当线程池中的工作线程数达到maximumPoolSize时,会执行拒绝策略
      6. 当线程取任务的时间达到keepAliveTime还没有取到任务,工作线程数大于corePoolSize时,会回收该线程

      注意: 不是刚创建的线程是核心线程,后面创建的线程是非核心线程,线程是没有核心非核心的概念的,这是我长期以来的误解。
      拒绝策略

      1. 调用者抛出RejectedExecutionException (默认策略)
      2. 让调用者运行任务
      3. 丢弃此次任务
      4. 丢弃阻塞队列中最早的任务,加入该任务

      提交任务的方法

      1. // 执行Runnable
      2. public void execute(Runnable command) {
      3. if (command == null)
      4. throw new NullPointerException();
      5. int c = ctl.get();
      6. if (workerCountOf(c) < corePoolSize) {
      7. if (addWorker(command, true))
      8. return;
      9. c = ctl.get();
      10. }
      11. if (isRunning(c) && workQueue.offer(command)) {
      12. int recheck = ctl.get();
      13. if (! isRunning(recheck) && remove(command))
      14. reject(command);
      15. else if (workerCountOf(recheck) == 0)
      16. addWorker(null, false);
      17. }
      18. else if (!addWorker(command, false))
      19. reject(command);
      20. }
      21. // 提交Callable
      22. public <T> Future<T> submit(Callable<T> task) {
      23. if (task == null) throw new NullPointerException();
      24. // 内部构建FutureTask
      25. RunnableFuture<T> ftask = newTaskFor(task);
      26. execute(ftask);
      27. return ftask;
      28. }
      29. // 提交Runnable,指定返回值
      30. public Future<?> submit(Runnable task) {
      31. if (task == null) throw new NullPointerException();
      32. // 内部构建FutureTask
      33. RunnableFuture<Void> ftask = newTaskFor(task, null);
      34. execute(ftask);
      35. return ftask;
      36. }
      37. // 提交Runnable,指定返回值
      38. public <T> Future<T> submit(Runnable task, T result) {
      39. if (task == null) throw new NullPointerException();
      40. // 内部构建FutureTask
      41. RunnableFuture<T> ftask = newTaskFor(task, result);
      42. execute(ftask);
      43. return ftask;
      44. }
      45. protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
      46. return new FutureTask<T>(runnable, value);
      47. }

      7.6、Execetors创建线程池

      1.newFixedThreadPool

      • 核心线程数 = 最大线程数 没有救急线程
      • 阻塞队列无界 可能导致oom ```java public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads,
        1. 0L, TimeUnit.MILLISECONDS,
        2. new LinkedBlockingQueue<Runnable>());
        }
      1. <a name="vq74g"></a>
      2. #### 2.newCachedThreadPool
      3. - 核心线程数是0,最大线程数无限制 ,救急线程60秒回收
      4. - 队列采用 SynchronousQueue 实现 没有容量,即放入队列后没有线程来取就放不进去
      5. - 可能导致线程数过多,cpu负担太大
      6. ```java
      7. public static ExecutorService newCachedThreadPool() {
      8. return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
      9. 60L, TimeUnit.SECONDS,
      10. new SynchronousQueue<Runnable>());
      11. }

      3.newSingleThreadExecutor

      • 核心线程数和最大线程数都是1,没有救急线程,无界队列 可以不停的接收任务
      • 将任务串行化 一个个执行, 使用包装类是为了屏蔽修改线程池的一些参数 比如 corePoolSize
      • 如果某线程抛出异常了,会重新创建一个线程继续执行
      • 可能造成oom ```java public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService
        1. (new ThreadPoolExecutor(1, 1,
        2. 0L, TimeUnit.MILLISECONDS,
        3. new LinkedBlockingQueue<Runnable>()));
        }
      1. <a name="xosVs"></a>
      2. #### 4.newScheduledThreadPool
      3. - 任务调度的线程池 可以指定延迟时间调用,可以指定隔一段时间调用
      4. ```java
      5. public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
      6. return new ScheduledThreadPoolExecutor(corePoolSize);
      7. }

      7.7、线程池的关闭

      shutdown()

      会让线程池状态为shutdown,不能接收任务,但是会将工作线程和阻塞队列里的任务执行完 相当于优雅关闭

      1. public void shutdown() {
      2. final ReentrantLock mainLock = this.mainLock;
      3. mainLock.lock();
      4. try {
      5. checkShutdownAccess();
      6. advanceRunState(SHUTDOWN);
      7. interruptIdleWorkers();
      8. onShutdown(); // hook for ScheduledThreadPoolExecutor
      9. } finally {
      10. mainLock.unlock();
      11. }
      12. tryTerminate();
      13. }

      shutdownNow()

      会让线程池状态为stop, 不能接收任务,会立即中断执行中的工作线程,并且不会执行阻塞队列里的任务, 会返回阻塞队列的任务列表

      1. public List<Runnable> shutdownNow() {
      2. List<Runnable> tasks;
      3. final ReentrantLock mainLock = this.mainLock;
      4. mainLock.lock();
      5. try {
      6. checkShutdownAccess();
      7. advanceRunState(STOP);
      8. interruptWorkers();
      9. tasks = drainQueue();
      10. } finally {
      11. mainLock.unlock();
      12. }
      13. tryTerminate();
      14. return tasks;
      15. }

      8、ThreadLocal关键字

      8.1、作用

      主要是做数据隔离,填充的数据只属于当前线程,变量的数据对别的线程而言是相对隔离的,在多线程环境下,防止自己的变量被其它线程篡改。

      8.2、使用

      首先,它是一个数据结构,有点像HashMap,可以保存”key : value”键值对,但是一个ThreadLocal只能保存一个,并且各个线程的数据互不干扰。

      1. ThreadLocal<String> localName = new ThreadLocal();
      2. localName.set("占小狼");
      3. String name = localName.get();

      在线程1中初始化了一个ThreadLocal对象localName,并通过set方法,保存了一个值占小狼,同时在线程1中通过localName.get()可以拿到之前设置的值,但是如果在线程2中,拿到的将是一个null。
      这是为什么,如何实现?不过之前也说了,ThreadLocal保证了各个线程的数据互不干扰。
      看看set(T value)和get()方法的源码

      1. public void set(T value) {
      2. Thread t = Thread.currentThread();
      3. ThreadLocalMap map = getMap(t);
      4. if (map != null)
      5. map.set(this, value);
      6. else
      7. createMap(t, value);
      8. }
      9. public T get() {
      10. Thread t = Thread.currentThread();
      11. ThreadLocalMap map = getMap(t);
      12. if (map != null) {
      13. ThreadLocalMap.Entry e = map.getEntry(this);
      14. if (e != null) {
      15. @SuppressWarnings("unchecked")
      16. T result = (T)e.value;
      17. return result;
      18. }
      19. }
      20. return setInitialValue();
      21. }
      22. ThreadLocalMap getMap(Thread t) {
      23. return t.threadLocals;
      24. }

      可以发现,每个线程中都有一个ThreadLocalMap数据结构,当执行set方法时,其值是保存在当前线程的threadLocals变量中,当执行set方法中,是从当前线程的threadLocals变量获取。
      所以在线程1中set的值,对线程2来说是摸不到的,而且在线程2中重新set的话,也不会影响到线程1中的值,保证了线程之间不会相互干扰。


      8.3、举例

      ThreadLocal通过Thread.threadlocals保存ThreadLocal的副本,但是ThreadLocal变量在多线程情况下仍然是不安全的。

      1. class MyClass{
      2. private Integer value;
      3. public MyClass(){
      4. }
      5. public MyClass(Integer value){
      6. this.value=value;
      7. }
      8. public Integer getValue() {
      9. return value;
      10. }
      11. public void setValue(Integer value) {
      12. this.value = value;
      13. }
      14. @Override
      15. public String toString() {
      16. return "MyClass{" +
      17. "value=" + value +
      18. '}';
      19. }
      20. }
      21. public class ThreadLocalTest {
      22. static ThreadLocal<MyClass> threadLocal=new ThreadLocal<>();
      23. public static void main(String[] args) {
      24. MyClass myClass=new MyClass();
      25. new Thread(){
      26. @Override
      27. public void run() {
      28. myClass.setValue(10);
      29. threadLocal.set(myClass);
      30. System.out.println(currentThread().getName()+":"+threadLocal.get());
      31. try {
      32. Thread.sleep(1000);
      33. } catch (InterruptedException e) {
      34. e.printStackTrace();
      35. }
      36. System.out.println(currentThread().getName()+":"+threadLocal.get());
      37. myClass.setValue(10); //这个值会影响下面线程最后一次打印
      38. }
      39. }.start();
      40. new Thread(){
      41. @Override
      42. public void run() {
      43. myClass.setValue(20);
      44. threadLocal.set(myClass);
      45. System.out.println(currentThread().getName()+":"+threadLocal.get());
      46. try {
      47. Thread.sleep(2000);
      48. } catch (InterruptedException e) {
      49. e.printStackTrace();
      50. }
      51. System.out.println(currentThread().getName()+":"+threadLocal.get());
      52. }
      53. }.start();
      54. }
      55. }

      通过查看上面代码的执行结果,可以看到,两个线程对myClass的value设置后,相互影响,线程逻辑完全混乱了,并不是我们想象中的各自拥有变量副本。
      image.png
      正确的写法是多线程共享ThreadLocal变量,但是不共享ThreadLocal里面的变量。也就是每个线程共享Thread.threadLocals的key,而不不共享Thread.threadLocals的value。

      1. public class ThreadLocalTest111 {
      2. static ThreadLocal<MyClass> threadLocal=new ThreadLocal<>();
      3. public static void main(String[] args) {
      4. new Thread(){
      5. @Override
      6. public void run() {
      7. MyClass myClass=new MyClass(10);
      8. threadLocal.set(myClass);
      9. System.out.println(currentThread().getName()+":"+threadLocal.get());
      10. try {
      11. Thread.sleep(1000);
      12. } catch (InterruptedException e) {
      13. e.printStackTrace();
      14. }
      15. System.out.println(currentThread().getName()+":"+threadLocal.get());
      16. myClass.setValue(10); //这个值会影响下面线程最后一次打印
      17. }
      18. }.start();
      19. new Thread(){
      20. @Override
      21. public void run() {
      22. MyClass myClass=new MyClass(20);
      23. threadLocal.set(myClass);
      24. System.out.println(currentThread().getName()+":"+threadLocal.get());
      25. try {
      26. Thread.sleep(2000);
      27. } catch (InterruptedException e) {
      28. e.printStackTrace();
      29. }
      30. System.out.println(currentThread().getName()+":"+threadLocal.get());
      31. }
      32. }.start();
      33. }
      34. }

      上面这个代码就是线程安全了。
      image.png

      8.4、应用场景

      • Spring的事务主要是ThreadLocal和AOP去做实现的

      1.每个人都一张银行卡
      2.每个人每张卡都有一定的余额。
      3.每个人获取银行卡余额都必须通过该银行的管理系统。
      4.每个人都只能获取自己卡持有的余额信息,他人的不可访问。

      参考
      https://juejin.cn/post/6844903553740308488
      https://juejin.cn/post/6959333602748268575
      https://www.cnblogs.com/mkl34367803/p/14684485.html 案例