Java线程(创建线程、线程的状态,常见使用方法,如何优雅的停止线程)

1、开始创建和运行一个线程

方法一:直接使用Thread创建一个线程

 // 构造方法的参数是给线程指定名字,推荐
 Thread t1 = new Thread("t1") {
  @Override
  // run 方法内实现了要执行的任务
  public void run() {
  log.debug("hello");
  }
 };
 t1.start();

可以直接通过new Thread() 创建一个线程,默认创建的线程是非守护线程,代码中t1为线程的名称,因为Thread类实现了Runnable接口,所以必须重写run方法,run方法中的代码就是线程的运行内容。人后通过start方法就可以启动该线程了。

运行结果如下:

 19:19:00 [t1] c.ThreadStarter - hello //打印hello

注意:run方法并不是开启该线程,必须执行了start方法,才能运行该线程,运行代码即为run方法中的内容。

考虑到Runnable接口只有一个run方法,则Runnable为函数式接口,则实现的Thread可以使用拉姆达(lambda)表达式,结果如下

 Thread t1 =  new Thread(() -> {
     log.debug("running...");
 });
 t1.start(); //实现的效果相同

方法二:使用 Runnable 配合 Thread(推荐使用)

为了提高代码的可用性,我们一般将线程的创建(线程)与只要执行的内容(内容)分开

  • Thread 代表线程

  • Runnable 可运行的任务(线程要执行的代码)

具体实现如下:

 // 创建任务对象
 Runnable task = new Runnable() {
      @Override
      public void run() {
      log.debug("hello");
      }
 };
 // 参数1 是任务对象; 参数2 是线程名字,推荐
 Thread t2 = new Thread(task, "t2");
 t2.start();

输出的结果如下:

 19:19:00 [t2] c.ThreadStarter - hello

同样可以使用lambda表达式简化,如下:

 // 创建任务对象
 Runnable task = () -> log.debug("hello");
 // 参数1 是任务对象; 参数2 是线程名字,推荐
 Thread t2 = new Thread(task, "t2");
 t2.start(); //实现的结果完全相同

方法三:FutureTask配合 Thread

FutureTask可以接收Callable类型的参数,Callable可以用来处理具有返回值的线程;

具体如下:

 // 创建任务对象
 FutureTask task3 = new FutureTask<>(() -> {
  log.debug("hello");
  return 100;
 });
 // 参数1 是任务对象; 参数2 是线程名字,推荐
 new Thread(task3, "t3").start();
 // 主线程阻塞,同步等待 task 执行完毕的结果
 Integer result = task3.get();
 log.debug("结果是:{}", result);

输出的结果如下:

 19:22:27 [t3] c.ThreadStarter - hello
 19:22:27 [main] c.ThreadStarter - 结果是:100

Runnable接口没有返回值,Callable接口有返回值

在创建线程是,建议使用Runnable或Callable接口,这样更加容易与线程池或更加高级的API配合使用;他们使线程的任务与线程的创建分开了,使用起来更加灵活;

2、线程的常见方法

方法名 是否静态(STATIC) 功能说明 注意
start() 启动一个新线程,在新的线程运行 run 方法中的代码 start 方法只是让线程进入就绪,里面代码不一定立刻运行(CPU 的时间片还没分给它)。每个线程对象的start方法只能调用一次,如果调用了多次会出现IllegalThreadStateException
run() 线程执行start方法后会执行的方法 如果在构造 Thread 对象时传递了 Runnable 参数,则线程启动后会调用 Runnable 中的 run 方法,否则默认不执行任何操作。但可以创建 Thread 的子类对象,来覆盖默认行为
join() 等待线程结束
join(long n) 有期限的等待线程结束,当等待n毫秒后若线程没有结束不会继续等待。
getId() 获取线程的长整形的id
getName() 获取线程名
setName(String name) 修改线程名
getPriority() 获取线程优先级
setPriority(int) 修改线程优先级 java中规定线程优先级是1~10 的整数,较大的优先级能提高该线程被 CPU 调度的机率
getState() 获取线程状态 Java 中线程状态是用 6 个 enum 表示,分别为:NEW, RUNNABLE, BLOCKED, WAITING,TIMED_WAITING, TERMINATED
isInterrupted() 判断是否被打 不会清除 打断标记
isAlive() 线程是否存活(还没有运行完毕)
interrupt() 打断线程 如果被打断线程正在 sleep,wait,join 会导致被打断的线程抛出 InterruptedException,并清除 打断标记 ;如果打断的正在运行的线程,则会设置 打断标记 ;park 的线程被打断,也会设置 打断标记
interrupted() 判断当前线程是否被打断 会清除 打断标记
currentThread() 获取当前正在执行的线程
sleep(long n) 让当前执行的线程休眠n毫秒,休眠时让出 cpu
yield() 提示线程调度器让出当前线程对CPU的使用 主要是为了测试和调试

3.3、部分方法的说明

3.3.1、start()和run()方法

  • 直接调用 run 是在主线程中执行了 run,没有启动新的线程

  • 使用 start 是启动新的线程,通过新的线程间接执行 run 中的代码

3.3.2、sleep()和yield()

sleep():

  1. 调用sleep()方法会让线程从Runnable(运行)状态转化为Timed Waiting(阻塞)状态;

  2. 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException;

  3. 睡眠结束后的线程未必会立刻得到执行;

  4. 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性

yield():

  1. 调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程

  2. 具体的实现依赖于操作系统的任务调度器

线程优先级:

  • 线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它

  • 如果 cpu 比较忙,那么优先级高的线程会获得更多的时间片,但 cpu 闲时,优先级几乎没作用

3.3.3、join 方法详解

为什么需要join()方法?

查看下面代码

 static int r = 0;
 public static void main(String[] args) throws InterruptedException {
      test1();
 }
 private static void test1() throws InterruptedException {
      log.debug("开始");
      Thread t1 = new Thread(() -> {
          log.debug("开始");
          sleep(1);
          log.debug("结束");
          r = 10;
      });
      t1.start();
      log.debug("结果为:{}", r);
      log.debug("结束");
 }

分析说明:

在上述代码中,因为主线程和线程 t1 是并行执行的,t1 线程需要 1 秒之后才能算出 r=10;而主线程一开始就要打印 r 的结果,所以只能打印出 r=0,那么怎样才能让主线程准确的打印出我们需要的结果r=10呢?

问题解决:

用 join()方法,加在 t1.start() 之后即可,即在 t1.start() 加上t1.join()。这样当主线程执行到t1.join(),就会等待t1线程执行结束之后才会继续向下执行。

用 sleep 行不行?为什么?

若在 t1.start()之后使用Thread.sleep(m),可以实现相应的结果,但是很不好,应为我们不知道t1线程需要执行的具体时间大概是多少;若mt1运行不得时间时,就会让主线程继续陷入阻塞状态,影响效率,所以,在此处不能使用sleep()方法。

3.3.4、 interrupt() 方法详解

打断 sleep(),wait()join() 的线程

这几个方法都会让线程进入阻塞状态

打断 sleep() 的线程, 会清空打断状态,以 sleep ()为例

 private static void test1() throws InterruptedException {
      Thread t1 = new Thread(()->{
          Thread.sleep(1000);
          }, "t1");
      t1.start();
      Thread.sleep(500);
      t1.interrupt();
      log.debug(" 打断状态: {}", t1.isInterrupted());
 }

输出结果:

 java.lang.InterruptedException: sleep interrupted
      at java.lang.Thread.sleep(Native Method)
      at java.lang.Thread.sleep(Thread.java:340)
      at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
      at cn.itcast.n2.util.Sleeper.sleep(Sleeper.java:8)
      at cn.itcast.n4.TestInterrupt.lambda$test1$3(TestInterrupt.java:59)
      at java.lang.Thread.run(Thread.java:745)
 21:18:10.374 [main] c.TestInterrupt - 打断状态: false

打断正常运行的线程, 不会清空打断状态

 private static void test2() throws InterruptedException {
      Thread t2 = new Thread(()->{
          while(true) {
              Thread current = Thread.currentThread();
              boolean interrupted = current.isInterrupted();
              if(interrupted) {
                  log.debug(" 打断状态: {}", interrupted);
                  break;
              }
          }
      }, "t2");
      t2.start();
       Thread.sleep(500);
      t2.interrupt();
 }

输出:

 20:57:37.964 [t2] c.TestInterrupt - 打断状态: true

3.4、不推荐使用的方法-停止和回复线程的方法

stop():停止线程运行

stop 方法会真正杀死线程,如果这时线程锁住了共享资源,那么当它被杀死后就再也没有机会释放锁,

其它线程将永远无法获取锁;

suspend() :挂起(暂停)线程运行

resume():恢复线程运行

推荐使用两阶段终止模式暂停线程

在一个线程 T1 中如何“优雅”终止线程 T2?这里的【优雅】指的是给 T2 一个料理后事的机会。

原理如下:

实现方法1:利用isInterrupted()方法

interrupt 可以打断正在执行的线程,无论这个线程是在 sleep,wait,还是正常运行

实现代码如下:

 class TPTInterrupt {
     private Thread thread;
     public void start(){
         thread = new Thread(() -> {
             while(true) {
                 Thread current = Thread.currentThread();
                 if(current.isInterrupted()) {
                     log.debug("料理后事");
                     break;
                 }
                 try {
                     Thread.sleep(1000);
                     log.debug("将结果保存");
                 } catch (InterruptedException e) {
                     current.interrupt();
                 }
                 // 执行监控操作 
             }
         },"监控线程");
         thread.start();
     }
     
     
     public void stop() {
         thread.interrupt();
     }
 }

调用暂停方法

     public static void main(String[] args) throws InterruptedException {
 ​
         TPTInterrupt t = new TPTInterrupt();
         t.start();
         Thread.sleep(3500);
         log.debug("stop");
         t.stop();
 ​
     }

实现结果,这样就可以在巧妙的停止线程了,还可以在停止之前做中做最后的事。

 12:10:53.249 c.TPTInterrupt [监控线程] - 将结果保存
 12:10:54.252 c.TPTInterrupt [监控线程] - 将结果保存
 12:10:55.254 c.TPTInterrupt [监控线程] - 将结果保存
 12:10:55.757 c.TestTPTInterrupt [main] - stop
 12:10:55.757 c.TPTInterrupt [监控线程] - 料理后事

实现方法2: 利用停止标记停止一个线程

 // 停止标记用 volatile 是为了保证该变量在多个线程之间的可见性
 // 我们的例子中,即主线程把它修改为 true 对 t1 线程可见
 @Slf4j(topic = "c.TPTVolatile")
 class TPTVolatile {
     private Thread thread;
     private volatile boolean stop = false;
     public void start(){
         thread = new Thread(() -> {
             while(true) {
                 Thread current = Thread.currentThread();
                 if(stop) {
                     log.debug("料理后事");
                     break;
                 }
                 try {
                     Thread.sleep(1000);
                     log.debug("将结果保存");
                 } catch (InterruptedException e) {
                 }
                 // 执行监控操作
             }
         },"监控线程");
         thread.start();
     }
     public void stop() {
         stop = true;
         thread.interrupt();
     }
 }
 ​
 //************************************************************************************
 //调用
     public static void main(String[] args) throws InterruptedException {
 ​
         TPTVolatile t = new TPTVolatile();
         t.start();
         Thread.sleep(3500);
         log.debug("stop");
         t.stop();
 ​
     }
 ​
 //*************************************************
 //输出结果
 12:17:07.660 c.TPTVolatile [监控线程] - 将结果保存
 12:17:08.672 c.TPTVolatile [监控线程] - 将结果保存
 12:17:09.685 c.TPTVolatile [监控线程] - 将结果保存
 12:17:10.158 c.TestTPTInterrupt [main] - stop
 12:17:10.158 c.TPTVolatile [监控线程] - 料理后事
 ​
 Process finished with exit code 0
 ​
 ​

你可能感兴趣的:(JUC编程篇,java,android,开发语言)