【Java操作系统】线程的介绍和创建方法

1.线程(thread)

如果把线程想象成是一个工厂,线程就是若干个流水线:

  1. 线程其实是包含在进程中的
  2. 一个进程中可能会有多个线程
  3. 每个线程都有一段自己要执行的逻辑(指令),每个线程都是一个独立的“执行流”
  4. 同一个进程中的很多线程之间,是共享了一些资源

所谓的“线程”可以理解成一种轻量级“进程”,也是一种实现并发编程的方式,创建一个线程比创建一个进程成本低,销毁一个线程,比销毁一个进程成本低。

成本低的原因是,新创建一个线程,不需要给这个线程分配很多新的资源,大部分资源都是和原来线程共享的,如果新创建一个进程,就需要给这个进程分配较多的资源

实际进行并打编程的时候美多线程方式要比多进程方式更常见,也效率更高。

同一个进程的多个线程之间共享的资源主要是两方面:

  1. 内存资源(但是两个不同进程之间的内存不能共享)
  2. 打开的文件

但是也有一些不是共享的资源:

  1. 上下文/状态/优先级/记账信息(每个线程要独立参与的 CPU 的调度)
  2. 内存中有一块特殊的区域——栈空间是每个线程要独立一份

进程是操作系统分配资源的最小单位,线程数操作系统进行执行调度得到最小单位。所谓的操作系统进行进程调度,本质上就是操作系统针对这个进程的若干个线程进行调度。

 

操作系统是如何管理线程的呢?

本质上和管理进程一样。

  • 先描述:用 PCB 描述
  • 再组织:用一个双向链表来组织

内核只认 PCB,一个线程和一个 PCB 对应,一个进程可能和多个 PCB 对应。

最开始的计算机是串行工作的,此时没有进程和线程的概念,后来计算机支持多任务并发执行了,在后来经常用多进程实现并发买不太好用,在后来,线程虽然比进程轻量,但是还是不够轻量(存在一个抢占式执行的问题),引入了“协成”的概念(轻量级线程)

 

一个进程中能搞多少个线程?

  1. CPU 的个数相关
  2. 和线程执行的任务类型相关——>1)CPU密集型:程序就一直在执行计算机任务;2)IO 密集型:程序没怎么进行计算,主要是进行输入输出操作

假设这个主机有 8 核 CPU,任务又完全用 CPU 计算,此时线程的数目大概是8个左右,如果任务完全 IO 密集型,理论上线程多少都可以(这是两种极端的情况),显示中的情况是要介于两者之间——>实践中一般需要通过 测试 的方式来找到合适的线程数

对于一个业务比较复杂的服务器来说,上面有几十个线程也是很常见的现象

 

 

2.创建线程

时间戳定义:

以 1970年1月1日0时0分0秒为基准时刻,计算当前时刻和基准时刻之间的秒数、毫秒数、微秒数的时间差

创建线程的几种写法:

  1. 通过显式继承 Thread 类的方式来实现
  2. 也可以通过匿名内部类的方式来继承 Thread
  3. 显式创建一个类,实现一个 Runnable 接口,然后把这个 Runnable 的实例关联到 Thread 实例上
  4. 通过匿名内部类实现 Runnable 接口
  5. 使用 lambda 表达式来指定线程执行的内容

这几种创建线程的方式,没有本质上的区别,核心都是依靠 Thread 类。只不过指定线程执行的任务的方式有所差异

细节的上的区别:

  • 通过 Runnable  、 lambda 的方式来创建线程和继承 Thread 类相比,代码耦合性更小一点,在写 Runnable 或者lambda 的手 run中没有涉及到任何的 Thread 相关内容,这就意味着很容易把这个逻辑从多线程中剥离出来,去搭配其他的并发编程的方式来执行,当然也可以很容易改成不并发的方式执行

 

2.1 继承 Thread 方法创建线程

public class Test0804 {
    static class  Mythread extends Thread {
        @Override
        public void run() {
            System.out.println("Hello ,I am a thread!");
        }
    }

    /**
     * 创建线程需要使用 Thread 类,来创建一个 Thread 实例
     * 另一方面还需要给这个线程指定要执行哪些指令、代码
     * 指定指令的方式有很多种,此处直接继承 Thread 类
     * 重写 Thread 类中的 run 方法
     * 【注意】放 Thread 对象被创建出来的时候,内核中并没有随之产生一个线程(PCB)
     * 需要调用 start 方法
     * @param args
     */
    public static void main(String[] args) {
        Thread t = new Mythread();
        //执行这个 start 方法才是真正创建出了一个线程
        //此时内核中才随之出现了一个 PCB ,这个 PCB 就会让 CPU 来执行该线程的代码
        t.start();
    }
}

上面的代码涉及到两个线程——MyThread 创建出来的线程,main 方法对应的主线程

jconsole

为了进一步的观察当前确实是两个线程,可以借助第三方工具来查看该线程的情况,JDK 中内置了一个 jconsole 这样的程序 ,jconsole 在 jdk 目录下的 bin 目录下
具体操作见 如何用 JConsole 查看线程

 

从折线图可以看到我们现在大概有 13 个线程,在这个折线图线面可以看到具体的线程

 

这些线程就都是这个 Java 进程中包含的线程

 

点击这些线程就可以看到具体的信息

图中的 RUNNABLE 相当于就绪状态,堆栈跟踪部分就是线程的调用栈

 

2.2 匿名内部类方法创建线程

创建了一个没有名字的类,只知道这个类继承自 Thread,“{}” 中是这个类的具体diamante,同时也会 new 出来这个类的实例

 

演示一下多线程并发执行和单线程得到区别

package com.Test0805;

/**
 * Create with IntelliJ IDEA
 * Description:串行和并发的执行效果展示
 * User:Zyt
 * Date:2020-08-05
 */
public class ThreadDemo1 {
    private static long count = 100_0000_0000L;
    public static void main(String[] args) {
        //serial();//串行
        //concurrency();//并发
    }

    private static void concurrency() {
        long beg = System.currentTimeMillis();
        Thread t1 = new Thread(){
            @Override
            public void run() {
                int a = 0;
                for (long i = 0; i < count; i++) {
                    a++;
                }
            }
        };
        Thread t2 = new Thread(){
            @Override
            public void run() {
                int b = 0;
                for (long i = 0; i < count; i++) {
                    b++;
                }
            }
        };
        t1.start();
        t2.start();
        try {
            //线程等待,让主线程等待 t1 和 t2 执行结束,然后再继续往下运行
            t1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //t1 t2 和主线程都是并发执行的
        //调用了额t1.start()和 t1.start() 之后,两个线程正在计算过程中
        //此时主线程仍然会继续执行,下面的 end 就随之被计算了
        //正确做法保证 t1 和 t2 都计算完毕再来计算 end 的时间戳
        long end = System.currentTimeMillis();//计算结束时间戳
        System.out.println("time:" + (end - beg) + "ms");
    }

    private static void serial() {
        long beg = System.currentTimeMillis();//计算开始时间戳
        int a = 0;
        for (long i = 0; i < count; i++) {
            a++;
        }
        int b = 0;
        for (long i = 0; i < count ; i++) {
            b++;
        }
        long end = System.currentTimeMillis();//计算结束时间戳
        System.out.println("time:" + (end - beg) + "ms");
    }
}

 

时间戳,以 ms 为单位

 

串行运行效果

并发执行效果

可以看出并发执行时间要比串行时间短

2.3 显式创建一个类,实现 Runnable接口,然后把这个 Runnable 的实例关联到 Thread 上

public class ThreadDemo3 {
    /**
     * 显式创建一个类,实现 Runnable接口,然后把这个 Runnable 的实例关联到 Thread 上
     * Runnable 本质上就是描述了一段要执行的任务代码是什么
     * @param
     */
    static class MyRunnable implements Runnable{
        @Override
        public void run() {
            System.out.println("我是一个新线程");
        }
    }

    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable());
        t.start();
    }
}

执行效果

 

2.4 用匿名内部类来实现 Runnable 接口

public class ThreadDemo4 {
    /**
     *
     * @param args
     */
    public static void main(String[] args) {
        Runnable runnable =  new Runnable() {
            @Override
            public void run() {
                System.out.println("我是一个新线程2!");
            }
        };
        Thread t= new Thread(runnable);
        t.start();
    }
}

执行效果

 

2.5 使用 lambda 表达式来指定线程执行的内容

public class ThreadDemo5 {
    public static void main(String[] args) {
        Thread t= new Thread(() -> {
            System.out.println("我是一个新线程3");
        });
        t.start();
    }
}

 

执行效果

 

Thread 的 run 和 start 之间的区别?

答:

run 只是一个普通的方法调用,没有创建新线程,输出语句是在原线程中执行的,而 start 是要创建一个新线程,由新的线程来执行输出

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值