JAVA线程的创建和使用

目录

第一个线程程序

创建线程

 四种创建方式

继承Thread类

实现Runnable接口

Thread的常用方法

构造方法

Thread的核心属性 

 线程的启动

获取当前正在执行的线程对象

线程的中断

等待其他线程

 休眠当前线程

多线程的作用 


第一个线程程序

  • 我们知道一个类的启动是一个进程
  • 一个类中的主方法是一个进程的主线程,所有的调用都是从主方法开始的,所有的任务都是从主方法中进行
  • 用thread类来创建线程,java.lang.thread,都是通过它来启动一个新的线程
package thread;

import java.util.Random;

public class FirstThreadDemo {
    private static class MyThread extends Thread{
        @Override
        public void run() {
            Random random=new Random();
            while (true){
                System.out.println(Thread.currentThread().getName());
                try {
                    Thread.sleep(random.nextInt(10));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        MyThread m1=new MyThread();
        MyThread m2=new MyThread();
        MyThread m3=new MyThread();
        //启动三个线程
        m1.start();
        m2.start();
        m3.start();
        Random random=new Random();
        while (true){
            System.out.println(Thread.currentThread().getName());
            try {
                Thread.sleep(random.nextInt(10));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

 

 其线程的执行顺序

  • 线程的执行顺序不是像单线程那样按照顺序执行的,其几个线程是“同时”进行的,这里的同时表示的是在宏观上是并行的,在微观上是并发执行

思考题

jconsole 查看当前JVM的内部线程情况

 

创建线程

 四种创建方式

  • 继承Thread类,覆写run方法(线程的核心工资任务方法)
  • 覆写Runnable接口,覆写run方法
  • 覆写Callable接口,覆写call方法
  • 使用线程池创建线程

继承Thread类

  • 一个子类继承Thread类
  • 覆写run方法
  • 产生这个子类对象,然后调用start方法启动线程

 可以看到相同的代码输出的顺序不一样,因为start启动线程,是由JVM产生操作系统的线程并启动,到底什么时候真正启动,我们是不可见的,也没法控制,而且线程的执行也是并行的

匿名内部类写法

 Lambda写法

不能用,因为Thread不是函数式接口

实现Runnable接口

  • 先实现Runnable接口
  • 覆写run方法
  • 先创建一个子类对象,然后创建一个Thread对象,接收这个子类对象

推荐第二种方法,因为实现Runnable接口更加灵活,子类还能实现其他的接口,继承别的类

匿名内部类写法

 Lambda写法

  • Lambda只能用来实现函数式接口,只有一个抽象方法的接口 

实现Callable接口

  • 先实现Callable接口
  • 覆写核心方法call方法
  • 创建相应的FutureTask类来接收Callable的返回值
  • 将FutureTask的对象传入Thread类的对象
创建线程计算 1 + 2 + 3 + ... + 1000, 使用 Callable 版本

  •  Callable实现接口的线程就是带有返回值,返回值要用FutureTask类的对象来接收
  • Callable Runnable 相对 , 都是描述一个 " 任务 ". Callable 描述的是带有返回值的任务 ,
    Runnable 描述的是不带返回值的任务
  • Callable 通常需要搭配 FutureTask 来使用 . FutureTask 用来保存 Callable 的返回结果 . 因为 Callable 往往是在另一个线程中执行的 , 啥时候执行完并不确定
     

    runnable 和 callable 有什么区别
    相同点:

    都是接口 都可以编写多线程程序 都采用Thread.start()启动线程

    主要区别:

    • Runnable 接口 run 方法无返回值(直接用Thread对象来接收这个Runnable接口的对象);
    • Callable 接口 call 方法有返回值,是个泛型,需要用FutureTask对象配合可以用来获取异步执行的结果(用FutureTask对象来接收Callable接口的,然后用Thread对象来接收FutureTask的对象)
    • Runnable 接口 run 方法只能抛出运行时异常,且无法捕获处理;Callable 接口 call 方法允许抛出异常,可以获取异常信息 注:Callalbe接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。

    什么是 FutureTask

    • FutureTask 表示一个异步运算的任务。FutureTask 里面可以传入一个 Callable 的具体实现类的对象,可以对这个异步运算的任务的结果进行等待获取、判断是否已经完成、取消任务等操作。只有当运算,完成的时候结果才能取回,如果运算尚未完成 get 方法将会阻塞。
     

    线程的 run()和 start()有什么区别(为什么启动线程调用的是start,而不是run)

    • start() 方法用于启动线程,run() 方法执行线程的运行时代码,覆写 run 方法是提供给线程要做的事情的指令清单(知道我们这个线程要干什么)。run() 可以重复调用,而 start()只能调用一次,多次调用会抛出异常。
    • new 一个 Thread,线程进入了新建状态。调用 start() 方法,会启动一个线程并使线程进入了就绪状态,当分配到CPU资源后就可以开始运行了(运行态)。 start() 会执行线程的相应准备工作,然后自动执行run() 方法的内容,真正实现了多线程运行。调用start()方法无需等待run方法体代码执行完毕,可以直接继续执行其他的代码; 
    • run()方法是在本线程里的,只是线程里的一个函数,而不是多线程的。 如果直接调用run(),其实就相当于是调用了一个普通函数而已,直接用run()方法必须等待run()方法执行完毕才能执行下面的代码,所以执行路径还是只有一条,根本就没有线程的特征,
    线程类的构造方法、静态块是被哪个线程调用的
    请记住:线程类的构造方法、静态块是被 new这个线程类所在的线程所调用的,而 run 方法里面的代码才是被线程自身所调用的。
    如果说上面的说法让你感到困惑,那么我举个例子,main 函数中 new 了 Thread2,那么:
    (1)Thread2 的构造方法、静态块是 main 线程调用的,Thread2 的 run()方法是Thread2 自己调用的

     

Thread的常用方法

构造方法

Thread的核心属性 

  • 每个线程都有自己唯一的ID
  • 优先级越高的线程是越有可能被CPU优先执行,我们JAVA只是建议优先级高的线程被执行,到底执行不执行,由操作系统说的算
  • JVM会在一个进程所有的非后台进程结束后,才会结束运行
  • 是否存活,就是为run方法是否运行结束

 线程的启动

之前我们已经看到了如何通过覆写 run 方法创建一个线程对象,但线程对象被创建出来并不意味着线程 就开始运行了。
  • 覆写 run 方法是提供给线程要做的事情的指令清单
  • 线程对象可以认为是把 李四、王五叫过来了
  • 而调用 start() 方法,就是喊一声:行动起来!,线程才真正独立去执行了
  • 调用 start 方法, 才真的在操作系统的底层创建出一个线程.
  • 无论是继承Thread类还是实现Runnable接口,最终启动还是使用Thread的start方法,Thread类就是JVM用来描述管理线程的类,每个线程都对应唯一一个Thread对象

获取当前正在执行的线程对象

线程的中断

定义:中断一个正在运行的线程,(run方法还没有执行结束),普通线程会在run方法执行结束后自动停止,我们的中断其实就是更改线程的状态,想让线程终止,只有run方法执行完毕,就自然终止了

两种中断方式
  • 通过共享变量进行中断
  • 使用Thread.interrupt()静态方法进行中断

共享变量进行中断

package Lambda;

public class ThreadInterrupted {
    private static class MyThread implements Runnable{
        volatile boolean isQuit=false;//共享变量
        @Override
        public void run() {
            while (!isQuit){
                System.out.println(Thread.currentThread().getName()+"我正在工作,别打扰我");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName()+"被中断了");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyThread m1=new MyThread();
        Thread t1=new Thread(m1,"帅哥线程");
        System.out.println("帅哥开始工作");
        t1.start();
        Thread.sleep(3000);
        System.out.println("帅哥工作结束了");
        m1.isQuit=true;
    }

}

  •  因为线程的并发执行,可能每次的执行结果都不一样
  • Thread的静态方法在那个线程调用,就生效在那个线程
  • 共享变量用volatile,volatile修饰的作用后面讲

使用interrupted静态方法中断或者Thread对象的成员方法isInterrupted

  • 原理:当前Thread类中特别设置了一个属性,当前线程是否被中断的属性,如果为true,表示当前是中断状态,为false说明不是中断状态,调用线程对象.interrupt方法就会讲线程对象的状态设置为中断状态(true)

线程收到interrupt内置中断通知的两种处理方法

  • 当线程调用sleep/wait/join等方法处于阻塞状态的时候,收到thread.interrupt(),就会抛出一个中断异常,InterruptedException,当抛出这个异常的时候(无论是使用那种判断方式),当前线程的中断状态会被清除(也就是Thread类中那个特别设置的中断属性)
  • 当没有调用以上三种方法,处在正常运行状态,收到中断通知thread.interrupt(),Thread.interrupted会判断当前线程是否被中断,若为true,清除中断标志变为false,线程对象.isInetrrupted()判断线程对象是否为中断状态,若状态为true,不会清除中断标志,保持为true

isinterrupetd成员方法和interrupted类方法的区别(当不是阻塞状态)

 当子线程处于阻塞状态对于中断信号的处理

package Lambda;

public class ThreadInterruptedByMethod {
    private static class MyThread implements Runnable{

        @Override
        public void run() {
            while (!Thread.currentThread().isInterrupted()){
                System.out.println(Thread.currentThread().getName()+"我正在工作");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    System.out.println("我休息一下");
                    break;
                }
            }
            System.out.println(Thread.currentThread().getName()+"到点了,我要下班了");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyThread m1=new MyThread();
        Thread t1=new Thread(m1,"帅哥");
        System.out.println("帅哥准备开始工作");
        t1.start();
        Thread.sleep(5*1000);
        t1.interrupt();//中断t1线程

    }
}

如何停止一个正在运行的线程?

  • 使用退出标志(共享变量 volatile实现),使线程正常退出,也就是当run方法完成后线程终止。
  • 使用stop方法强行终止,但是不推荐这个方法,因为stop和suspend及resume一样都是过期作废的方法
  • 使用interrupt方法中断线程。

等待其他线程

有时,我们需要等待一个线程完成它的工作后,才能进行自己的下一步工作。例如,张三只有等李四转 账成功,才决定是否存钱,这时我们需要一个方法明确等待线程的结束。
  • 我们用线程对象.join()成员方法来实现,在哪个线程调用别的线程对象的join方法,意思就是这个线程要等待另一个线程执行完毕再能继续执行本线程

  • 第一个就是死等,痴汉属性
  • 第二个是有理性的等,最多等你多少毫秒,如果执行完最后,执行不完,我也不等你了 
package Lambda;

public class ThreadJoin {
    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    System.out.println(Thread.currentThread().getName()+"正在学习JavaSE阶段");
                    try {
                        Thread.sleep(1000);//表示此进程休息一秒
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        },"刘颂成");
        Thread t2=new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    System.out.println(Thread.currentThread().getName()+"正在学习数据结构阶段");
                    try {
                        Thread.sleep(1000);//表示此进程休息一秒
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        },"进化的刘颂成");
        System.out.println("先学习JavaSE");
        t1.start();
        t1.join();//此时走到这里,t1线程已经执行,main主线程必须先执行完t1子线程,再去执行本线程或者其他线程
        t2.start();
        t2.join();此时走到这里,t2线程已经执行,main主线程必须先执行完t2子线程,再去执行本线程或者其他线程
        System.out.println("开始学习JavaEE");
    }
}

去除两个join的执行过程

 休眠当前线程

  • 该方法通过Thread这个类调用,意思就是在哪个线程的内部调用,那么就休眠哪个线程 

常用方法

  • sleep休眠线程,yeild让出CPU,join等待线程
  • interrtue 中断线程和interrtued来判断是否中断
  • 获得各种属性,getID,getName,或者设置属性,setID
  • start启动线程
  • currentThread方法获得当前运行的线程

多线程的作用 

比较一下多线程和顺序执行的速度差异,比如20个亿数字的连续累加

package Lambda;

public class ThreadNB {
    private static final long count=10_0000_0000;
    public static void main(String[] args) throws InterruptedException {
        serial();
        concurrent();
    }
    public static void serial(){
        long start=System.nanoTime();
        long a=0;
        for (int i = 0; i < count; i++) {
            a++;
        }
        long b=0;
        for (int i = 0; i < count; i++) {
            b++;
        }
        long end=System.nanoTime();
        double allTime=(end-start)*1.0/1000/1000;
        System.out.println("串行执行所用的时间"+allTime+"ms");
    }
    public static void concurrent() throws InterruptedException {
        //并行实现20亿的累加
        long start=System.nanoTime();
        Thread thread1=new Thread(()->{

            long a=0;
            for (int i = 0; i < count; i++) {
                a++;
            }
        });
        thread1.start();//子线程进行十亿次累加
        //主线程也进行10亿次累加
        long b=0;
        for (int i = 0; i < count; i++) {
            b++;
        }
        // 等待子线程执行结束,主线程和子线程的加法操作都完成
        // 等待子线程thread执行结束才能执行下面代码
        thread1.join();//限制子线程执行完毕,才能运行下面的代码
        long end=System.nanoTime();
        double allTime=(end-start)*1.0/1000/1000;
        System.out.println("并行耗费的时间为"+allTime+"ms");
    }
}

  • 理论上并发的执行速度应该是顺序执行的一倍
  • 多线程的最大应用场景就是把一个大任务拆分为多个子任务(交给子线程),多个子线程并发执行,提高系统的处理效率,比如12306系统就是一个多线程程序,我们每个人其实都是一个线程,我们多个人可以同时登录系统买票,付款操作是一个非常耗时的操作,如果不是多线程,每个人买票就得像排队买票一样,依次进行,非常慢,有多线程(就可以趁着比如调整付款页面的时间去处理别人买票的操作,类似于有些任务场景需要 "等待 IO", 为了让等待 IO 的时间能够去做一些其他的工作, 也需要用到并发编程)

为什么使用多线程(并发编程)

  • 提升多核CPU的利用率:一般来说一台主机上的会有多个CPU核心,我们可以创建多个线程,理论上讲操作系统可以将多个线程分配给不同的CPU去执行,每个CPU执行一个线程,这样就提高了CPU的使用效率,如果使用单线程就只能有一个CPU核心被使用。
  • 比如当我们在网上购物时,为了提升响应速度,需要拆分,减库存,生成订单等等这些操作,就可以进行拆分利用多线程的技术完成。面对复杂业务模型,并行程序会比串行程序更适应业务需求,而并发编程更能吻合这种业务拆分 。

简单来说就是:

  • 充分利用多核CPU的计算能力;
  • 方便进行业务拆分,提升应用性能

并发编程有什么缺点
并发编程的目的就是为了能提高程序的执行效率,提高程序运行速度,但是并发编程并不总是能提高程序运行速度的,而且并发编程可能会遇到很多问题,比如:内存泄漏上下文切换线程安全、死锁等问题
 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

库里不会投三分

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

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

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

打赏作者

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

抵扣说明:

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

余额充值