Java-多线程与线程池

1.进程与线程

1.1 概念:

  • 进程:内存中运行的应用程序,一个程序可执行多个线程,并为线程提供内存资源。

  • 线程:进程内部的一个独立执行代码的单元,运行程序的基本单位,一个进程可同时并发多个线程。

  • 并发:指两个及多个事件在同一时间段内发生。

  • 并行:指两个及多个事件在同一时刻发生。

1.2 线程运行模式

  • 分时式模式:所有线程轮流获得CPU使用权,均分每个线程占用时间

  • 占时式模式:优先级高的线程先获得CPU使用权的概率高,优先级相同的随机选择一个

1.3多线程运行原理

  • 每个线程都会有自己独立的栈空间,同一个线程中的代码仍由上到下的顺序执行。同一个进程中的线程共享进程的堆空间。

2. Thread类常用的方法

2.1 构造方法

  - public Thread() : 分配一个新的线程
  - public Thread(String name) : 分配一个指定名字的新线程对象
  - public Thread(Runnable target) : 分配一个指定目标的新线程对象
  - public Thread(Runnable target,String name) : 分配一个指定目标和指定名字的新线程对象

2.2常用方法

  - String getName() : 获得线程名称,默认名称:Thread-序号
  - void setName(String name) : 设置线程名称
  - void start() : 启动线程,在新的线程中执行runff
  - static Thread currentThread() : 获得执行当前方法的线程对象
  - static void sleep(long time) : 让线程休眠指定的毫秒值

3. 创建线程(重点)

3.1线程创建方法一:利用Thread子类创建

步骤:

  • 定义Thread子类,重写run(),即线程执行的代码;

  • 创建Thread子类对象(线程对象);

  • 调用线程对象的start方法启动新线程;

示例代码如下:

/*
  定义Thread子类,重写run()
  */
  public class MyThread extends Thread {   
      @Override
      public void run() {
          try {
              // 让线程休眠2秒,模拟后台运行和数据传输的延迟时间
              Thread.sleep(2000);
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
          for (int i = 0; i < 10; i++) {
              System.out.println("run = " + i);
          }
          // 获得子线程对象
          Thread rt = Thread.currentThread();
          // 获得子线程的名称
          System.out.println(rt.getName());
      }
  }
  public class Test01 {
      public static void main(String[] args) throws InterruptedException {
          // 创建Thread子类对象,即线程对象,并设置名字
          MyThread t = new MyThread("传输文件");
          // 获得线程名称并输出
          System.out.println(t.getName());
          // 根据线程对象开启新线程
          t.start();
          System.out.println("-----------------------");
          //测试双线程的执行情况
           for (int i = 0; i < 10; i++) {
              System.out.println("main = " + i);
          }
          // 获得主线程对象
          Thread mt = Thread.currentThread();
          // 获得主线程的名称
          System.out.println(mt.getName());
  }

方法1一般可用匿名内部类简化,则上述代码可以转为:

public class Test01 {
      public static void main(String[] args) throws InterruptedException {
          // 创建Thread子类对象,即线程对象
          MyThread t = new MyThread(new Thread(){
               @Override
          public void run() {
              try {
                  // 让线程休眠2秒,模拟后台运行和数据传输的延迟时间
                  Thread.sleep(2000);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              for (int i = 0; i < 10; i++) {
                  System.out.println("run = " + i);
              }
              // 获得子线程的名称
              System.out.println(Thread.currentThread().getName());
          }},"传输文件").start();
          
          System.out.println("-----------------------");
          //测试双线程的执行情况
           for (int i = 0; i < 10; i++) {
              System.out.println("main = " + i);
          }
          // 获得主线程的名称
          System.out.println(Thread.currentThread().getName());
      }
  }

至于用lambda表达式简化之后再说

3.2线程创建方法二:利用Runnable实现类创建

步骤

  • 定义Runnable接口的实现类,并重写该接口的run()方法,即线程执行的代码

  • 创建实现类对象,并以此对象欧威Thread的target来创建Thread对象,即线程对象

  • 调用线程对象的start()方法启动新线程

示例代码如下:

 public class Notes03 {
      public static void main(String[] args) {
          //创建Runnable实现类对象
          MyRunnable mr = new MyRunnable();
          //mr作为Thread对象的target
          Thread t = new Thread(mr,"子线程");
          //调用t的start方法启动新线程
          t.start();
      }
  }
  //创建Runnable实现类,并重写run方法
  class MyRunnable implements Runnable{
      @Override
      public void run() {
          System.out.println("这个是新建线程:" + Thread.currentThread().getName());
      }
  }

方法2同样可用匿名内部类简化,则上述代码可以转为:

public class Notes04 { 
      public static void main(String[] args) {
          //mr作为Thread对象的target
          new Thread(new Runnable() {
              @Override
              public void run() {
                  System.out.println("这个是新建线程:" + Thread.currentThread().getName());
              }
          }, "子线程").start();
      }
  }

3.3 创建线程的注意事项

  • 不管是用继承Thread类还是用实现Runnable接口,创建线程都必须要用Thread对象(线程对象)的start()方法才能启动;

  • 匿名内部类创建线程的使用场景:任务只需要执行一次。若需多次使用则不推荐使用。

3.4 两种创建方式的对比

1、继承Thread类不适合资源共享,而实现Runnable接口容易实现资源共享

2、实现Runnable的优势:

1)适合多个相同的程序代码的线程共用同一个资源;

2)避免java单继承的局限;

3)线程与任务分离,降低程序耦合性,代码和数据独立;

4)线程池只能放入实现Runnable和Callable的线程。

4. 线程池

4.1 概念

  • 容纳多个线程的容器,其中线程可反复使用,无需反复创建线程

4.2 优势

  • 降低资源消耗(减少线程建销次数);

  • 提高相应速度(不需要等待线程建立);

  • 提高线程的可管理性(根据系统承载能力调整工作线线程数目)。

4.3 线程池创建

  • 利用官方提供的Executors类的静态方法创建线程池对象,该类实现ExecutorService接口。

  public static ExecutorService newFixedThreadPool(int nThreads);
              - 根据指定的线程数量创建线程池对象。
  • 线程池常用方法

  ExecutorService接口常用方法:
      - Future<?> submit(Runnable task); - 提交Runnable任务
      - Future<T> submit(Callable<T> task); - 提交Callable任务
      - void shutdown(); - 完成所有已提交的任务,并销毁线程池
      - void shutdownNow(); - 忽略线程池中尚未开始执行的任务并立即销毁线程池(已开始的仍会等完成后再销毁)
      - V get(); - 获得Future的返回值(只有Callable才会有返回值,该方法Runnable不能使用)

4.4 线程池任务提交(重点)

4.4.1 提交Runnable任务

步骤

  - 1、创建线程池对象并指定线程数量:ExecutorService es = Executors.newFixedThreadPool(n);
  - 2、创建类实现Runnable接口,重写run方法;
  - 3、创建实现类对象(task),调用线程池对象的submit方法传递实现类对象;
  - 4、调用线程池对象的shutdown方法销毁线程池(实际开发一般比较少销毁)。

示例代码如下:

public class Notes01 {
      public static void main(String[] args) {
          //创建线程池对象
          ExecutorService service = Executors.newFixedThreadPool(3);
          //创建Runnable实现类对象
          MyRunnable mr = new MyRunnable();
          //提交Runnable任务
          service.submit(mr);
          service.submit(mr);
          service.submit(mr);
          //关闭线程池
          service.shutdown();
      }
  }
  ​
  class MyRunnable implements Runnable{
      @Override
      public void run() {
          System.out.println(Thread.currentThread().getName() + "执行线程任务");
      }
  }

当然可以用匿名内部类简化:

public class Notes01_Another {
      public static void main(String[] args) {
          //创建线程池对象
          ExecutorService service = Executors.newFixedThreadPool(3);
          //提交Runnable任务
          service.submit(new Runnable() {
              @Override
              public void run() {
                  System.out.println(Thread.currentThread().getName() + "执行线程任务");
              }
          });
          //关闭线程池
          service.shutdown();
      }
  }

4.4.2 提交Callable任务

步骤

  - 1、创建线程池对象并指定线程数量:ExecutorService es = Executors.newFixedThreadPool(n);
  - 2、创建类实现Callable接口,重写call方法;
  - 3、创建实现类对象(task),调用线程池对象的submit方法传递实现类对象;
  - 4、如果需要对返回值进行处理,用Future<T>接收submit的Callable任务,并调用其get()方法接收;
  - 5、调用线程池对象的shutdown方法销毁线程池(实际开发一般比较少销毁)。

示例代码如下:

 public class Notes02 {
      public static void main(String[] args) throws ExecutionException, InterruptedException {
          //创建线程池对象
          ExecutorService service = Executors.newFixedThreadPool(3);
          //创建Callable实现类对象
          MyCallable mc = new MyCallable();
          //提交Callable任务
          //接收返回值
          Future<String> f = service.submit(mc);  //用Future<T>接收返回值
          System.out.println(f.get());
          //关闭线程池
          service.shutdown();
      }
  }
  class MyCallable implements Callable{
  ​
      @Override
      public Object call() throws Exception {
          System.out.println(Thread.currentThread().getName() + "开启执行线程任务");
          for (int i = 0; i < 10; i++) {
              System.out.println("call = " + i);
          }
          return "已完成线程任务";
      }
  }

当然可以用匿名内部类简化:

public class Notes02_Another {
      public static void main(String[] args) throws ExecutionException, InterruptedException {
          //创建线程池对象
          ExecutorService service = Executors.newFixedThreadPool(3);
          //创建Callable实现类对象
          MyCallable mc = new MyCallable();
          //提交Callable任务
          //接收返回值
          Future<String> f = service.submit(new Callable<String>() {
              @Override
              public String call() throws Exception {
                  System.out.println(Thread.currentThread().getName() + "开启执行线程任务");
                  for (int i = 0; i < 10; i++) {
                      System.out.println("call = " + i);
                  }
                  return "已完成线程任务";
              }
          });  //用Future<T>接收返回值
          System.out.println(f.get());
          //关闭线程池
          service.shutdown();
      }
  }

4.4.3 Runnable和Callable的选择

  • 执行任务完毕后需要返回值给调用者,选择Callable

  • 否则两种均可以

5.多线程等待唤醒机制

5.1 线程状态

  • NEW 线程被新建但未启动

  • RUNNABLE 线程在JVM的状态,正在运行或还未运行

  • BLOCKED 线程未获得锁对象,获得后转RUNNABLE

  • WAITING 线程无限等待,必须有另一线程notify或notifyAll

  • TIMED-WAITING 线程计时等待,直到超时或唤醒

  • TEMINATED 线程终止

5.2等待唤醒机制

  • 解决线程之间对同一变量的使用或操作(多线程共同处理同一个资源,但任务不同)

  • 线程等待与唤醒又称为线程间通信。

5.2.1常用方法

  - void wait(); 等待,让线程释放CPU的使用权以及锁对象进入无限等待状态
  - void notify(); 唤醒,随机唤醒一个正在等待状态的线程,让其进入可运行状态。
  - void notifyAll();唤醒所有,唤醒所有正在等待状态的线程,让其进入可运行状态。

注意事项:

  • 以上三个方法必须由锁对象调用且必须在同步代码块或同步方法中调用(重点)

  • wait()、notify()都属于Object类的方法

  • 如能获得锁,线程从WAITING进入RUNNABLE,否则从wait set出来后又会进入entry set(BLOCKED状态),与其他线程争锁。

sleep()和wait()的区别

  • sleep()需要指定时间,wait()可以指定也可不指定;

  • sleep()在任意类任意方法都可调用,wait()只能在同步代码块或同步方法中用锁对象调用;

  • sleep()不需要被唤醒,而wait()无指定时间就必须被唤醒

5.2.2实例代码

以包子店和顾客为例

 public class Notes03 {
      public static void main(String[] args) {
          //创建共享资源
          Resource baozi = new Resource();
          //创建线程并调用run
          new Thread(new Produce(baozi),"包子铺").start();
          new Thread(new Consumer(baozi),"顾客").start();
      }
  }
  /*
  共享资源类
   */
  ​
  class Resource {
      public String name;
      public String content;
  }
  ​
  /*
  生产线程
   */
  ​
  class Produce implements Runnable{
      //设置共享资源
      public Resource baozi;
      //构造函数
      public Produce(Resource baozi) {
          this.baozi = baozi;
      }
  ​
      //设置计数器
      int count = 0;
  ​
      //重写run(),生产包子
      @Override
      public void run() {
          while (count <= 20) {
              synchronized (baozi) {  //增加锁,保证不会两个线程同时执行
                  if (count % 2 == 0) {
                      baozi.name = "叉烧包";
                      baozi.content = "肉";
                  } else {
                      baozi.name = "菜包";
                      baozi.content = "白菜";
                  }
                  System.out.println("包子铺做了" + baozi.content + "馅儿的" + baozi.name + ",开张了~");
                  /*
                  必须保证wait和notify是在同步代码块内
                   */
                  try {
                      baozi.notify();//唤醒消费线程
                      baozi.wait();   //生产线程进入休眠
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              }
              count++;
          }
  ​
      }
  }
  /*
  消费线程
   */
  ​
  class Consumer implements Runnable{
      //设置共享资源
      public Resource baozi;
      //构造函数
      public Consumer(Resource baozi) {
          this.baozi = baozi;
      }
      //重写run(),消费包子
      @Override
      public void run() {
          while (true) {
              synchronized (baozi) {  //增加锁,保证不会两个线程同时执行
                  if (baozi.name == null) {   //避免第一次消费线程先运行
                      try {
                          baozi.wait();
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                  }
                  System.out.println("顾客吃了" + baozi.content + "做的" + baozi.name);
                  try {
                      baozi.notify();//唤醒生产线程
                      baozi.wait();   //生产消费进入休眠
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              }
          }
  ​
      }
  }

猜你喜欢

转载自blog.csdn.net/KeepStruggling/article/details/81560390