Thread类的基本用法(详解版)

什么是线程?

线程是操作系统能够进行调度的最小单位,通常被视为轻量级的进程。线程在同一进程中共享进程的资源(如内存,打开的文件,网络等),但每一个线程都有自己的执行栈、程序计数器和局部变量。

为什么要引进线程?

        首先,“并发编程”成为“刚需”。

  • 单核CPU的发展遇到了瓶颈,想要提高计算能力,就需要多核CPU,而并发编程能更充分利用多核资源。
  • 有些任务场景需要“等待io”,为了让等待IO的时间能够去做一些其他工作,也需要用到并发编程。
    其次,虽然多进程也能实现并发编程,但是线程比进程更轻量。
  • 创建线程比创建进程更快
  • 销毁线程比销毁进程更快
  • 调度线程比调度进程更快  

1.线程的创建

        a.继承Thread类
//继承Thread来创建一个线程类:
class MyThread extends Thread{
    @Override
    //重写run方法
    public void run() {
        System.out.println("hello Thread");
    }
}

public class Demo1 {
    public static void main(String[] args) {
        //创建MyThread类的实例
        MyThread thread = new MyThread();
        //调用start()方法,启动线程
        thread.start();
    }
}
        b.实现Runnable接口
//实现Runnable接口
class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println(" hello Thread2");
    }
}

public class Demo2 {
    public static void main(String[] args) {
        MyRunnable runnable = new MyRunnable();
        //创建 Thread 类实例, 调⽤ Thread 的构造⽅法时将 Runnable 对象作为 target 参数.
        Thread t = new Thread(runnable);
        //调用start方法
        t.start();
    }
}
c.匿名内部类创建 Thread 子类对象
// 使⽤匿名类创建 Thread ⼦类对象
public class Demo3 {
    public static void main(String[] args) {
        Thread t = new Thread(new Thread(){
            
            @Override
            public void run() {
                System.out.println("hello Thread3");
            }
        });

        t.start();
    }
}
d.匿名内部类实现Runnable子类对象
// 使⽤匿名类创建 Runnable ⼦类对象
public class Demo4 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(" hello Thread4");
            }
        });
        
        t.start();
    }
}
 e.lambda 表达式创建 Runnable 子类对象
// 使⽤ lambda 表达式创建 Runnable ⼦类对象
public class Demo5 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            System.out.println(" hello Thread5");
        });
        
        t.start();
    }
}

2.Thread的常见构造方法

Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("this is my name");
Thread t4 = new Thread(new MyRunnable(), "this is my name");

3. Thread 的几个常见属性

  • ID是线程的唯一标识,不同的线程不会重复
  • 名称是各种调试工具用到的
  • 状态表示线程当前所处的一个情况
  • 优先级高的理论上来说更容易被调度到
  • JVM会在一个进程的所有非后台进程结束后,才会结束运行
  • 简单理解为run方法是否执行完
  • 线程中断问题,下面会进一步说到
public class Demo6 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        t1.start();
        System.out.println("ID:"+Thread.currentThread().getId());
        System.out.println("线程名称:"+Thread.currentThread().getName());
        System.out.println("线程状态:" +Thread.currentThread().getState());
        System.out.println("线程是否存活:"+Thread.currentThread().isAlive());
        System.out.println("线程的优先级:"+Thread.currentThread().getPriority());
        System.out.println("线程是否被中断:"+Thread.currentThread().isInterrupted());
        System.out.println("是否为后台线程"+Thread.currentThread().isDaemon());


    }
}

4.线程的中断

假设B线程正在运行,A线程想要让B结束。其实核心就是A要想办法,让B的run方法执行完毕,此时B就自然结束了,而不是说,B的run执行一半,直接把B强制结束。

java中,结束线程,是一个“温柔”的过程,不是直接简单粗暴的。主要是怕,B某个工作,执行一半时,被强制结束了,此时B对应的结果数据是一个“半成品”(我们更希望,是一个完整的过程)。

a.通过共享的标记来进行沟通
public class Demo7 {
    private static boolean flg = false;
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while(!flg) {
                System.out.println("hello Thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            System.out.println("t 线程执行结束");
        });

        t.start();

        Thread.sleep(3000);
        //修改flg变量,就能够影响到t线程结束了
        System.out.println("main线程尝试终止t线程");
        flg = true;
    }
}
b. 调用 interrupt() 方法来通知
public class Demo8 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            //获取当前引用 Thread类的静态方法 就能获取到调用这个方法的线程的实例
            Thread t2 = Thread.currentThread();
            //Thread类里面有一个成员,就是interrupted boolean类型的 初始情况下,这个变量是false,未被终止的
            //一旦外面的其他线程调用一个interrupted方法,就会设置上述标志位
            while(!t2.isInterrupted()) {
                System.out.println("hello Thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println(" t 线程结束");
        });

        t.start();
        Thread.sleep(3000);
        //在主线程中,控制t线程被终止,设置上述标志位
        t.interrupt();
    }
}

那么根据上述的理解,此代码是能够跑起来的。接下来我们来看看是否正确呢?

 抛出了一个RuntimeException异常,由于判定isInterrupted()和执行打印这两个操作太快了。因此整个循环,主要的时间都花在sleep上面了。main在调用interrupted的时候,大概率t线程处于sleep状态中呢。此处的interrupted不仅仅能设置标志位,还能把那个sleep操作给唤醒。调用interrupted之后,sleep被唤醒,此时就会进入catch语句,抛出异常。由于catch语句中默认再次抛出异常,没人catch,最终就交给了jvm这一层了,进程就直接异常终止了!那么有了上述原因,我们直接不抛出异常,换成一个打印。

public class Demo8 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            //获取当前引用 Thread类的静态方法 就能获取到调用这个方法的线程的实例
            Thread t2 = Thread.currentThread();
            //Thread类里面有一个成员,就是interrupted boolean类型的 初始情况下,这个变量是false,未被终止的
            //一旦外面的其他线程调用一个interrupted方法,就会设置上述标志位
            while(!t2.isInterrupted()) {
                System.out.println("hello Thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    //throw new RuntimeException(e);
                    System.out.println("执行到catch语句了");
                }
            }
            System.out.println(" t 线程结束");
        });

        t.start();
        Thread.sleep(3000);
        //在主线程中,控制t线程被终止,设置上述标志位
        t.interrupt();
    }
}

我们接着运行看看效果:

 可以发现,一直在打印hello Thread,说明还是有问题的,看起来上面的标志位好像没有被设置一样。

首先,interrupted肯定会设置这个标志位的,其次,当sleep等阻塞的函数被唤醒之后,就会先清空刚才设置的interrupted标志位,所以循环才会一直执行。

所以,如果想要结束循环,可以在catch中使用break或者return。

 1.Interrupted方法能够设置标志位,也能够唤醒sleep等阻塞方法

2.sleep被唤醒之后,又能够清除标志位

5.线程等待

操作系统中,针对多个线程的执行,是一个“随机调度,抢占式执行”的过程。

线程等待,就是确定两个线程的“结束顺序”。我们虽然无法确定两个线程调度执行的顺序,但是可以控制,谁先结束,谁后结束。让后结束的线程,等待先结束的线程即可。此时后结束的线程就会进入阻塞,一直到先结束的线程真的结束了,阻塞才会解除。

例如:现在有两个线程A,B    

在A线程中调用B.join,意思就是让A线程等待B线程先结束,然后A再继续执行!

public class Demo9 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {

            for (int i = 0; i < 3; i++) {
                System.out.println("hello Thread1");
            }
        });

        Thread t2 = new Thread(() -> {
            try {
                //让t2线程等待t1线程先完成它的任务
                t1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            for (int i = 0; i < 3; i++) {
                System.out.println("hello Thread2");
            }
        });

        t2.start();
        t1.start();
    }
}

5.线程休眠

两个参数的方法可以进一步精确控制睡眠的时长。 

public class Demo10 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            try {
                //让线程休眠3秒的时间
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t线程休眠完");
        });

        t.start();
        //主线程等待t线程先完成任务
        t.join();
        System.out.println("main线程等待完成t线程");
    }
}

那么我们运行程序在显示台上就会看到,三秒后才会开始打印"t线程休眠完",因为我们是在main线程中写的t.join(),所以我们的main线程会等待t线程先完成任务后,main线程才开始执行自己的任务。

好了,本期的分享就到这里了,我们下期再见咯! 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

并不会

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值