前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Thread介绍

Thread介绍

原创
作者头像
HLee
修改2021-10-18 09:07:22
6410
修改2021-10-18 09:07:22
举报
文章被收录于专栏:房东的猫房东的猫

简介

说到线程就不得不提与之相关的另一概念:进程,那么什么是进程?与线程有什么关系呢?简单来说一个运行着的应用程序就是一个进程,比如:我启动了自己手机上的酷猫音乐播放器,这就是一个进程,然后我随意点了一首歌曲进行播放,此时酷猫启动了一条线程进行音乐播放,听了一部分,我感觉歌曲还不错,于是我按下了下载按钮,此时酷猫又启动了一条线程进行音乐下载,现在酷猫同时进行着音乐播放和音乐下载,此时就出现了多线程,音乐播放线程与音乐下载线程并行运行,说到并行,你一定想到了并发吧,那并行与并发有什么区别呢?并行强调的是同一时刻,并发强调的是一段时间内。线程是进程的一个执行单元,一个进程中至少有一条线程,进程是资源分配的最小单位,线程是 CPU 调度的最小单位。

程序是静态的,进程是动态的。多进程是操作系统中多个程序同时执行。

如果程序是采用多线程技术编写的,那么运行在单核单线程上运行的话,是并发执行;那么运行在多核多线程上运行的话,是并行执行的。

进程

狭义:进程是正在运行的程序的实例(一个程序)。

广义:进程是一个具有一定独立功能的程序,关于某个集合的一次运行活动。

进程(程序段+数据段+进程控制块PCB+进程标识符PID)

  • 资源分配的基本单位
  • 独立运行的基本单位

目的:改善资源利用率以及提高系统吞吐量

线程

线程是操作系统能够进行运算调试的最小单位,它被包含在进程中,是进程中实际动作单位。一个线程指的是进程中的一个单一顺序的控制流,一个进程中可以并发多个线程,每个线程执行不同的任务。

  • 独立调度的基本单位

目的:减少程序并发执行是所付出的时空开销

线程是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器、一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。

Java默认有2个线程:main线程、GC线程。

代码语言:javascript
复制
并发(单核):多个线程操作同一个资源。
并行(多核):多个线程操作多个资源。

并发编程的本质:充分利用CPU的资源

多线程就是分时利用CPU,宏观上让所有线程一起执行 ,也叫并发。

生命周期

代码语言:javascript
复制
public enum State {    
    NEW, //新生
    RUNNABLE, //运行
    BLOCKED, //阻塞
    WAITING, //等待  死死等
    TIMED_WAITING, //超时等
    TERMINATED; //终止
}
  • 使用new创建线程对象后,系统并没有提供运行所需的资源
  • 使用start()方法启动线程后(RUNNING、RUNABLE)系统就分配资源(具备执行的资格),但是具体执行还得听后CPU调配
  • RUNABLE状态的线程只能进入RUNNING状态或者意外终止(不可进入BLOCKED状态)
  • BLOCKED状态可以进入RUNABLE状态(不可以进入RUNNING)
代码语言:javascript
复制
public class ThreadTest34 {

    public static void main(String[] args) throws Exception{
        Thread thread = new ThreadUser34();
        System.out.println("线程在Main方法中的状态1:" + thread.getState());
        Thread.sleep(1000);
        thread.start();
        Thread.sleep(1000);
        System.out.println("线程在Main方法中的状态2:" + thread.getState());
    }
}

class  ThreadUser34 extends Thread{

    public ThreadUser34() {
        System.out.println("构造方法的状态:" + Thread.currentThread().getState());
    }

    @Override
    public void run() {
        System.out.println("run方法中的状态:" + Thread.currentThread().getState());
    }
}

执行结果:
构造方法的状态:RUNNABLE
线程在Main方法中的状态1:NEW
run方法中的状态:RUNNABLE
线程在Main方法中的状态2:TERMINATED

创建线程

创建线程只有一种方式那就是构造Thread类,而实现线程的执行单元有两种方式:

  • 第一种是重新Thread的run方法
  • 第二种是实现Runnable接口的run方法,并将Runnable实例用作构造Thread的参数
  • 第三种使用FutureTask,在创建Thread对象的时候传进去

Thread负责线程本身相关的职责和控制,而Runnable则负责逻辑执行单元的部分。

代码语言:javascript
复制
Thread的run源码:

@Override
public void run() {
    //如果构造Thread时传入了Runnable,则会执行Runnable的run方法。
    if (target != null) {
        target.run();
    }
    //负责需要重新run方法
}

备注:线程的执行单元指的是run方法

继承Thread类创建

  • 线程的启动顺序并不是按照start的顺序启动的,而是随机启动的
代码语言:javascript
复制
@Slf4j
public class ThreadTest {

    public static void main(String[] args){

        log.info("主线程ID:" + Thread.currentThread().getId() + ", 线程名字:" +  Thread.currentThread().getName());

        Thread thread = new MyThread();
        thread.start();
    }
}

/**
 * 通过继承Thread来实现
 */
@Slf4j
class MyThread extends Thread{

    @Override
    public void run() {
        log.info("子线程ID: " + Thread.currentThread().getId() + ", 子线程名字: " +  Thread.currentThread().getName());
    }
}

执行结果:
20:06:38.901 [main] INFO com.java.master.线程.ThreadTest - 主线程ID:1, 线程名字:main
20:06:38.904 [Thread-0] INFO com.java.master.线程.MyThread - 子线程ID: 11, 子线程名字: Thread-0

实现Runnable接口创建

代码语言:javascript
复制
@Slf4j
public class ThreadTest2 {

    public static void main(String[] args){

        log.info("主线程ID:" + Thread.currentThread().getId() + ", 线程名字:" +  Thread.currentThread().getName());

        Thread thread = new Thread(new MyThread2());
        thread.start();
    }
}

@Slf4j
class MyThread2 implements Runnable{

    @Override
    public void run() {
        log.info("子线程ID: " + Thread.currentThread().getId() + ", 子线程名字: " +  Thread.currentThread().getName());
    }
}


执行结果:
20:13:48.236 [main] INFO com.java.master.线程.ThreadTest2 - 主线程ID:1, 线程名字:main
20:13:48.240 [Thread-0] INFO com.java.master.线程.MyThread2 - 子线程ID: 11, 子线程名字: Thread-0

注意:这种方式必须将Runnable作为Thread类的参数,然后通过Thread的start方法来创建一个新线程来执行该子任务

在Java中,这2种方式都可以用来创建线程去执行子任务,具体选择哪一种方式要看自己的需求。直接继承Thread类的话,可能比实现Runnable接口看起来更加简洁,但是由于Java只允许单继承,所以如果自定义类需要继承其他类,则只能选择实现Runnable接口。

事实上,查看Thread类的实现源代码会发现Thread类是实现了Runnable接口的。

FutureTask方式

代码语言:javascript
复制
public class CallableTest {

    public static void main(String[] args) throws Exception{
        Callable<Integer> callable = new CallableService();
        FutureTask<Integer> task = new FutureTask<>(callable);
        Thread thread = new Thread(task);
        thread.start();
        System.out.println("线程返回值: " + task.get());
    }
}

class CallableService implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        System.out.println(Thread.currentThread().getName() + "调用了callable方法");
        return (int)(Math.random() * 10);
    }
}

匿名内部类创建

代码语言:javascript
复制
public class ThreadTest3 {

    public static void main(String[] args) {

        //匿名内部类方式创建Thread
        new Thread() {
            @Override
            public void run() {
                enjoyMusic();
            }
        }.start();

        //Lambda方式1,等价于上边的匿名内部类创建方式
        new Thread(ThreadTest3::enjoyMusic).start();

        //Lambda方式2,等价于上边的匿名内部类创建方式
        new Thread(() -> {
            enjoyMusic();
        }).start();
    }

    public static void enjoyMusic() {

        for (; ;) {
            System.out.println("music music");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

执行结果:
music music
music music
music music

成员变量与线程安全

1. 不共享数据

每个线程都有各自的count变量,自己减少自己的count变量的值,这种情况就是不共享。

代码语言:javascript
复制
public class ThreadTest4 {

    public static void main(String[] args) {
        Thread thread1 = new MyThread4();
        Thread thread2 = new MyThread4();
        Thread thread3 = new MyThread4();

        thread1.start();
        thread2.start();
        thread3.start();
    }
}

class MyThread4 extends Thread {

    private int count = 5;

    @Override
    public void run() {
        while (count > 0) {
            count --;
            System.out.println(Thread.currentThread().getName() + " - " +count);
        }
    }
}

执行结果:
Thread-1 - 4
Thread-1 - 3
Thread-0 - 4
Thread-0 - 3
Thread-1 - 2
Thread-0 - 2
Thread-2 - 4
Thread-0 - 1
Thread-0 - 0
Thread-1 - 1
Thread-1 - 0
Thread-2 - 3
Thread-2 - 2
Thread-2 - 1
Thread-2 - 0

2. 共享变量

共享变量有概率出现不同线程使用相同的count的值,产生了“非线程安全”问题。

代码语言:javascript
复制
public class ThreadTest5 {

    public static void main(String[] args) {

        Thread thread = new MyThread5();

        Thread thread1 = new Thread(thread);
        Thread thread2 = new Thread(thread);
        Thread thread3 = new Thread(thread);

        thread1.start();
        thread2.start();
        thread3.start();
    }
}

class MyThread5 extends Thread {

    private int count = 5;

    @Override
    public void run() {
        count --;
        System.out.println(Thread.currentThread().getName() + " - " +count);
    }
}

执行结果:
Thread-2 - 3
Thread-1 - 3
Thread-3 - 2

线程分类

线程分为两种,用户线程和守护线程。其实守护线程和用户线程区别不大,可以理解为特殊的用户线程。特殊就特殊在如果程序中所有的用户线程都退出了,那么所有的守护线程就都会被杀死,很好理解,没有被守护的对象了,也不需要守护线程了。

在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程) 。用个比较通俗的比如,任何一个守护线程都是整个JVM中所有非守护线程的保姆。

所谓守护线程,是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分。因此,当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。反过来说,只要任何非守护线程还在运行,程序就不会终止。

只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。

Daemon的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是 GC (垃圾回收器),它就是一个很称职的守护者。

用户线程

默认创建出来的线程都是用户线程。

守护线程

线程是否为守护线程和他的父类有很大关系,如果父类是正常线程,则子类线程也是正常线程,反之亦然。

代码语言:javascript
复制
thread.setDaemon(true);

备注:必须在线程启动start()方法之前设置。
代码语言:javascript
复制
代码分析:

public static void main(String[] args){

    System.out.println("start");
    Thread thread = new Thread() {
        @Override
        public void run() {
            while (true) {
                try {
                    TimeUnit.SECONDS.sleep(1);
                }catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    };

//        thread.setDaemon(true);
    thread.start();
    System.out.println("finish");
}

这段代码里边存在两个线程(main,thread),当main线程即使生命周期结束,JVM进程还是不能退出,因为还有一个非守护线程thread在执行。
如果将thread.setDaemon(true)注释放开,则当main线程执行完毕之后,JVM也随之退出,当然thread也就退出了。

备注:守护线程具备自动结束生命周期得特性,而非守护线程则不具备这样的特点。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 简介
    • 进程
      • 线程
        • 生命周期
        • 创建线程
          • 继承Thread类创建
            • 实现Runnable接口创建
              • FutureTask方式
                • 匿名内部类创建
                  • 成员变量与线程安全
                    • 1. 不共享数据
                    • 2. 共享变量
                • 线程分类
                  • 用户线程
                    • 守护线程
                    相关产品与服务
                    腾讯云代码分析
                    腾讯云代码分析(内部代号CodeDog)是集众多代码分析工具的云原生、分布式、高性能的代码综合分析跟踪管理平台,其主要功能是持续跟踪分析代码,观测项目代码质量,支撑团队传承代码文化。
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档