zoukankan      html  css  js  c++  java
  • 多线程与高并发学习笔记2

    二、 线程的使用

    创建线程的三种方式:

    • 方式1: 继承 Thread

      import java.util.concurrent.TimeUnit;
      
      /**
       * 通过继承方式创建线程
       *
       * @author 赵帅
       * @date 2021/1/1
       */
      public class CreateMyThreadByExtendThread extends Thread {
      
          @Override
          public void run() {
              try {
                  TimeUnit.SECONDS.sleep(3);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              System.out.println("通过继承方式实现自定义线程,当前线程为:" + Thread.currentThread().getName());
          }
      
          public static void main(String[] args) {
              // 当前线程为主线程 main
              System.out.println("Thread.currentThread().getName() = " + Thread.currentThread().getName());
      
              // 创建一个新的线程并开启线程,打印新的线程名
              CreateMyThreadByExtendThread thread = new CreateMyThreadByExtendThread();
              thread.start();
          }
      }
      
      
    • 方式2: 实现Runnable接口

      import java.util.concurrent.TimeUnit;
      
      /**
       * 通过实现Runnable接口方式
       *
       * @author 赵帅
       * @date 2021/1/1
       */
      public class CreateMyThreadByImplRunnable implements Runnable {
      
          @Override
          public void run() {
              try {
                  TimeUnit.SECONDS.sleep(3);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              System.out.println("通过实现Runnable接口方式实现自定义线程,当前线程为:" + Thread.currentThread().getName());
          }
      
          public static void main(String[] args) {
              // 当前线程为主线程 main
              System.out.println("Thread.currentThread().getName() = " + Thread.currentThread().getName());
      
              // 创建一个新的线程并开启线程,打印新的线程名
              Thread thread = new Thread(new CreateMyThreadByImplRunnable());
              thread.start();
          }
      }
      
    • 方式3: Callable+Feature

      import java.util.concurrent.*;
      
      /**
       * 通过实现Callable接口方式
       *
       * @author 赵帅
       * @date 2021/1/1
       */
      public class CreateMyThreadByImplCallable implements Callable<String> {
      
          @Override
          public String call() throws Exception {
              try {
                  TimeUnit.SECONDS.sleep(3);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              System.out.println("通过实现Callable接口方式实现自定义线程,当前线程为:" + Thread.currentThread().getName());
              return "hello";
          }
      
          public static void main(String[] args) throws ExecutionException, InterruptedException {
              // 当前线程为主线程 main
              System.out.println("Thread.currentThread().getName() = " + Thread.currentThread().getName());
      
              // callable接口实现的任务和Future接口或线程池一起使用
              CreateMyThreadByImplCallable callable = new CreateMyThreadByImplCallable();
      
              // 和Feature一起使用,Future表示未来,也就是说我执行这个任务,未来我去取任务执行的结果
              FutureTask<String> task = new FutureTask<>(callable);
              new Thread(task).start();
              System.out.println("main线程继续运行");
      
              // 等其他线程执行完了,再来拿task的结果,
              System.out.println("task.get() = " + task.get());
      
      
              // 使用线程池方式调用callable
              Future<String> submit = Executors.newSingleThreadExecutor().submit(callable);
              System.out.println("submit.get() = " + submit.get());
          }
      }
      

    三种方式的区别

    通过上面三种创建线程的方式,我们对线程的使用有了基本的了解 ,下面我们来分析这三种方式有什么区别:

    1. 继承Thread: 通过继承方式创建线程,因为java单继承的特性,使用的限制就非常多了,使用不方便。
    2. 实现Runnable: 因为单继承的限制,所以出现了Runnable,接口可以多实现,因此大大提高了程序的灵活性。但是无论是继承Thread还是实现Runnable接口,线程执行的方法都是 void 返回值。
    3. 实现Callable: Callable接口就是为了解决线程没有返回值的问题,Callable接口有一个泛型类型,这个泛型就代表返回值的类型,使用Callable接口就可以开启一个线程取执行, Callable一般和Future接口同时使用,返回值为Future类型,可以通过Future接口的get方法拿执行结果。get()方法是一个阻塞的方法。

    相同点:都是通过Thread类的start()方法来开启线程。

    线程的方法

    • start(): 开启线程,使线程从新建进入就绪状态

    • sleep(): 睡眠,使当前线程休息, 需要指定睡眠时间,当执行sleep方法后进入阻塞状态,

    • Join():加入线程,会将调用的线程加入当前线程。等待加入的线程执行完成后才会继续执行当前线程。

      /**
       * 线程方法示例
       * @author 赵帅
       * @date 2021/1/1
       */
      public class ThreadMethodDemo {
          public static void main(String[] args) throws InterruptedException {
              // 打印当前线程 main线程的线程名 main
              System.out.println("当前主线程线程名 = " + Thread.currentThread().getName());
      
              // 创建一个新的线程
              Thread thread = new Thread(() -> {
      
                  // 线程进入睡眠状态
                  try {
                      Thread.sleep(1000L);
                      System.out.println("当前线程名:" + Thread.currentThread().getName());
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              });
              // 开启线程 thread
              thread.start();
      
      //        thread.join();
              System.out.println("主线程执行完毕");
          }
      }
      

      打开和关闭注释thread.join()可以发现输出结果是不一样的,使用join后会等待thread执行结束后再继续执行main方法。

    • wait(): 当前线程进入等待状态,让出CPU给其他线程,自己进入等待队列,等待被唤醒。

    • notify(): 唤醒等待队列中的一个线程,唤醒后会重新进入就绪状态,准备抢夺CPU。

    • notifyAll(): 唤醒等待队列中的所有线程,抢夺CPU。

    • yield(): 让出CPU。当前线程让出CPU给其他的线程执行,但是自己也会进入就绪状态参与CPU的抢夺,因此调用yield方法后,仍然可能继续获得CPU。

      import java.util.concurrent.TimeUnit;
      
      /**
       * 线程方法示例
       * @author 赵帅
       * @date 2021/1/1
       */
      public class ThreadMethodDemo {
          public static void main(String[] args) throws InterruptedException {
              // 打印当前线程 main线程的线程名 main
              System.out.println("当前主线程线程名 = " + Thread.currentThread().getName());
      
              Object obj = new Object();
      
              // 创建一个新的线程
              Thread thread = new Thread(() -> {
      
                  // 线程进入睡眠状态
                  try {
                      synchronized (obj) {
                          obj.wait();
                          System.out.println("当前线程名:" + Thread.currentThread().getName());
                      }
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              });
              // 开启线程 thread
              thread.start();
      
              System.out.println("主线程执行完毕");
              // 等待thread进入等待状态释放锁,否则会产生死锁
              TimeUnit.SECONDS.sleep(6);
              synchronized (obj) {
                  obj.notify();
              }
          }
      }
      

    注意:wait和notify只能在synchronized同步代码块中调用,否则会抛异常。

    • Interrupt(): 中断线程,调用此方法后,会将线程的中断标志设置为true。

    线程的状态

    一个完整的线程的生命周期应该包含以下几个方面:

    1. 新建(New):创建了一个线程,但是还没有调用start方法
    2. 就绪(Runnable):调用了start()方法,线程准备获取CPU运行
    3. 运行(Running):线程获取CPU成功,正在运行
    4. 等待(Waiting):等待状态,和TimeWaiting一样都是等待状态, 不同的是Waiting是没有时间限制的等,而TimeWaiting会进入一个有时间限制的等。例如调用wait()方法后就会进入一个无限制的等,等待调用notify唤醒,而调用sleep( time)就会进入一个有时间限制的等。等待结束后(被唤醒或sleep时间到期)后就会重新进入就绪队列,等待获取CPU继续向下执行。
    5. 阻塞(Blocked):多个线程再等待临界区资源时,进入阻塞状态。
    6. 销毁(Teminated): 线程执行完毕,进入销毁状态,这个状态是不可逆的,是最终状态,当进入这个状态时,就代表线程执行结束了。

    以一张图来理解这几个状态:

    简单介绍一个线程的生命周期:

    当我们使用 new Thread()创建一个线程时,那么这个线程就处于创建状态;当我们调用start()方法后,此时线程就处于就绪状态(进入就绪状态后就不可能再进入创建状态了),但是调用start()方法后并不是说立马就会被CPU执行,而是会参与CPU的抢夺,当这个线程拿到CPU后,就会被执行。那么拿到CPU后就进入了运行状态。当调用了sleep或wait方法后,线程就进入了等待状态, 当等待状态被唤醒后,就会重新进入就绪队列等待获取CPU,当访问同步资源时或其他阻塞式操作时就会进入阻塞状态,阻塞状态结束重新进入就绪状态获取CPU。当线程运行完成后进入Teminate状态后,就代表线程执行结束了。

    sleep操作不释放锁,wait操作释放锁。

  • 相关阅读:
    图-拓扑排序
    图-最短路径-Dijkstra及其变种
    【链表问题】打卡7:将单向链表按某值划分成左边小,中间相等,右边大的形式
    【链表问题】打卡5:环形单链表约瑟夫问题
    【链表问题】打卡6:三种方法带你优雅判断回文链表
    【链表问题】打卡4:如何优雅着反转单链表
    【链表问题】打卡3:删除单链表的中间节点
    【链表问题】打卡2:删除单链表的第 K个节点
    史上最全面试题汇总,没有之一,不接受反驳
    一些可以让你装逼的算法技巧总结
  • 原文地址:https://www.cnblogs.com/Zs-book1/p/14232806.html
Copyright © 2011-2022 走看看