[Java EE] 多线程(一) :线程的创建与常用方法(上)

1. 认识线程

1.1 概念

1.1.1 什么是线程

⼀个线程就是⼀个"执⾏流".每个线程之间都可以按照顺序执⾏⾃⼰的代码.多个线程之间"同时"执⾏

着多份代码.

还是回到我们之前的银⾏的例⼦中。之前我们主要描述的是个⼈业务,即⼀个⼈完全处理⾃⼰的业务。我们进⼀步设想如下场景:

⼀家公司要去银⾏办理业务,既要进⾏财务转账,⼜要进⾏福利发放,还得进⾏缴社保。

如果只有张三⼀个会计就会忙不过来,耗费的时间特别⻓。为了让业务更快的办理好,张三⼜找来两位同事李四、王五⼀起来帮助他,三个⼈分别负责⼀个事情(可以理解为一个进程中包含着多个线程),分别申请⼀个号码进⾏排队,⾃此就有了三个执⾏流共同完成任务,但本质上他们都是为了办理⼀家公司的业务。

此时,我们就把这种情况称为多线程,将⼀个⼤任务分解成不同⼩任务,交给不同执⾏流就分别排队执⾏。其中李四、王五都是张三叫来的,所以张三⼀般被称为主线程(MainThread)。

1.1.2 为什么要有线程

首先,"并发编程"是"刚需".

  • 单核CPU的发展遇到了瓶颈.要想提高算例,就需要多核CPU.而并发编程能更充分利用多核CPU
    资源
    .可以把一个任务拆解为多个部分,分配给不同的CPU核心来完成.
  • 其次虽然多进程也可以实现并发编程,但是线程比进程更加轻量 .
    • 创建线程比创建进程快.
    • 销毁线程要比销毁进程快(上述两方面的原因都是因为每次创建进程都会分配系统资源,这种操作的开销比较大 ,而线程就可以对资源进程沿用)
    • 调度线程比调度进程快.(通过PCB中的线程调度属性来支持)

1.1.3 PCB与线程

严格意义上来说,一个PCB是描述一个线程的,而进程是若干个PCB合起来描述的.

  1. pid:每个线程都不同.(线程id)
  2. 内存指针:同一进程相同,线程之间不一样.
  3. 文件描述符表:同一进程相同,线程之间不一样.(由于同一进程调度的系统文件和使用的内存资源都是一样的,所以2,3一样)
  4. 线程调度属性:由于它是支持线程调度的一个属性,所以很明显不同线程之间一定不一样,他是每个线程独有的属性.
  5. tgid:同一进程中的线程之间都是一样的.(进程id)

我们拿一个"合租"的例子来说明上述问题

比如我们合租一间房子,客厅,卫生间,厨房等空间都是公共区域,相当于上述PCB中的内存指针和文件描述符表还有tgid,每个线程之间共享着内存空间和系统文件.每个卧室之间是每个人独有的空间,每个人的房间都不一样,就像上述的pid,线程调度属性.

1.1.4 线程与进程的区别(重点面试题,面试必考)

  • 进程包含线程,每个进程至少有一个进程存在,就是主线程.
  • 进程和进程之间不共享内存空间和文件资源,即它们的内存指针和文件描述符表不一样.而同一进程的线程之间共享内存空间和文件资源.
  • 进程是资源分配的基本单位(分配内存空间和文件资源),线程是执行调度的基本单位(创建线程调度属性).
  • 一个进程崩溃不会影响到其他进程,一个线程崩溃可能会影响其他进程,连同其他线程一起崩溃.

举例

下面有请我们的助教,蔡徐坤老师.
全民制作人们大家好,我是练习时长两年半的个人练习生cxk,喜欢Java,数据结构,数据库,music

  1. 坤坤要进一间房中吃只因,创建了一个带有一个主线程的进程:
  2. 由于房间中的只因实在太多了,有100只,于是便分配到两个房间来有两个坤坤吃:
  3. 但是由于在盖一间房(创建一个新的进程)的开销实在是太大了,所以我们让好多个坤坤(好多个线程)进同一个房间吃只因.(创建好多个线程),这样就会节省开销.
  4. 但是此时,有两个坤坤产生了冲突,它们在抢一只只因大腿,它们都想吃.
  5. 此时,旁边的坤坤来劝架(鸡哥算了算了),劝住了.大家继续吃只因.(进程继续运行)
  6. 如果没劝住,就直接掀桌(╯‵□′)╯︵┻━┻了,大家谁都别吃了,进程也就挂掉了.

1.2 创建多线程

注:下面在主方法中创建的线程属于主线程,在程序执行之后会自动启动线程.建议看完后面的1.3的一部分内容之后再来看这里.run()方法,start()方法,构造,sleep()方法都在后面.

  1. 通过继承Tread类,并在主线程创建线程对象.
java 复制代码
public class MyTread extends Thread{
    @Override
    public void run() {
        while (true){
            System.out.println("hello tread");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {//注意调用sleep方法需要抛出异常
                throw new RuntimeException(e);
            }
        }
    }

    public static void main(String[] args) {
        Thread thread = new MyTread();
        thread.start();
        while (true){
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
  1. 通过实现Runnable接口,并在主线程为Tread的构造方法传入这个类的引用来创建线程对象.
java 复制代码
public class MyTread1 implements Runnable{
    @Override
    public void run() {
        while (true){
            System.out.println("hello tread");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new MyTread());
        thread.start();
        while (true){
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
  1. 通过匿名内部类来创建线程对象.第一种是直接重写Tread接口中的方法,第二种是给Tread类的构造方法传入Runnable接口,并重写Runnable中的方法.
java 复制代码
public class MyTread2 {
    public static void main(String[] args) {
        Thread thread = new Thread(){
            @Override
            public void run() {
                while (true){
                    System.out.println("hello tread");
                    try {
                        Thread.sleep(1000);//Tread类中的一个static方法
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        };
        thread.start();
        while (true) {
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
java 复制代码
public class MyTread3 {
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    System.out.println("hello tread");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        });
        thread.start();
        while (true){
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
  1. 通过创建labda表达式,这是对run方法的一种平替版本.
java 复制代码
public class MyTread4 {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            while (true){
                System.out.println("hello tread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        thread.start();
        while (true){
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

2. Tread类及其常见方法

Thread类是JVM⽤来管理线程的⼀个类,换句话说,每个线程都有⼀个唯⼀的Thread对象与之关

联。⽤我们上⾯的例⼦来看,每个执⾏流,也需要有⼀个对象来描述,类似下图所⽰,⽽Thread类的对象就是⽤来描述⼀个线程执⾏流的,JVM会将这些Thread对象组织起来,⽤于线程调度,线程管理.

2.1 Tread的常见构造方法

方法 说明
Tread() 创建线程对象
Tread(Runnable target) 使用Runnable对象来创建线程
Tread(String name) 创建线程对象并命名
Tread(Runnable target,String name) 使用Runnable对象来创建线程并命名
java 复制代码
Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("这是我的名字");
Thread t4 = new Thread(new MyRunnable(), "这是我的名字");

2.2 Tread的常见属性

属性 获取方法
id getId()
名称 getName()
优先级 getPriority()
状态 getState()
是否是后台线程 isDaemon()
是否存活 isAlive()
是否被中断 isInterrupted()

拓展:把一个线程设置为后台线程的方法是线程引用.setDaemon(true).

  • id是线程的唯一标识,不同的线程不会重复
  • 名称可以通过jconsole看到.
  • 状态表示线程当前所处的⼀个情况,下面我们会进⼀步说明.
  • 优先级高的线程理论上来说更容易被调度到.
  • 关于后台线程,我们要多唠几句:
    首先什么是前台线程,这和我们平时所知道的前台不一样,这里指的前台线程是,前台线程运行不结束,Java程序永远不会结束,后台线程是,后台即使在运行,只要前台线程全部结束,也不可以阻止Java程序结束,当然后台线程的结束也不可以对Java程序的执行产生任何影响.(如gc线程,就是Java垃圾回收线程,是周期性持续执行的后台线程)

我们下面举一个代码的例子:

java 复制代码
public class Demo0 {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("tread");
        });
        thread.setDaemon(true);//把tread线程设置为后台线程
        thread.start();
        System.out.println("main is terminate");
    }
}

运行结果:

这里我们可以看到,只打印了最后main线程的一句话,由于在tread设置为了后台线程,而且在线程中sleep了1s,tread还没来得及反应,前台线程main线程就已经结束了,整个进程就结束了,后台的tread也不得不结束.

我们下面通过一个实际生活中的例子-----吃酒席来说明:

  1. 今天吃酒席,有马化腾,马云,还有一些小喽啰.

    由于马云和马化腾都是大老板,掌管者整个酒席的节奏,一桌酒席是否结束,由他们说了算.它们就是前台线程,掌管者整个进程.
  2. 马云和马化腾掌管者整个酒席的节奏,它们都说结束才可以结束,要是有一个没喝好,就不可以结束.

    酒席结束了,小喽啰不走也得走了.
  3. 突然有一个小喽啰实在喝不动了,提前溜了.但是酒席不可以结束,马爸爸和马爹爹还没有喝好,接着喝,可见后台进程的结束并不影响进程是否结束.
  • 是否存活,线程中的线程引用是否被gc回收,这里不可以理解为线程是否还在执行,两者有一定的区别.
  • 线程终端问题,后序介绍.

未完待续...

相关推荐
小信丶2 小时前
Spring Cloud Stream EnableBinding注解详解:定义、应用场景与示例代码
java·spring boot·后端·spring
无限进步_2 小时前
【C++】验证回文字符串:高效算法详解与优化
java·开发语言·c++·git·算法·github·visual studio
亚历克斯神2 小时前
Spring Cloud 2026 架构演进
java·spring·微服务
七夜zippoe2 小时前
Spring Cloud与Dubbo架构哲学对决
java·spring cloud·架构·dubbo·配置中心
海派程序猿2 小时前
Spring Cloud Config拉取配置过慢导致服务启动延迟的优化技巧
java
阿维的博客日记2 小时前
为什么不逃逸代表不需要锁,JIT会直接删掉锁
java
William Dawson2 小时前
CAS的底层实现
java
九英里路2 小时前
cpp容器——string模拟实现
java·前端·数据结构·c++·算法·容器·字符串
YDS8292 小时前
大营销平台 —— 抽奖前置规则过滤
java·spring boot·ddd
仍然.3 小时前
多线程---CAS,JUC组件和线程安全的集合类
java·开发语言