Thread 类的基本用法 — 学习笔记

13 篇文章 0 订阅
11 篇文章 0 订阅

线程创建

  • 线程创建与运行

Java中有三种线程创建方式, 分别为实现 Runnable 接口的 run 方法, 继承 Thread 类并重写 run 的方法,和使用带有返回值的 FutureTask 方式。

  1. 继承 Thread 类方式的实现
/**
 * 继承Thread类创建方式,并重写run方法
 */
public class ThredDemo {
    public static void main(String[] args) {
        // 获得当前的线程
        System.out.println("线程名称:" + mainThread.getName());
        // 创建线程
        Thread thread = new MyThread();
        // 启动线程
        thread.start();
    }
}
class MyThread extends Thread {
    //业务执行代码
    public void run() {
        System.out.println("你好,线程");
        System.out.println("线程的名称:" + thread.getName());
    }
    /**
     * 继承方法实现多线程的缺点:
     * Java语言是单继承,如果继承了Thead之后,就不能继承其他类。
     * 继承了线程类后就不可以继承其他业务类了
     */
}

如上代码中的 MyThread 类继承了 Thread 类, 并重写了 run() 方法。 在 main 函数里面创建了一个 MyThread 的实例, 然后调用该实例的 start 方法启动了线程。需要注意的是,当创建完对象后该线程并没有被启动执行, 直到调用了 start 方法后才真正启动了线程。

其实调用 start 方法后线程并没有马上执行而是处于就绪状态, 这个就绪状态是指该线程已经获取了除 CPU 资源外的其他资源, 等待获取 CPU 资源后才会真正处于运行状态。 一旦 run 方法执行完毕, 该线程就处于终止状态。

使用继承方式的好处是, 在 run() 方法内获取当前线程直接使用 this 就可以了, 无需使用 Thread.currentThread() 方法; 不好的地方是 Java 不支持多继承, 如果继承了 Thread 类, 那么就不能再继承其他类。另外任务与代码没有分离, 当多个线程执行一样的任务时需要多份任务代码, 而 Runnable 则没有这个限制。

  1. 实现 Runnable 接口的 run 方法方式
/**
 * 实现接口的创建线程方式
 * Runnable接口实现第一种
 */
public class ThredDemo3 {
    public static void main(String[] args) {
        // Thread thread = new MyThread2(); 不可以直接使用这种创建方式,因为不具备向上转型的条件,类不匹配

        // 创建 Runnable
        RunnableTask myThread = new RunnableTask ();
        // 创建一个新线程,传入
        Thread thread1 = new Thread(myThread);
        Thread thread2 = new Thread(myThread)

        thread1.start();
        thread2.start();
    }
}
class RunnableTask implements Runnable {
    //实现run方法。写具体的业务代码
    @Override
    public void run() {
        // 得到当前线程
        Thread thread = Thread.currentThread();
        System.out.println("线程执行: " + thread.getName());
    }
}

如上面的代码所示, 两个线程公用一个 myThread 代码逻辑, 如果需要, 可以给 RunnableTask 添加参数进行任务分区。 另外, RunnableTask 可以继承其他类。但是上面的两种方式都有一个缺点, 就是任务没有返回值。 下面是最后一种, 即使用 FutureTask 的方式。

  1. 使用可以有返回值的 FutureTask 的方式
/**
 * 实现 Callable
 */
public class ThredDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CallableTask myCallable = new CallableTask ();
        // 中间类来接收创建新线程的返回值
        FutureTask<Integer> futureTask = new FutureTask<>(myCallable);
        // 创建新线程
        Thread thread = new Thread(futureTask);
        // 启动线程
        thread.start();
        // 接收新线程执行的结果
        int result = futureTask.get();
        System.out.println("新线程执行的结果:" + result);
    }
}

/**
 * Callable后面的泛型里面可以是任意类型
 */
class CallableTask implements Callable<Integer> {
//生成随机数的方法
    @Override
    public Integer call() throws Exception {
        // 生成随机数 0-9
        int randomNum = new Random().nextInt(10);
        System.out.println(Thread.currentThread().getName() +
                "——随机数:" + randomNum);
        return randomNum;
    }
}

如上代码中 CallableTask 类实现了 Callable 接口的 call() 方法。在 main 函数内首先创建了一个 FutrueTask 对象,然后使用创建的 FutrueTask 对象作为任务创建了一个线程并且启用它, 最后通过 futureTask.get() 等待任务执行完毕并返回结果。

小结 : 使用继承方式的好处是方便传参,你可以在子类里面添加成员变 ,通过 set 方法设置参数或者通过构造函数进行传递,而如果使用 Runnable 方式,则只能使用主线程里面被声明为 final 变量。不好的地方是 Java 不支持多继承,如果继承了 Thread 类,那么子类不能再继承其他 ,而 Runnable 则没有这个限制 。前两种方式都没办法拿到任务的返回结果,但是Futuretask 方式可以。

线程等待

Java 中的 Object 类是所有类的父类, 鉴于继承机制, Java把所有类都需要的方法放到了 Object 类里面。

nao~ 就是下面这些方法。
在这里插入图片描述

在项目实践中经常会遇到一个场景, 就是需要等待某几件事情完成后才能继续往下执行, 比如多个线程加载资源,需要等待多个线程全部加载完毕再汇总处理。 Thread 类中有一个 join 方法就可以做这个事情, join 是无参且返回值为 void的方法。下面看一个简单的例子。

/**
 * 在主线程中创建两个子线程,每个子线程中产生一个随机数,最终等待子线程执行完之后
 * 在主线程累计两个子线程的结果
 */
public class ThreadDemo_数组求和 {
    static int num1 = 0;
    static int num2 = 0;
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            num1 = new Random().nextInt(10);
        });
        t1.start();
        Thread t2 = new Thread(() -> {
            num2 = new Random().nextInt(10);
        });
        t2.start();
        
        //等待子线程执行完毕再往下继续执行
        t1.join();
        t2.join();
        System.out.println(num1 + num2);
    }

}

如上代码在主线程里面启动了两个子线程, 然后分别调用了它们的 join() 方法,那么主线程首先会在调用 t1.join() 方法后被阻塞, 等待 t1 执行完毕后返回。t1 执行完毕后 t1.join() 就会返回, 然后主线程调用 t2.join() 方法后再次被阻塞, 等待 t2 执行完毕后返回。

另外, 线程 A 调用线程 B 的 join 方法后会被阻塞,当其他线程调用了线程 A 的 interrupt() 方法中断了线程 A 时, 线程 A 会抛出 InterruptedException 异常而返回。 下面是一个例子:

public class ThreadDemo_线程中断 {
    public static void main(String[] args) throws InterruptedException {
        // 线程1
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程1开始执行...");
                for (; ;) {
                }
            }
        });
        // 获取主线程
        final Thread mainThread = Thread.currentThread();

        // 线程2
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                // 休眠1s
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 中断主线程
                mainThread.interrupt();
            }
        });

        // 启动子线程1
        thread1.start();

        //延迟1s启动线程2
        thread2.start();

        try { // 等待线程1执行结束
            thread1.join();
        }catch (InterruptedException e) {
            System.out.println("main thread:" + e);
        }
    }
}

输出如下结果:
在这里插入图片描述
如上代码在 thread1 线程里面执行死循环,主线程调用 thread1的 join方法阻 塞自己等待线程 thread1 执行完毕,待 thread2 休眠 1s 后会调用主线程的interrupt() 方法设置主线程的中断标志,从结果看在主线程中的thread1.join()处会抛出 InterruptedException 异常。这里需要注意的是 thread2 里面调用 是主线程的 interrupt() 方法, 而不是线程 thread1 的。

线程休眠

  • 让线程睡眠的 sleep 方法

Thread 类中有一个静态的 sleep 方法, 当一个执行中的线程调用了 Thread 的 sleep 方法后, 调用线程会暂时让出指定时间的执行权, 也就是在这期间不参与 CPU 的调度。 指定的睡眠时间到了后该函数会正常返回, 线程就处于就绪状态, 然后参与 CPU 的调度, 获取到 CPU 资源后就可以继续运行了。

import java.text.SimpleDateFormat;
import java.util.Date;

public class ThreadDemo_sleep {
    public static void main(String[] args) throws InterruptedException {
        SimpleDateFormat formatter= new SimpleDateFormat("yyyy-MM-dd 'at' HH:mm:ss z");


        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("我是子线程");
            }
        });
        Date date1 = new Date(System.currentTimeMillis());
        System.out.println(formatter.format(date1));
        thread.start();
        //System.out.println(System.currentTimeMillis());
        
        // 休眠3s
        Thread.sleep(3000);
        
        Date date2 = new Date(System.currentTimeMillis());
        System.out.println(formatter.format(date2));
        //System.out.println(System.currentTimeMillis());

    }
}

在这里插入图片描述
从执行结果来看,线程调用 sleep 方法让自己睡眠 10s ,再继续执行主线程。这里要注意, 如果在调用 Thread.sleep(long millis) 时为 millis 参数传递了一个负数,则会抛出 IllegalArgumentException 异常。

  • 使用 TimeUnit 休眠
import java.util.concurrent.TimeUnit;
TimeUnit.DAYS.sleep(1);//天
TimeUnit.HOURS.sleep(1);//⼩时
TimeUnit.MINUTES.sleep(1);//分
TimeUnit.SECONDS.sleep(1);//秒
TimeUnit.MILLISECONDS.sleep(1000);//毫秒
TimeUnit.MICROSECONDS.sleep(1000);//微妙
TimeUnit.NANOSECONDS.sleep(1000);//纳秒
  • 让出 CPU 执行权的 yield 方法

Thread 类中有一个静态的 yield 方法, 当一个线程调用 yield 方法时, 实际就是在暗示线程调度器当前线程请求让出自己的 CPU 使用, 但是线程调度器可以无条件忽略这个暗示。 我们知道操作系统是为每个线程分配一个时间片来占用 CPU 的, 正常情况下当一个线程把分配给自己的时间片使用完后, 线程调度器才会进行下一轮的线程调度, 而当一个线程调用了 Thread 类的静态方法 yield 时, 是在告诉线程调度器自己占有的时间片中还没有使用完的部分自己不想使用了, 这暗示线程调度器现在就可以进行下一轮的线程调度。

当一个线程调用 yield 方法时,当前线程会让出 CPU 使用权, 然后处于就绪状态,线程调度器会从线程就绪队列里面获取一个线程优先级最高的线程,当然也有可能会调度到刚刚让出 CPU 的那个线程来获取 CPU 执行权。

下面举例来理解:

public class ThreadDemo_yield implements Runnable{

    ThreadDemo_yield() {
        // 创建并启动线程
        Thread thread = new Thread(this);
        thread.start();
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            // 当i=0时让出CPU执行权,放弃时间片,进行下一轮调度
            if ((i % 5) == 0) {
                System.out.println(Thread.currentThread() + "yield cpu...");

                //当前线程让出CPU执行权,放弃时间片,进行下一轮调度
                //Thread.yield();
            }
        }
        System.out.println(Thread.currentThread() + " is over");
    }

    public static void main(String[] args) {
        new ThreadDemo_yield();
        new ThreadDemo_yield();
        new ThreadDemo_yield();
    }
}

输出结果如下
在这里插入图片描述
如上代码开启了三个线程,每个线程的功能都一样,都是在 for 循环中执行三次打印。每次运行都会有不一样的结果。解开 Thread.yield() 注释再执行,结果如下:
在这里插入图片描述

从结果可知, Thread.yield() 方法生效了, 三个线程分别在 i=0 时调用了 Thread.yield() 方法,所以三个线程自己的两行输出没有在一起,因为输出了第一行后当前线程让出了 CPU 执行权。

总结:sleep 与 yield 方法的区别在于,当线程调用 sleep 方法时调用线程会被阻塞挂起指定的时间,在这期间线程调度器不会去调度该线程。而是调用 yield 方法时,线程只是让出自己剩余的时间片,并没有被阻塞挂起,而是处于就绪状态,线程调度器下一次调度时就有可能调度到当前执行的线程。

线程中断

Java 中的线程中断是一种线程间协作模式, 通过设置线程中断标志并不能直接终止该线程的执行,而是被中断的线程根据中断状态自行处理。

  1. void interrupt() 方法: 中断线程,例如,当线程 A 运行时,线程 B 可以调用线程 A 的 interrupt() 方法来设置线程 A 的中断标志为 true 并立即返回。设置标志仅仅是设置标志,线程 A 实际并没有被中断,它会继续往下执行。如果线程 A 因为调用了 wait 系列函数、join 方法或者 sleep 方法而被阻塞挂起,这时候若线程 B 调用线程 A 的 interrupt() 方法,线程 A 会在调用这些方法的地方抛出 InterruptedException 异常而返回。
  2. boolean isInterrupted() 方法: 检测当前线程是否被中断,如果是返回 true,否则返回 false。
    public boolean isInterrupted() {
        // 传递 false,说明不清除中断标志
        return isInterrupted(false);
    }
  1. boolean interrupted() 方法: 检测当前线程是否被中断,如果是返回 true,否则返回 false。与 isInterrupted 不同的是,该方法如果发现当前线程被中断,则会清除中断标志,并且该方法是 static 方法,可以通过 Thread 类直接调用。另外从下面的代码可以知道 interrupted() 内部是获取当前调用线程的中断标志而不是调 interrupted() 方法的实例对象的中断标志。
    public static boolean interrupted() {
        // 清除中断标志
        return currentThread().isInterrupted(true);
    }

下面通过一个例子来了解 interrupted() 与 isInterrupted() 方法的不同之处。

public class ThreadDemo_线程中断 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                while (!Thread.currentThread().isInterrupted()) { }
                System.out.println("isInterrupted:" + Thread.currentThread().isInterrupted());
                System.out.println("isInterrupted:" + Thread.currentThread().isInterrupted());
                System.out.println("isInterrupted:" + Thread.currentThread().isInterrupted());
                System.out.println();
                System.out.println("interrupted:" + Thread.interrupted());
                System.out.println("interrupted:" + Thread.interrupted());
                System.out.println("interrupted:" + Thread.interrupted());
            }
        });
        t.start();
        Thread.sleep(100);
        t.interrupt();
    }
}

在这里插入图片描述
我们看到在使用 Thread.currentThread().isInterrupted() 时,线程中断后标志位都是 true ,他是没有被清除掉的。而调用 Thread.interrupted() 方法后在一次标志位为 true 后到下一次中断标志就被清除归回 false 了。

获取当前线程

在这里插入图片描述

public class ThreadDemo {
    public static void main(String[] args) {
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName());
   }
}
  • 14
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值