线程的简单认识及实现线程的几种方式

线程(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密集型,理论上搞多少线程都可以

现实中的情况是介于两者之间 一般通过测试的方式来找到合适的线程数

多线程程序,还是有一些问题的
涉及到线程不安全的问题
一个线程如果抛出异常,并且没有很好地处理这个异常,那么就会终止整个进程.

代码部分:

	static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("hello world, 我是一个线程");
            while (true) {

            }
        }
    }

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

        while (true) {
        
        }
    }

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

为了进一步的观察当前确实是两个线程,可以借助第三方的工具来查看进程中的线程情况

JDK中内置了jconsole这样的程序,必须要想办法让进程结束别那么快,才能看到线程

写一个简单的代码来演示一下:
多线程并发执行和单线程之间的对比,针对一个整数进行大量的循环相加.

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

 private static long count = 100_0000_0000L;

    public static void main(String[] args) {
        // serial();
        concurrency();
    }
	//串行
    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");
    }
	//多线程
    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 ,{} 中是该类的具体代码,同时也会new出该类的一个实例

        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();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // t1 t2 和 main 线程之间都是并发执行的.
        // 调用了 t1.start 和 t2.start 之后, 两个新线程正在紧锣密鼓的进行计算过程中,
        // 此时主线程仍然会继续执行, 下面的 end 就随之被计算了.
        // 正确的做法应该是要保证 t1 和 t2 都计算完毕, 再来计算这个 end 的时间戳.
        long end = System.currentTimeMillis();
        System.out.println("time: " + (end - beg) + " ms");
    }

serial() 运行时间: 8075ms
concurrency () 运行时间: 3684ms

线程数量多了之后,效率是会提高,但是不一定会成倍提高.因为受到影响的因素可能很多

比如线程创建和销毁也是会消耗时间的,线程多了以后,线程的调度也会需要时间的.

单线程的代码编译器更好进行优化

创建线程的几种代码写法:

  1. 通过继承Thread类的方式来实现
  2. 通过匿名内部类的方式继承Thread类
  3. 显示创建一个类,实现Runnable接口,然后把这个Runnable的实例关联到Thread实例上
  4. 通过匿名内部类来实现Runnable接口
  5. 使用Lambda表达式

3

public class Test {
//    Runnable本身就描述了一段要执行的代码
    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();
    }
}

4

public static void main(String[] args) {
        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println("我是一个线程");
            }
        };
        Thread t = new Thread(r);
        t.start();
        
    }

5

public static void main(String[] args) {
        Thread t = new Thread(() -> {
            System.out.println("我是一个新线程");
        });
        t.start();
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值