多线程的基础编程

目录

一. 线程的定义方法

二. 线程的并发执行

三. 查看线程效率

四. Thread类中的方法

五. 中断线程

六. 线程等待


一. 线程的定义方法

线程有五种定义方法

  • 第一种: 创建一个类继承Thread
class thread1 extends Thread{
    @Override
    public void run(){
        System.out.println("thread1");
    }
}

public class TestDemo {
    public static void main(String[] args) {
        //第一种方式:继承Thread
        Thread thread1 = new thread1();
        thread1.start();
    }
}
  • 第二种: 创建一个类实现Runnable接口
class thread2 implements Runnable{
    @Override
    public void run() {
        System.out.println("thread2");
    }
}

public class TestDemo {
    public static void main(String[] args) {
        //第二种方式:实现Runnable接口
        Thread thread2 = new Thread(new thread2());
        thread2.start();
    }
}
  • 第三种: 使用匿名内部类
public class TestDemo {
    public static void main(String[] args) {
        //第三种方式:匿名内部类
        Thread thread3 = new Thread(){
            @Override
            public void run() {
                System.out.println("thread3");
            }
        };
        thread3.start();
    }
}
  • 第四种: 使用Runnable匿名内部类
public class TestDemo {
    public static void main(String[] args) {
        //第四种方式:Runnable的匿名内部类
        Thread thread4 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread4");
            }
        });
        thread4.start();
    }
}
  • 第五种: lambda表达式, 本质上还是实现了Runnable接口
public class TestDemo {
    public static void main(String[] args) {
        //第五种方式:lambda表达式,这里是代替了Runnable接口
        Thread thread5 = new Thread(() ->{
            System.out.println("thread5");
        });
        thread5.start();
    }
}

这么多种定义方式中, 推荐使用Runnable,因为它只描述了任务,不关心由谁来执行,并且接口不关心多继承问题.

  

二. 线程的并发执行

这里我们实现两个线程一起运行案例:

这里先创建一个Thread类,内部的sleep方法用于让线程休眠

class MyThread extends Thread{
    @Override
    public void run() {
        while (true){
            System.out.println("hello world");
            try {
                Thread.sleep(1000);//强制让线程休眠1s,进入阻塞状态
                //1000的含义是1000ms内上不了CPU
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

然后创建main函数线程,main函数启动时会自带一个线程

public class TestDemo2 {
    public static void main(String[] args) {
        Thread thread = new MyThread();
        thread.start();//创建线程

        //这是main方法的线程,两个线程同时发生了并行或者并发
        //宏观区分不了并行并发,取决于系统的调度,所以统称为并发
        while (true){
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //休眠1s后唤醒哪个线程不知道,顺序完全随机(抢占式执行)
    }
}

看运行结果,可以看出两个线程交替运行

由于休眠1s后唤醒哪个线程不知道,所以执行顺序是随机的,也叫抢占式执行.

  

三. 查看线程效率

这里再来写一个案例:对一个数字进行10亿次自增,测试用循环和线程来执行的时间

先写一个普通循环的方法: (注意这里的测试数据不能太小, 因为创建线程也需要时间)

    //这里测试数据也不能太小,因为创建线程也需要时间
    public static final long Default_Test_Num = 10_0000_0000;

    //普通循环:
    public static void serial(){
        long beg = System.currentTimeMillis();
        long a = 0;
        for (long i = 0 ; i < Default_Test_Num; i++){
            a++;
        }
        long b = 0;
        for (long i = 0 ; i < Default_Test_Num; i++){
            b++;
        }
        long end = System.currentTimeMillis();
        System.out.println("循环执行时间:" + (end - beg) + "ms");
    }

再来一个利用线程的方法;

    public static void concurrency() throws InterruptedException {
        long beg = System.currentTimeMillis();
        Thread t1 = new Thread(() -> {
            long a = 0;
            for (long i = 0 ; i < Default_Test_Num; i++){
                a++;
            }
        });
        t1.start();

        Thread t2 = new Thread(() -> {
            long b = 0;
            for (long i = 0 ; i < Default_Test_Num; i++){
                b++;
            }
        });
        t2.start();

        //此处不能直接这样记录结束时间,因为求时间戳的代码是在main线程中
        //main与t1,t2是并发执行的,此时两个线程没执行完,就直接记录main函数结束时间了
        //应该让main等待两个线程跑完了再记录结束时间
        t1.join();//main等待t1执行完
        t2.join();//main等待t2执行完

        long end = System.currentTimeMillis();
        System.out.println("线程执行时间:" + (end - beg) + "ms");
    }

其中用到了join方法,此方法用于让main线程等待线程执行完

如果不加的话,main线程会提前结束,不会等待线程执行完,这样的测试数据不准确. 

这里我们用到了如下的时间戳来对程序计时

long beg = System.currentTimeMillis();
//执行的代码
long end = System.currentTimeMillis();
System.out.println("线程执行时间:" + (end - beg) + "ms");

结果如下, 线程的执行时间明显要快不少 

   

四. Thread类中的方法

首先介绍两个方法:

  • isDaemon() 是否是后台进程.    t1,t2默认都是前台进程,即使main结束,他俩也不会退出,都执行完才退出 但是后台进程在main结束后就退出,他俩被强行终止
  • isAlive() 是否存活.    start执行之前就是false,之后就是true

接下来是run和start方法的区别:

  • run只是单纯得描述了任务的内容,在main中创建就是运行在main线程中,从前到后运行
  • start则是一个特殊的方法,内部会在系统中创建线程

最后来介绍一下线程名:

我们可以给线程取名,方便在 jconsole(jdk的bin目录的文件) 中观察此线程

如下代码给 t1, t2 线程取了名字

    public static void main1(String[] args) {
        //线程也可以用构造方法去取名,再jconsole中观察
        Thread t1 = new Thread(() ->{
            while (true){
                System.out.println("thread1");
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "thread t1");
        t1.start();

        Thread t2 = new Thread(() ->{
            while (true){
                System.out.println("thread2");
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "thread t2");
        t2.start();
    }

 再运行此代码, 进入jconsole观察一下:

这个就是正在运行的线程名称.

如果我们想在IDEA中获取线程名称,也可以如下方式:

    public static void main1(String[] args) {
        Thread t = new Thread(() -> {
            //System.out.println(this.getName());//Runnable接口没有name属性
            //所以在Runnable接口中想要获取名字只能用currentThread()
            System.out.println(Thread.currentThread().getName());
        });
        t.start();
        Thread t2 = new Thread(){
            @Override
            public void run() {
                System.out.println(this.getName());//Thread类才有name属性
            }
        };
        System.out.println(Thread.currentThread().getName());
    }

这里注意, 如果线程用Runnable接口实现,是没有name属性

需要用currentThread方法获取实例后才能获得名字.

  

五. 中断线程

想要中断线程,我们可以设置一个标志位,在运行到一定次数后,改变标志位就可以中断线程.

这里列举两种方式:

  • 第一种方式:自己设置一个标志位来控制线程结束,但不够严谨
    /*第一种方式*/
    public static boolean isQuit = false;

    public static void main1(String[] args) {
        Thread t = new Thread(() -> {
            while (!isQuit) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
        //过了5s后,更改这个标志位,让这个线程停下来
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        isQuit = true;
        System.out.println("线程中断");
    }
  • 第二种方式:使用Thread中内置的一个标志位来判定

Thread内置标签有个:

Thread.interrupted();     这是一个静态方法
Thread.currentThread().isInterrupted(); currentThread()能获取当前线程的实例 (推荐)

这里用第二种来写一下: 

    /*第二种方式*/
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    //触发异常后,立即退出
                    System.out.println("ByeBye!!!");
                    break;
                }
            }
        });
        t.start();
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t.interrupt();
        /*此方法可能会有两种情况:
            1)如果t线程是处在就绪状态,就是设置线程的标志位为true
            2)如果t线程处在阻塞状态,就会出发一个异常
          我们这里的代码每次都会sleep进入阻塞状态,所以会报异常
        */
    }

这里的 interrupt() 就充当了改变标志位的身份, 能够中断线程

但此处结果报了一个异常,因为此方法会面对两种情况:

  1. 如果t线程是处在就绪状态,就是设置线程的标志位为true
  2. 如果t线程处在阻塞状态,就会出发一个异常

因为每次都在阻塞,所以会报异常.

最后,报了异常后我们要注意及时break退出.

    

六. 线程等待

上面讲过join方法,此方法也可以给上参数,表示线程等待的时间.

public class TestDemo6 {
    public static void main1(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("hello thread");
                try{
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
        //join就是让main等待t执行完,此时main在阻塞状态
        //t.join();
        //但是总不能死等吧,所以join提供了另一个版本,可以设置一个最长等待时间
        t.join(10000);//超过10s,不等了
    }
}

  

谢谢你能看到这(ง •_•)ง 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

krack716x

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

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

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

打赏作者

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

抵扣说明:

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

余额充值