简单学习Thread类的基本用法

Thread类

在java中,我们通过Thread类来进一步操作线程;

线程的创建

比较常用的创建线程的方法和jconsole的使用可以看这个=> 创建线程的常用方法

这里我们来看一下Thread类中的两个构造方法;

1. Thread (String name)  ;  在创建线程时,给线程设置一个名字 , 这样方便我们后续调试 ;

2. Thread (Runnable runnable , String name) ;  第一个参数是把一个Runnable任务交给线程, 第二个参数 是 给线程设置一个名字;

例子:  我想创建一个 名字是t2的线程;

public class Test1 {
    public static void main(String[] args) {
        Thread t2 = new Thread(new Runnable(){
            @Override
            public void run() {
                while (true){
                    System.out.println("t2线程");
                }
            }
        } , "t2");  // 创建一个名字是 t2 线程 , 并且t2线程里循环输出 "t2线程"
        t2.start();   // 不要忘记 调用start() ;

        while (true){
            System.out.println("main");  // main主线程循环输出 "main"
        }
    }
}

(创建线程时,设置线程名字是 "t2" , 并且线程的任务是循环输出"t2线程") 

此时运行代码的 和 查看 jconsole 的结果 :

 (给线程设置一个名字,后面我们一看名字就知道是哪个线程)

一定要调用 start()方法, 调用start()才能真正调用系统api创建出线程 ;

注意:变量捕获问题

举一个例子: 我想创建一个 t1 线程 , 让他把1到100打印输出 ;

 (用 num 来 记录 1到100的值, 但编译器确包错了)

原因: Lambda表达式本质就是匿名内部类,匿名内部类里的变量捕获, 要求匿名内部类里访问的变量要是 final修饰的 常量  或者  从创建出来就没有修改过值的变量(把变量当成常量来用) , 很明显这里 num 有修改值的 操作 , 所以编译器报错了 ;

解决方法 : 将这里的 num 从局部变量 改成 成员变量 或 静态成员变量 ;

   (此时num作为类的成员属性定义,编译器就没有报错了)

此时这里就是用的 匿名内部类访问外部类成员的语法, 就不会触发 变量捕获 ;

线程的休眠

举个例子 :   我想创建一个 t1 线程 , 让他把1到100打印输出 ;

public class Test1 {
    private static int num = 1 ;      // num 写在方法外面
    public static void main(String[] args) {
         Thread t1 = new Thread(()->{         // 这里用Lambda表达式的方法创建线程
             while (num <= 100){           //判断 num <= 100
                 System.out.println(num);
                 num ++ ;
             }
         } , "t1");
         t1.start();    // 记得调用start方法`
    }
}

运行一下 : 结果 刷一下就结束了

(怎么眨一下眼睛就把1到100给打印完了?)

Thread.sleep() 方法 : 是Thread类的静态方法

可以让线程休眠 xx 毫秒 , 需要抛一个 InterruptedException 异常;

休眠时线程不会执行任务,也不会被销毁,相当于摸鱼一段时间,时间到我在来工作; 

我们用这个方法让线程每隔 1 秒 打印输出一次 (每打印输出一次就休眠1秒) ; 

public class Test1 {
    private static int num = 1 ;      // num 写在方法外面
    public static void main(String[] args) {
         Thread t1 = new Thread(()->{         // 这里用Lambda表达式的方法创建线程
             while (num <= 100){           //判断 num <= 100
                 System.out.println(num);
                 num ++ ;
                 try {
                     Thread.sleep(1000);   // 休眠 1000毫秒 = 1 秒
                 } catch (InterruptedException e) {
                     throw new RuntimeException(e);
                 }
             }
         } , "t1");
         t1.start();    // 记得调用start方法`
    }
}

此时看运行结果 : 每隔1秒打印一次,就不会搜一下就结束了;

线程的终止/打断

如果想让一个线程执行到一半或者执行到某个阶段,就终止了,不在继续运行下去;

在java中唯一的方法,就是让 run()方法执行完 ; (run()方法执行完了,线程就自动销毁了); 

 1.设置一个"标记" 作为run结束的标记 

大部分情况下,run没有执行完,是因为run里有一个 while 之类的循环没有结束,while循环不结束,导致run也迟迟不能结束,我们可以设置一个"标记",来让while循环停止,从而达到让 run 快点结束的效果; 

例子:  我想让 t1 线程执行10秒后就终止;

代码思路: 

1. 设置 一般while 里我们用的是 true, 但这里我们用 一个变量 isQuit 作为 while结束的标记

2.main主线程里,我们用sleep,让main线程休眠10秒后,将isQuit置为true;

3.让t1线程在执行过程中,打印一些信息方便我们查看 ;

整体流程:

运行结果

(在差不多10秒后, t1 结束)

代码  

public class Test2 {
    private static boolean isQuit = false ;
    public static void main(String[] args) throws InterruptedException {
         Thread t1 = new Thread(()->{
             while(!isQuit){       // 用 isQuit代替 true
                 System.out.println("t1正在执行");    // 打印输出,方便我们查看
                 try {
                     Thread.sleep(2000);    // 让t1每输出一次就休眠 2 秒
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }
             System.out.println("t1终止");

         } , "t1");  // 用Lambda创建 t1线程 ;
        t1.start();

        // 在t1 运行 10 秒后 , 将 isQuit 置为 true, 让 while循环停止 ;
        System.out.println("10秒后t1停止");
        Thread.sleep(10000);
        System.out.println("将isQuit置为true");
         isQuit = true ;     // 将isQuit设置为 true
    }
}

2. 使用Thread自带的"标志位"来实现终止

上面手动设置"标记"的方法,有两个大问题: 1.要创建出一个变量(这个变量还要考虑Lambda的变量捕获问题); 2.当我们把isQuit设置true时,while不会立刻停止,还要等sleep休眠结束返回到while判断才会停止(不够及时) ;

通过这两个方法来用Thread自带的"标志位"来实现终止 

1.isInterrupted() 方法 : 获取当前线程的标志位 ;

2. Interrupted() 方法 : 将当前线程的标志位置为 true; 

例子:  我想让 t1 线程执行5秒后就终止;

代码思路:

1.用 isInterrupted() 作为 while的 判断 , 不需要手动创建 一个变量; 

2.在 线程执行5秒后, 调用 Interrupted() 将 t1对象的标志位 置为 true;

细节问题:  怎么在Lambda表达式里调用isInterrupted()方法?

用 Thread.currentThread() 来, 获取 当前 t1线程的实例,就可以调用isInterrupted()方法;

代码

public class Test1 {
    public static void main(String[] args) throws InterruptedException {
          Thread t1 = new Thread(()->{
              while (!Thread.currentThread().isInterrupted()){
                  try{
                      System.out.println("t1正在运行");
                      Thread.sleep(1000);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                       break;             // 使用break让while循环停止 ;
                  }
              }
              System.out.println("t1停止");
          }, "t1");
           t1.start();
        // 在t1运行5秒后,将t1的标志位置为true,
        System.out.println("5秒后t1停止");
        Thread.sleep(5000);
        System.out.println("将t1的 标志位 置为true");
        t1.interrupt();
    }
}
运行结果

(调用Interrupted()后,while循环结束,线程也执行完了)

interrupted()的终止原理 

1. sleep 让线程休眠, 线程必须到时间了才会被唤醒,就算 此时 标志位已经为 true了, 也要等我这里的sleep休眠完; 

2.interrupted 将标志为改为true , 同时会触发 sleep的一个异常, 达到 唤醒线程的作用,让线程不必等sleep休眠结束 就被唤醒 ; 这里的异常就是 : InterruptedException ;

3.当sleep触发异常后, 会清除掉我们设置的 标记位 , 需要我们手动在 catch 里加一个 break , 来让while循环停止 (如果没有break的话,线程就不会停止; 我们也可以用来做了一些收尾工作,然后在break);

用自带标志位的好处

1. 不需要额外创建变量 (不用我们手动创建变量,我们也无需考虑变量捕获的问题);

2. 可以唤醒正在sleep休眠的线程 , 手动设置的标志位还要等sleep休眠完, 而自带的标志位可以直接唤醒线程(设置休眠个100秒,就可以很好的看出两者的区别)

线程的等待

让一个线程等待 另一个线程的执行完后, 自己再执行 ; 

join() 方法 : 在哪个线程里调用 , 哪个线程就要等待 , 哪个对象调用的join, 哪个对象就是被等待的;

和 sleep一样 , join要抛一个 InterruptedException异常 ;

例子:  让 b线程等待a线程执行完后 ,b线程才执行 ;

代码  : a和b 线程每隔一秒输出一次,输出5次 ; b线程等待a线程执行完后,自己才开始输出 ;

public class Test2 {
    public static void main(String[] args) throws InterruptedException {
        Thread a = new Thread(()->{
           try{
               for (int i = 0; i < 5; i++) {
                   System.out.println("a正在运行");
                   Thread.sleep(1000);
               }
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
            System.out.println("a结束运行");
        }, "a");
        Thread b = new Thread(()->{
            try {
                a.join();          // 让b线程等待a线程 : a线程执行完后,b线程才执行 ;
                for (int i = 0; i < 5; i++) {
                    System.out.println("b正在运行");
                    Thread.sleep(1000);
                }
            } catch (InterruptedException e) {
              e.printStackTrace();
            }

        }, "b");
         a.start();
         b.start();
    }
}
运行结果

(b线程要等a线程结束了,自己才开始输出)

join()方法的等待是"死等", "等待的线程" 一定要等到 "被等待的线程"执行完,自己才开始执行 ;

(如果a线程里是一个while(true)循环, 那么b线程要等到天荒地老)

设置等到时间

可以给 join传一个参数,设置线程等待的时间, 时间单位是毫秒(ms) ;(设置一个界限,如果超时了我就不等了)

代码 :   b线程只等待 a 线程 3秒 , 3秒后b线程就不等了 ;

public class Test2 {
    public static void main(String[] args) throws InterruptedException {
        Thread a = new Thread(()->{
           try{
               for (int i = 0; i < 5; i++) {
                   System.out.println("a正在运行");
                   Thread.sleep(1000);
               }
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
            System.out.println("a结束运行");
        }, "a");
        Thread b = new Thread(()->{
            try {
                a.join(3000);          // b线程只等a线程3秒, 3秒后b线程就不等了 ; 3秒=3000毫秒
                for (int i = 0; i < 5; i++) {
                    System.out.println("b正在运行");
                    Thread.sleep(1000);
                }
            } catch (InterruptedException e) {
              e.printStackTrace();
            }

        }, "b");
         a.start();
         b.start();
    }
}
运行结果

(b在等了a 3秒后 , 就走了)

 获取线程的实例

Thread.currentThread() :是Thread类的静态方法;

可以获取当前线程的引用(谁调用就是获取谁的)

例子:  获取main的主线程的引用

public static void main(String[] args) {
    Thread t1 = Thread.currentThread();
    System.out.println(t1.getName());  //打印输出 线程的名字
}

main方法本身就会创建一个 "main主线程" ,在 main线程里调用的 Thread.currentThread(), 自然获取的是 main 线程的引用 , 所以t1获取到的就是 main线程 ;

运行结果输出:  main ;

getName():获取线程的名字, main线程,名字自然是: "main", 所以最后输出main; 

线程还有很多属性,和方法例如这里的getName(), 咋们下次在学习;

总结

1. Thread.sleep( xx ms)  能让线程休眠  xx 毫秒 ;

2. isInterrupted() 获取Thread对象自带的标志位,并且可以通过interrupt() 将标志位修改成true; 同时 interrupt() 可以将休眠的线程唤醒, 唤醒线程后可以针对后续工作做出调整:(可以什么都不做,继续让线程运行 ; 可以直接break 停止循环,终止线程 ; 可以做一些收尾工作,然后break)

3.join() 线程等待,是哪个对象调用的,哪个就是"被等待" ;在哪个线程里调用的,哪个线程就是"要等待"; 一般最好加上等待时间,不然"被等待的人"迟迟不来, "等的人"岂不可怜;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值