Java多线程

创建线程的几种方式

1.实现 Runnable 接口

public class MyThread implements Runnable{ // 实现Runnable接口,作为线程的实现类
    private String name ;       // 表示线程的名称
    public MyThread(String name){
        this.name = name ;      // 通过构造方法配置name属性
    }
    public void run(){  // 覆写run()方法,作为线程 的操作主体
        for(int i=0;i<10;i++){
            System.out.println(name + "运行,i = " + i) ;
        }
    }
};
public class RunnableDemo01{
    public static void main(String args[]){
        MyThread mt1 = new MyThread("线程A ") ;    // 实例化对象
        MyThread mt2 = new MyThread("线程B ") ;    // 实例化对象
        Thread t1 = new Thread(mt1) ;       // 实例化Thread类对象
        Thread t2 = new Thread(mt2) ;       // 实例化Thread类对象
        t1.start() ;    // 启动多线程
        t2.start() ;    // 启动多线程
    }
};

2.继承 Thread 类

class MyThread extends Thread{  // 继承Thread类,作为线程的实现类
    private String name ;       // 表示线程的名称
    public MyThread(String name){
        this.name = name ;      // 通过构造方法配置name属性
    }
    public void run(){  // 覆写run()方法,作为线程 的操作主体
        for(int i=0;i<10;i++){
            System.out.println(name + "运行,i = " + i) ;
        }
    }
};
public class ThreadDemo02{
    public static void main(String args[]){
        MyThread mt1 = new MyThread("线程A ") ;    // 实例化对象
        MyThread mt2 = new MyThread("线程B ") ;    // 实例化对象
        mt1.start() ;   // 调用线程主体
        mt2.start() ;   // 调用线程主体
    }
};

3. 实现Callable接口

@Data
public class Callable01 implements Callable<String> {
    private String name;
    private Long sleep;
    @Override
    public String call() throws Exception {
        System.out.println(name + ":开始运行");
        Thread.sleep(sleep);
        return name + "运行完成";
    }
 }
  • Callable: 返回结果并且可能抛出异常的任务。
  • 可以获得任务执行返回值;
  • 通过与Future的结合,可以实现利用Future来跟踪异步计算的结果。

Runnable 和 Thread

核心源码如下【进行过代码裁剪】:继承于Thread并实现它的run方法能创建一个线程,实际这个run方法就是Runnable所提供的代理对象所有的,因此我们可以得到Runnable的全部优点

public class Thread implements Runnable {
    private Runnable target;
    private native void start(){
        @Override
        public void run() {
            if (target != null) {
                target.run();
            }
        }
    }
}
  • Runable的优势总结

1.【多线程内部数据共享】适合多个相同程序代码的线程去处理同一个资源(多线程内的数据共享)

2.【继承】避免java特性中的单根继承限制

3.【解耦】增加程序健壮性,数据被共享时,仍然可以保持代码和数据的分离和独立

4.【设计】更能体现java面向对象的设计特点

错误的线程并发案例【Thread实现】

每个线程拥有自己的变量,线程之间没有共享数据

public class Test03ThreadMain extends Thread {
    private int count = 5;

    @Override
    public void run() {
        for (int i = 0; i < 7; i++) {
            if (count > 0) {
                try {
                    TimeUnit.SECONDS.sleep(1);
                    System.out.println(Thread.currentThread().getName() + "----currentIndex:" + (count--));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        Test03ThreadMain h1 = new Test03ThreadMain();
        Test03ThreadMain h2 = new Test03ThreadMain();
        Test03ThreadMain h3 = new Test03ThreadMain();
        h1.start();
        h2.start();
        h3.start();
    }
}

正确的线程并发案例【Runable实现】

public class Test03ThreadRunable implements Runnable{
    private int count = 5;

    @Override
    public void run() {
        for (int i = 0; i < 7; i++) {
            if (count > 0) {
                try {
                    TimeUnit.SECONDS.sleep(1);
                    System.out.println(Thread.currentThread().getName() + "----currentIndex:" + (count--));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    // 一个执行任务,让多个线程进行执行
    public static void main(String[] args) {
        Test03ThreadRunable runable = new Test03ThreadRunable();
        new Thread(runable, "1号窗口").start();
        new Thread(runable, "2号窗口").start();
        new Thread(runable, "3号窗口").start();
    }
}

Runnable和Callable

  • 1、Callable规定的方法是call(),Runnable规定的方法是run().
  • 2、Callable的任务执行后可返回值,而Runnable的任务是不能返回值得
  • 3、call方法可以抛出异常,run方法不可以
  • 4、运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。

Future接口

  • Future是一个接口,代表了一个异步计算的结果。接口中的方法用来检查计算是否完成、等待完成和得到计算的结果。
  • 当计算完成后,只能通过get()方法得到结果,get方法会阻塞直到结果准备好了。
  • 如果想取消,那么调用cancel()方法。其他方法用于确定任务是正常完成还是取消了。
  • 一旦计算完成了,那么这个计算就不能被取消。

FutureTask类

  • FutureTask类实现了RunnableFuture接口,而RunnnableFuture接口继承了Runnable和Future接口,所以说FutureTask是一个提供异步计算的结果的任务。
  • FutureTask可以用来包装Callable或者Runnbale对象。因为FutureTask实现了Runnable接口,所以FutureTask也可以被提交给Executor(如上面例子那样)。

Callable两种执行方式

1、借助FutureTask执行

  • FutureTask类同时实现了两个接口,Future和Runnable接口,所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。

      //定义实现Callable接口的的实现类重写call方法。
      public class MyCallableTask implements Callable<Integer>{
          @Override
              public Integer call() throws Exception {
                 //TODO 线程执行方法
              }
      }
      ---------------------------------------------------------
      //创建Callable对象
      Callable<Integer> mycallabletask = new MyCallableTask();
      //开始线程
      FutureTask<Integer> futuretask= new FutureTask<Integer>(mycallabletask);
      new Thread(futuretask).start();
      --------------------------------------------------------
      通过futuretask可以得到MyCallableTaskcall()的运行结果:
      futuretask.get();
    

2、借助线程池来运行

 ```java
 ExecutorService exec = Executors.newCachedThreadPool();
 Future<Integer> future = exec.submit(new MyCallableTask());

 ```

举例说明

例1:

  public class CallableTest {
      public static void main(String[] args) throws ExecutionException, InterruptedException,TimeoutException{
          //创建一个线程池
          ExecutorService executor = Executors.newCachedThreadPool();
          Future<String> future = executor.submit(()-> {
                  TimeUnit.SECONDS.sleep(5);
                  return "CallableTest";
          });
          System.out.println(future.get());
          executor.shutdown();
      }
  }

例2:Callable任务借助FutureTask运行:

  public class CallableAndFutureTask {
      Random random = new Random();
      public static void main(String[] args) {
          Callable<Integer> callable = new Callable<Integer>() {
              public Integer call() throws Exception {
                  return random.nextInt(10000);
              }
          };
          FutureTask<Integer> future = new FutureTask<Integer>(callable);
          Thread thread = new Thread(future);
          thread.start();
          try {
              Thread.sleep(2000);
              System.out.println(future.get());
          } catch (Exception e) {
              e.printStackTrace();
          }
      }
  }

例3:Callable任务和线程池一起使用,然后返回值是Future

   public class CallableAndFuture {
       Random random = new Random();
       public static void main(String[] args) {
           ExecutorService threadPool = Executors.newSingleThreadExecutor();
           Future<Integer> future = threadPool.submit(new Callable<Integer>() {
               public Integer call() throws Exception {
                   return random.nextInt(10000);
               }
           });
           try {
               Thread.sleep(3000);
               System.out.println(future.get());
           } catch (Exception e) {
               e.printStackTrace();
           }
       }
   }

例4:当执行多个Callable任务,有多个返回值时,我们可以创建一个Future的集合

  class MyCallableTask implements Callable<String> {
      private int id;
      public OneTask(int id){
          this.id = id;
      }
      @Override
      public String call() throws Exception {
          for(int i = 0;i<5;i++){
              System.out.println("Thread"+ id);
              Thread.sleep(1000);
          }
          return "Result of callable: "+id;
      }
  }
  public class Test {

      public static void main(String[] args) {
          ExecutorService exec = Executors.newCachedThreadPool();
          ArrayList<Future<String>> results = new ArrayList<Future<String>>();

          for (int i = 0; i < 5; i++) {
              results.add(exec.submit(new MyCallableTask(i)));
          }

          for (Future<String> fs : results) {
              if (fs.isDone()) {
                  try {
                      System.out.println(fs.get());
                  } catch (Exception e) {
                      e.printStackTrace();
                  }
              } else {
                  System.out.println("MyCallableTask任务未完成!");
              }
          }
          exec.shutdown();
      }
  }

CompletableFuture

CompletableFuture 是 JDK1.8 版本新引入的类,提供了非常强大的 Future 的扩展功能,它针对Future做了改进,可以传入回调对象,当异步任务完成或者发生异常时,自动调用回调对象的回调方法。

构造方法

构造一个CompletableFuture

  • CompletableFuture.supplyAsync(Supplier supplier):提供返回值;异步执行一个任务,默认用 ForkJoinPool.commonPool();
  • CompletableFuture.supplyAsync(Supplier supplier,Executor executor):提供返回值;异步执行一个任务,但是可以自定义线程池;
  • CompletableFuture.runAsync(Runnable runnable,Executor executor) :没有返回值;异步执行一个任务,但是可以自定义线程池;
  • CompletableFuture.runAsync(Runnable runnable):没有返回值;异步执行一个任务, 默认用 ForkJoinPool.commonPool();
package com.example.thread.callable;

import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;
import lombok.Data;
import lombok.SneakyThrows;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import java.util.function.Supplier;

/**
 * @Package: com.example.thread.callable
 * @ClassName: Supplier01
 * @Author: xu kang kai
 * @Description:
 * @Date: 2022/12/7 14:34
 * @Version: 1.0
 */
@Data
public class Supplier01 implements Supplier<String> {

    private String name;
    private Long sleep;

    @SneakyThrows
    @Override
    public String get() {
        System.out.println(name + ":开始运行");
        Thread.sleep(sleep);
        return name + "运行完成";
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        List<CompletableFuture<String>> results = new ArrayList<>();
        CountDownLatch countDownLatch = new CountDownLatch(100);
        for (int i = 0; i < 100; i++) {
            Supplier01 call1 = new Supplier01();
            call1.setName("线程" + i);
            call1.setSleep(2000L);
            CompletableFuture<String> objectCompletableFuture = CompletableFuture.supplyAsync(call1).whenCompleteAsync((x, y) -> {
                System.out.println(x);
                countDownLatch.countDown();
            });
            results.add(objectCompletableFuture);
        }
// 为了获取所有线程执行完的结果 用CountDownLatch 和下面的 CompletableFuture.allOf.都是可以的join()
//        CompletableFuture<Void> allCF = CompletableFuture.allOf(results.toArray(new CompletableFuture[0]));
//        阻塞,直到所有任务结束。任务complete就会执行, handler里面不一定会执行..
//        allCF.join();
        for (CompletableFuture<String> result : results) {
            // 如果执行成功
            result.thenAccept((re) -> {
                System.out.println("price: " + re);
            });
            // 如果执行异常:
            result.exceptionally((e) -> {
                e.printStackTrace();
                return null;
            });
            // 直接获取结果
            // System.out.println(result.get());
        }
        countDownLatch.await();
    }
}

线程的几种状态

在Java程序中,一个线程对象只能调用一次start()方法启动新线程,并在新线程中执行run()方法。一旦run()方法执行完毕,线程就结束了。
因此,Java线程的状态有以下几种:

  • New:新创建的线程,尚未执行;

  • Runnable:运行中的线程,正在执行run()方法的Java代码;

  • Blocked:运行中的线程,因为某些操作被阻塞而挂起;

  • Waiting:运行中的线程,因为某些操作在等待中;

  • Timed Waiting:运行中的线程,因为执行sleep()方法正在计时等待;

  • Terminated:线程已终止,因为run()方法执行完毕。

当线程启动后,它可以在Runnable、Blocked、Waiting和Timed Waiting这几个状态之间切换,直到最后变成Terminated状态,线程终止。

线程终止的原因有:

线程正常终止:run()方法执行到return语句返回;

线程意外终止:run()方法因为未捕获的异常导致线程终止;

对某个线程的Thread实例调用stop()方法强制终止(强烈不推荐使用)。

一个线程还可以等待另一个线程直到其运行结束。例如,main线程在启动t线程后,可以通过t.join()等待t线程结束后再继续运行:

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            System.out.println("hello");
        });
        System.out.println("start");
        t.start();
        t.join();
        System.out.println("end");
    }
}

当main线程对线程对象t调用join()方法时,主线程将等待变量t表示的线程运行结束,即join就是指等待该线程结束,然后才继续往下执行自身线程。所以,上述代码打印顺序可以肯定是main线程先打印start,t线程再打印hello,main线程最后再打印end。
如果t线程已经结束,对实例t调用join()会立刻返回。此外,join(long)的重载方法也可以指定一个等待时间,超过等待时间后就不再继续等待。

中断线程

中断一个线程非常简单,只需要在其他线程中对目标线程调用interrupt()方法,
目标线程需要反复检测自身状态是否是interrupted状态,如果是,就立刻结束运行。

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new MyThread();
        t.start();
        Thread.sleep(1); // 暂停1毫秒
        t.interrupt(); // 中断t线程
        t.join(); // 等待t线程结束
        System.out.println("end");
    }
}

class MyThread extends Thread {
    public void run() {
        int n = 0;
        while (!isInterrupted()) {
            n ++;
            System.out.println(n + " hello!");
        }
    }
}

仔细看上述代码,main线程通过调用t.interrupt()方法中断t线程,但是要注意,interrupt()方法仅仅向t线程发出了“中断请求”,至于t线程是否能立刻响应,要看具体代码。而t线程的while循环会检测isInterrupted(),所以上述代码能正确响应interrupt()请求,使得自身立刻结束运行run()方法。

线程池

几种常见线程池

  • newCachedThreadPool

数量无上限,该线程池会根据需要创建,但是优先使用之前构造的线程。
这些池通常将提高执行许多短期异步任务的程序的性能,如果没有可用的现有线程,则将创建一个新线程并将其添加到池中。
当60S内未使用的线程将被终止并从缓存中删除。 因此,保持空闲时间足够长的池不会消耗任何资源。

构造函数

public static ExecutorService newCachedThreadPool() {
   return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                   60L, TimeUnit.SECONDS,
                                   new SynchronousQueue<Runnable>());
}
  • newFixedThreadPool
    数量固定大小,该线程池重用在共享的无边界队列上运行的固定数量的线程。
    在任何时候,最多nThreads(构造函数的参数,核心线程数与最大线程数相等)个线程都是活动的处理任务。
    如果在所有线程都处于活动状态时提交了其他任务,则它们将在队列中等待,直到某个线程可用为止。
    如果在关闭之前执行过程中由于执行失败导致任何线程终止,则在执行后续任务时将使用新线程代替。 池中的线程将一直存在,直到明确将其关闭

构造函数

public static ExecutorService newFixedThreadPool(int nThreads) {
   return new ThreadPoolExecutor(nThreads, nThreads,
                                   0L, TimeUnit.MILLISECONDS,
                                   new LinkedBlockingQueue<Runnable>());
}

ThreadPoolExecutor的重要参数

ThreadPoolExecutor主要有6个参数,构造方法如下:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
名称含义
corePoolSize核心线程数 核心线程会一直存活,当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭
maximumPoolSize当线程数>=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务 当线程数=maxPoolSize,且任务队列已满时,线程池会拒绝处理任务而抛出异常
keepAliveTime当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize如果allowCoreThreadTimeout=true,则会直到线程数量=0
unit时间单位
workQueue任务队列
threadFactory线程工厂,用来创建新线程
handler处理被拒绝的任务

RejectedExecutionHandler 参数 四种拒绝策略

RejectedExecutionHandler是一个接口:

public interface RejectedExecutionHandler {
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

里面只有一个方法。当要创建的线程数量大于线程池的最大线程数的时候,新的任务就会被拒绝,就会调用这个接口里的这个方法。

可以自己实现这个接口,实现对这些超出数量的任务的处理。

ThreadPoolExecutor自己已经提供了四个拒绝策略,分别是CallerRunsPolicy,AbortPolicy,DiscardPolicy,DiscardOldestPolicy

这四个拒绝策略其实一看实现方法就知道很简单。

  • 策略1:ThreadPoolExecutor.AbortPolicy()

    中止策略,将抛出 RejectedExecutionException。(默认情况)
    在任务不能再提交的时候,抛出异常,及时反馈程序运行状态。
    使用场景:当系统不能承载更大的并发量的时候,能够及时的通过异常发现。

    下面是他的实现:

public static class AbortPolicy implements RejectedExecutionHandler {
   public AbortPolicy() { }
   public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
       throw new RejectedExecutionException("Task " + r.toString() +
                                            " rejected from " +
                                            e.toString());
   }
}
  • 策略2:ThreadPoolExecutor.CallerRunsPolicy()

CallerRunsPolicy在任务被拒绝添加后,会调用当前线程池的所在的线程去执行被拒绝的任务。

下面是他的实现:

public static class CallerRunsPolicy implements RejectedExecutionHandler {
   public CallerRunsPolicy() { }
   public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
       if (!e.isShutdown()) {
           r.run();
       }
   }
}
  • 策略3:ThreadPoolExecutor.DiscardPolicy()

这个东西什么都没干。
因此采用这个拒绝策略,会让被线程池拒绝的任务直接抛弃,不会抛异常也不会执行。

public static class DiscardPolicy implements RejectedExecutionHandler {
   public DiscardPolicy() { }
   public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
   }
}
  • 策略4:ThreadPoolExecutor.DiscardOldestPolicy()

DiscardOldestPolicy策略的作用是,当任务呗拒绝添加时,会抛弃任务队列中最旧的任务也就是最先加入队列的,再把这个新任务添加进去。

public static class DiscardOldestPolicy implements RejectedExecutionHandler {
   public DiscardOldestPolicy() { }
   public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
       if (!e.isShutdown()) {
           e.getQueue().poll();
           e.execute(r);
       }
   }
}

在rejectedExecution先从任务队列总弹出最先加入的任务,空出一个位置,然后再次执行execute方法把任务加入队列。

测试四种策略

先自定义一个Runnable,给每个线程起个名字,下面都用这个Runnable

public class MyThread implements Runnable {
        String name;
        public MyThread(String name) {
            this.name = name;
        }
        @Override
        public void run() {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程:"+Thread.currentThread().getName() +" 执行:"+name +"  run");
        }
    }

然后构造一个核心线程是1,最大线程数是2的线程池

ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 2, 0, 
        TimeUnit.MICROSECONDS, 
        new LinkedBlockingDeque<Runnable>(2), 
        new ThreadPoolExecutor.AbortPolicy());
for (int i = 0; i < 6; i++) {
    System.out.println("添加第"+i+"个任务");
    executor.execute(new MyThread("线程"+i));
}

自定义拒绝策略

static class MyRejectedExecutionHandler implements RejectedExecutionHandler {
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        new Thread(r,"新线程"+new Random().nextInt(10)).start();
    }
}

二、ThreadPoolExecutor执行顺序

线程池按以下行为执行任务

  1. 当线程数小于核心线程数时,创建线程。
  2. 当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
  3. 当线程数大于等于核心线程数,且任务队列已满
    若线程数小于最大线程数,创建线程
    若线程数等于最大线程数,抛出异常,拒绝任务

如何设置参数

默认值
corePoolSize=1
queueCapacity=Integer.MAX_VALUE
maxPoolSize=Integer.MAX_VALUE
keepAliveTime=60s
allowCoreThreadTimeout=false
rejectedExecutionHandler=AbortPolicy()

需要根据几个值来决定

  • tasks :每秒的任务数,假设为500~1000
  • taskcost:每个任务花费时间,假设为0.1s
  • responsetime:系统允许容忍的最大响应时间,假设为1s

做几个计算

corePoolSize = 每秒需要多少个线程处理? threadcount = tasks/(1/taskcost) =tasks*taskcout = (500~1000)*0.1 = 50~100 个线程。corePoolSize设置应该大于50 根据8020原则,如果80%的每秒任务数小于800,那么corePoolSize设置为80即可 queueCapacity =
(coreSizePool/taskcost)responsetime 计算可得 queueCapacity = 80/0.11 = 80。意思是队列里的线程可以等待1s,超过了的需要新开线程来执行
切记不能设置为Integer.MAX_VALUE,这样队列会很大,线程数只会保持在corePoolSize大小,当任务陡增时,不能新开线程来执行,响应时间会随之陡增。
maxPoolSize = (max(tasks)- queueCapacity)/(1/taskcost) 计算可得
maxPoolSize = (1000-80)/10 = 92 最大任务数-队列容量)/每个线程每秒处理能力 = 最大线程数
rejectedExecutionHandler:根据具体情况来决定,任务不重要可丢弃,任务重要则要利用一些缓冲机制来处理
keepAliveTime和allowCoreThreadTimeout采用默认通常能满足
以上都是理想值,实际情况下要根据机器性能来决定。如果在未达到最大线程数的情况机器cpu
load已经满了,则需要通过升级硬件(呵呵)和优化代码,降低taskcost来处理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值