6-线程常用方法

目录

1.线程等待join(共有3个方法重载)

1.1.public void join()

1.2.public void join(long millis)

2.线程终止

2.1.通过自定义标记符来中断(常用)

2.2.调用 interrupt() 方法来中断(常用,最推荐使用)

2.2.1.Thread.interrupted()

2.2.2.Thread.currentThread().isInterrupted()

--->PS:(常见面试题)interrupted VS isInterrupted

2.3.使用stop停止线程(过期方法,不推荐使用)

3.yield让出CPU的执行权

4.获取当前线程

5.休眠当前线程

5.1.使用sleep休眠

5.2.使用TimeUnit休眠

PS:(线程练习题1)在主线程中创建两个子线程,每个子线程中产生一个随机数,最终等待子线程执行完之后,在主线程中累加两个子线程的结果

PS:(线程练习题2)实现多线程数组求和


1.线程等待join(共有3个方法重载)

有时我们需要等待⼀个线程完成它的⼯作后,才能进⾏⾃⼰的下⼀步⼯作。这时就需要⼀个⽅法明确等待线程的结束。

相比isAlive()的优势:①写法更优雅;②使用wait()进行阻塞等待,会使用更小的资源来完成等待的工作。

1.1.public void join()

/**
 * 关于join示例
 */
public class ThreadByJoin {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            //1.张三开始上班
            System.out.println("1.张三开始上班");
            //2.张三正在上班
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //3.张三下班
            System.out.println("3.张三下班");
        });
        t1.start();

        //等待线程t1执行完之后,再执行后面的代码
        t1.join();

        Thread t2 = new Thread(() -> {
            //1.李四开始上班
            System.out.println("1.李四开始上班");
            //2.李四正在上班
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //3.李四下班
            System.out.println("3.李四下班");
        });
        t2.start();
    }
}

 

查看源码:

 

1.2.public void join(long millis)

import java.time.LocalDateTime;

public class ThreadByJoin {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            System.out.println("1.张三开始上班" + LocalDateTime.now());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("3.张三下班" + LocalDateTime.now());
        });
        t1.start();

        t1.join(500);

        Thread t2 = new Thread(() -> {
            System.out.println("1.李四开始上班" + LocalDateTime.now());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("3.李四下班" + LocalDateTime.now());
        });
        t2.start();
    }
}

查看源码:

2.线程终止

李四⼀旦进到⼯作状态,就会按照⾏动指南上的步骤去进⾏⼯作,不完成是不会结束的。但有时需要增加⼀些机制,例如⽼板突然来电话了,说转账的对⽅是个骗⼦,需要赶紧停⽌转账,那张三该如何通知李四停⽌呢?这就涉及到我们的停⽌线程的⽅式了。

2.1.通过自定义标记符来中断(常用)

/**
 * 使用自定义标识符终止线程
 */
public class ThreadInterrupt {
    //1.声明一个自定义标识符(一定要加volatile(易挥发的,不稳定的)关键字)
    private volatile static boolean flag = false;

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            while(!flag) {
                System.out.println("正在转账......");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("啊?差点误了大事!");
        });
        thread.start();

        Thread.sleep(3000);

        //终止线程
        System.out.println("有内鬼,终止交易!");
        flag = true;
    }
}

但其问题在于:线程中断地不够及时。因为线程在执行过程中,无法调用 while(!isInterrupt) 来判断线程是否为终止状态,它只能在下一轮运行时判断是否要终止当前线程。

class InterruptFlag {
    // 自定义的中断标识符
    private static volatile boolean isInterrupt = false;

    public static void main(String[] args) throws InterruptedException {
        // 创建可中断的线程实例
        Thread thread = new Thread(() -> {
            while (!isInterrupt) { // 如果 isInterrupt=true 则停止线程
                System.out.println("thread 执行步骤1:线程即将进入休眠状态");
                try {
                    // 休眠 1s
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("thread 执行步骤2:线程执行了任务");
            }
        });
        thread.start(); // 启动线程

        // 休眠 100ms,等待 thread 线程运行起来
        Thread.sleep(100);
        System.out.println("主线程:试图终止线程 thread");
        // 修改中断标识符,中断线程
        isInterrupt = true;
    }
}

期望的是:线程执行了步骤 1 之后,收到中断线程的指令,然后就不要再执行步骤 2 了,但上述执行结果看出,使用自定义中断标识符无法实现预期结果。

2.2.调用 interrupt() 方法来中断(常用,最推荐使用)

使用 interrupt 方法可以给执行任务的线程,发送一个中断线程的指令,它并不直接中断线程,而是发送一个中断线程的信号,把是否正在中断线程的主动权交给代码编写者。

相比于自定义中断标识符,它能更及时地接收到中断指令。

public static void main(String[] args) throws InterruptedException {
    // 创建可中断的线程实例
    Thread thread = new Thread(() -> {
        while (!Thread.currentThread().isInterrupted()) {
            System.out.println("thread 执行步骤1:线程即将进入休眠状态");
            try {
                // 休眠 1s
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                System.out.println("thread 线程接收到中断指令,执行中断操作");
                // 中断当前线程的任务执行
                break;
            }
            System.out.println("thread 执行步骤2:线程执行了任务");
        }
    });
    thread.start(); // 启动线程

    // 休眠 100ms,等待 thread 线程运行起来
    Thread.sleep(100);
    System.out.println("主线程:试图终止线程 thread");
    // 修改中断标识符,中断线程
    thread.interrupt();
}

从执行结果看出,线程在接收到中断指令之后,立即中断了线程。

interrupt()(执行终止)需要配合 Thread.interrupted()(判断终止)或 Thread.currentThread().isInterrupted()(判断终止)来使用。

使⽤ Thread.interrupted() 或者 Thread.currentThread().isInterrupted() 代替⾃定义标志位。

Thread内部包含了一个boolean类型的变量作为线程是否被中断的标记。

2.2.1.Thread.interrupted()

/**
 * 使用interrupt终止线程
 */
public class ThreadInterrupt2 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            while(!Thread.interrupted()) { //Thread.interrupted()判断线程是否被终止,如果线程被终止,返回true;否则返回false。
                System.out.println("正在转账......");
            }
            System.out.println("啊?险些误了大事!");
        });
        //启动线程
        thread.start();

        Thread.sleep(100);

        //终止线程
        thread.interrupt();
        System.out.println("有内鬼,终止交易!");
    }
}

2.2.2.Thread.currentThread().isInterrupted()

public class ThreadInterrupt2 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            while(!Thread.currentThread().isInterrupted()) { //得到当前的线程,判断其是否是终止状态,如果是终止状态,返回true,否则返回false。 
                System.out.println("正在转账......");
            }
            System.out.println("啊?险些误了大事!");
        });
        thread.start();

        Thread.sleep(100);
        
        thread.interrupt();
        System.out.println("有内鬼,终止交易!");
    }
}

--->PS:(常见面试题)interrupted VS isInterrupted

二者执行结果一样。区别如下:

  1. interrupted属于静态方法,所有程序都可以直接调用的全局方法;而isInterrupted属于某个实例的私有方法。
  2. interrupted在使用完之后会重置中断标识符为初始状态false;而isInterrupted在使用完之后不会重置中断标识符。

推荐使用isInterrupted,不建议用interrupted是因为它是全局的,用完后状态会自动回滚,加剧了程序的复杂性和理解上的难度。

public class ThreadInterrupt2 {
    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();
    }
}

标识符默认为false,表示线程未被终止。

public class ThreadInterrupt2 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                while (!Thread.interrupted()) { }
                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();
    }
}

2.3.使用stop停止线程(过期方法,不推荐使用)

stop 方法是被 @Deprecated 修饰的过期方法,并且在其源码注释的第一句话就说明了 stop 方法为非安全的方法:因为 stop 方法会直接停止线程,这样就没有给线程足够的时间来处理停止前的保存工作,就会造成数据不完整的问题。

在最新版本 Java 中,此方法已经被直接移除了,所以强烈不建议使用。

小结——停止线程的 3 种方法:

  1. 自定义中断标识符,此方法的缺点是不能及时响应中断请求。
  2. 使用 interrupt,此方法是发送一个中断信号给线程,它可以及时响应中断,也是最推荐使用的方法。
  3. stop 方法,过期的不推荐使用。

3.yield让出CPU的执行权

  • static静态方法。
  • native表示yield是原生的方法,是JVM里的一个方法,调用C/C++里的yield方法,不是Java的方法。

yield不改变线程的状态,但是会重新去排队,而排队之后选择谁是不确定的。

yield方法会让出CPU执行权,让线程调度器重新随机调度线程,是有一定几率再一次调度到出让CPU执行权的线程上,这一次它就会接着刚才的继续往后执行到线程的方法了,不会再让一次执行权,因为yield已经被执行过。

/**
 * yield方法演示(让出CPU的执行权)
 */
public class ThreadYield {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            //得到当前线程
            Thread cThread = Thread.currentThread();
            for (int i = 0; i < 10; i++) {
                //让出CPU执行权
                Thread.yield();
                System.out.println("执行线程:" + cThread.getName());
            }
        }, "张三");
        t1.start();

        //创建并启动线程
        new Thread(() -> {
            //在一个main方法中可以起2个相同的cThread线程名字,是因为()->{}相当于有2个匿名内部类,都是在自己的作用域里,两个变量cThread是互相看不到的,彼此是隔离的
            Thread cThread = Thread.currentThread();
            for (int i = 0; i < 10; i++) {
                System.out.println("执行线程:" + cThread.getName());
            }
        },"李四").start();
    }
}

4.获取当前线程

public class ThreadDemo { 
    public static void main(String[] args) {
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName());
    }
}

5.休眠当前线程

5.1.使用sleep休眠

public class ThreadSleep {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            try {
                Thread.sleep(60 * 60 * 1000);
            } catch (InterruptedException e) {
                System.out.println("我接受到了终止执行的通知");
//                e.printStackTrace();
            }
        });
        thread.start();
        Thread.sleep(1000);
        System.out.println("终止子线程thread");
        thread.interrupt();
    }
}

缺点:在休眠较长时间时,还需要自己手动计算时间,麻烦、易错。

5.2.使用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);//纳秒

查看源码(实现原理-套了个壳):

PS:(线程练习题1)在主线程中创建两个子线程,每个子线程中产生一个随机数,最终等待子线程执行完之后,在主线程中累加两个子线程的结果

实现思路①:使用主线程把两个普通子线程(无返回值)生成的随机数值保存起来,等两个子线程执行完之后进行累加操作。考察如何把线程内部的变量赋值给全局变量。

JVM规定:在一个线程中修改另一个线程中的私有变量是不安全的。

在一个线程中可以打印另一个线程中的属性,但不能修改。

解决方案:将num1和num2提成公共的全局变量:加static关键字并将其放在类中main方法外。

import java.util.Random;

/**
 * 在主线程中创建两个子线程,每个子线程中产生一个随机数,最终等待子线程执行完之后,在主线程累计两个子线程的结果
 */
public class ThreadDemo14 {
    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));
    }
}

实现思路②:两个子线程使用有返回值的线程调用,执行完将生成的随机数值返回给主线程,主线程把值进行累加。

import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class ThreadDemo15 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<Integer> futureTask = new FutureTask<>(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int num = new Random().nextInt(10);
                return num;
            }
        });

        Thread t1 = new Thread(futureTask);
        Thread t2 = new Thread(futureTask);
        t1.start();
        t2.start();

        int num1 = futureTask.get();
        int num2 = futureTask.get();
        System.out.println("最终的结果:" + (num1 + num2));
    }
}

注:

  • 普通线程(无返回值)在打印最终结果前一定要加join()等待线程执行完;
  • 而带返回值的线程有get(),get方法需要得到线程的返回值,当线程执行完才能有返回值,所以不用加join()。

PS:(线程练习题2)实现多线程数组求和

  1. 给定一个很长的数组 (长度 1000w), 通过随机数的方式生成 1-100 之间的整数.
  2. 实现代码, 能够创建两个线程, 对这个数组的所有元素求和.
  3. 其中线程1 计算偶数下标元素的和, 线程2 计算奇数下标元素的和.
  4. 最终再汇总两个和, 进行相加.
  5. 记录程序的执行时间.
实现思路有2种(同上),此处使用思路2:

import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * 实现多线程大数组相加
 */
public class ThreadDemo16 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //记录开始执行时间
        long stime = System.currentTimeMillis(); //返回的是一个13位的时间戳-毫秒数

        int[] arrs = new int[10000000];

        //1.使用随机数初始化数组
        Random random = new Random();
        for (int i = 0; i < arrs.length; i++) {
            arrs[i] = (random.nextInt(100) + 1);
        }

        //2.创建两个线程执行相加操作
        //2.1.返回偶数之和
        FutureTask<Integer> task1 = new FutureTask<Integer>(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int temp = 0;
                for (int i = 0; i < arrs.length; i = i + 2) {
                    temp += arrs[i];
                }
                System.out.println("线程1:" + temp);
                return temp;
            }
        });
        Thread t1 = new Thread(task1);
        t1.start();

        //2.2.返回奇数之和
        FutureTask<Integer> task2 = new FutureTask<Integer>(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int temp = 0;
                for (int i = 1; i < arrs.length; i = i + 2) {
                    temp += arrs[i];
                }
                System.out.println("线程2:" + temp);
                return temp;
            }
        });
        Thread t2 = new Thread(task2);
        t2.start();

        int sum = task1.get() + task2.get();
        System.out.println("最终结果:" + sum);

        //记录执行完成时间
        long etime = System.currentTimeMillis(); //返回的是一个13位的时间戳

        //程序执行时间 = 执行完成时间 - 开始执行时间
        System.out.println("程序执行时间:" + (etime - stime) + "ms");
    }
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值