Java 线程是如何创建的?创建原理详解

目录

1、Java线程的实现方式

2、Java线程创建原理详解

3、什么是协程?

4、Java线程调度机制


1、Java线程的实现方式

        (1)方式一:使用Thread类或继承Thread类

// 方式1:使用Thread
Thread thread = new Thread() {
      public void run() {
           //要执行的任务
           System.out.println("通过new Thread方式执行任务");
      }
};
thread.start();

        (2)方式二:实现 Runnable 接口

        配合Thread 把【线程】和【任务】(要执行的代码)分开:

// 方式2:使用Runnable
Runnable runnable = new Runnable() {
     @Override
     public void run() {
         System.out.println("通过Runnable方式执行任务");
     }
};
new Thread(runnable).start();

        (3)方式三:使用有返回值的 Callable

public class CallableTask implements Callable {
    @Override
    public Object call() throws Exception {
        return new Random().nextInt();
    }
}
//创建线程池
ExecutorService service = Executors.newFixedThreadPool(10);
//提交任务,并用 Future提交返回结果
Future<Integer> future = service.submit(new CallableTask());
System.out.println("通过Callable方式执行任务,返回结果:" + future.get());

        但本质上,Java创建线程只有一种方式,就是new Thread(),然后调用start()方法。不论是继承Thread重写run()方法,还是直接实现Runnable接口,本质都是一样的,因为Java在创建线程后都会去调用 Runnable.run() 方法执行任务。定义一个 Runnable(new Thread) 和把 Runnable 作为参数传递(接口方式)并没有什么区别。

        线程池方式,最终也是使用 DefaultThreadFactory 创建线程

2、Java线程创建原理详解

        对比三种执行任务方式的区别

public class ThreadExecuteTest {
    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "通过Runnable方式执行任务");
            }
        };
        // 三种调用方式的区别
        // 方式一:使用start() -> 操作系统创建线程
        // java Thread(映射) --> jvm JavaThread(JVM虚拟机) ----> os Thread(具体操作系统)
        new Thread(runnable, "线程1").start();
        // 方式一:new Thread(),然后直接调用run() -> 普通对象的方法调用
        new Thread(runnable, "线程2").run();
        // 方式三:直接调用 -> 普通对象的方法调用
        runnable.run();
    }
}

        执行结果

        从结果看出,只有通过 start() 调用任务,才会真正的创建线程,其他方式都是普通的对象方式调用。进入 start() 方法的源码,方法中调用了本地方法 start0() 

public synchronized void start() {
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
        group.add(this);
        boolean started = false;
        try {
            start0(); // 调用start0()
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
            }
        }
    }
// JVM方法start0()
private native void start0();

        对于底层 start0() 方法的执行流程,流程图如下: // 很重要,创建线程是操作系统进行的

线程创建和启动流程总结:

  1. 使用 new Thread() 定义一个线程对象,然后调用 start() 方法进行 Java 层面的线程启动
  2. 调用本地方法 start0(),然后会去调用JVM中的JVM_StartThread方法进行线程创建和启动
  3. 调用 new JavaThread() 会进入内核模式,由底层操作系统进行线程创建
  4. 底层操作系统新创建的线程为Initialized装态(初始化),调用 sync -> wait() 方法进行等待,直到被唤醒才会执行 thread -> run();
  5. 回到JVM层,会将Java中的Thread和JVM中的Thread进行绑定
  6. 调用Thread::start() 方法进行线程启动,并将线程状态设置成RUNNABLE,接着调用OS::start_thread() 根据不同的操作系统选择不同的线程启动方式
  7. 线程启动后状态设置成RUNNABLE,唤醒等待线程,执行 thread -> run() 方法
  8. JavaThread::run() 方法会回调 new Thread 中复写的 run() 方法

        所以,Java创建线程会涉及到内核模式的调用,非常消耗CPU资源,应该尽量让Java线程能够得到复用,减少不必要的重复创建

        Java线程属于内核级线程,JDK1.2 基于操作系统原生线程模型来实现。Sun JDK,它的Windows版本和Linux版本都使用一对一的线程模型实现,一条Java线程就映射到一条轻量级进程之中。

        内核级线程(Kernel Level Thread ,KLT):它们是依赖于内核的,即无论是用户进程中的线 程,还是系统进程中的线程,它们的创建、撤消、切换都由内核实现。

        用户级线程(User Level Thread,ULT):操作系统内核不知道应用线程的存在。

3、什么是协程?

        协程,英文Coroutines,是一种基于线程之上,但又比线程更加轻量级的存在,协程不是被操作系统内核所管理,而完全是由程序所控制(也就是在用户态执行),具有对内核来说不可见的特 性。这样带来的好处就是性能得到了很大的提升,不会像线程切换那样消耗资源。// go语言有协程机制

操作系统

        协程的特点在于是一个线程执行。那么和多线程比,协程有何优势?

  1. 线程的切换由操作系统调度,协程由用户自己进行调度,因此减少了上下文切换,提高了效率。
  2. 线程的默认stack大小是1M,而协程更轻量,接近1k。因此可以在相同的内存中开启更多的协程。
  3. 不需要多线程的锁机制:因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。

        注意: 协程适用于被阻塞的,且需要大量并发的场景(网络IO),不适合大量计算的场景

4、Java线程调度机制

        线程调度是指系统为线程分配处理器使用权的过程,主要调度方式分两种,分别是协同式线程调 度和抢占式线程调度

(1)协同式线程调度

        线程执行时间由线程本身来控制,线程把自己的工作执行完之后,要主动通知系统切换到另外一个线程上。最大好处是实现简单,且切换操作对线程自己是可知的,没啥线程同步问题。坏处是线程执行时间不可控制,如果一个线程有问题,可能一直阻塞在那里。

(2)抢占式线程调度

        每个线程将由系统来分配执行时间,线程的切换不由线程本身来决定(Java中, Thread.yield() 可以让出执行时间,但无法获取执行时间)。线程执行时间系统可控,也不会有一个线程导致整个进程阻塞// 避免线程阻塞

        Java线程调度是抢占式调度,如果希望系统能给某些线程多分配一些时间,给一些线程少分配一些时间,可以通过设置线程优先级来完成。

        Java语言一共10个级别的线程优先级(Thread.MIN_PRIORITY至 Thread.MAX_PRIORITY),在两线程同时处于ready状态时,优先级越高的线程越容易被系统选择执行。但优先级并不是很靠谱,因为Java线程是通过映射到系统的原生线程上来实现的,所以线程调度最终还是取决于操作系统

        一个简单的小测试:

public class SellTicketTest implements Runnable {
    /*** 车票 */
    private int ticket;

    private static Map<String, Integer> threadMap = new ConcurrentHashMap<>();

    public SellTicketTest() {
        this.ticket = 1000;
    }

    @Override
    public void run() {
        while (ticket > 0) {
            synchronized (this) {
                if (ticket > 0) {
                    try {
                        // 线程进入暂时的休眠
                        Thread.sleep(2);
                        String threadName = Thread.currentThread().getName();
                        if (threadMap.get(threadName) == null) {
                            threadMap.put(threadName, 1);
                        } else {
                            threadMap.put(threadName, threadMap.get(threadName) + 1);
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // 计算余票
                    ticket--;
                }
            }
            Thread.yield();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SellTicketTest ticketTest = new SellTicketTest();
        Thread thread1 = new Thread(ticketTest, "thread1");
        Thread thread2 = new Thread(ticketTest, "thread2");
        Thread thread3 = new Thread(ticketTest, "thread3");
        Thread thread4 = new Thread(ticketTest, "thread4");
        // priority优先级默认是5,最低1,最高10
        // 高优先级的线程要抢占低优先级线程CPU的执行权,但是只是从概率上讲高优先级的线程在高概率下被执行,
        // 并不意味着只有当高优先级的线程执行完之后,低优先级的线程才执行。
        thread1.setPriority(Thread.MAX_PRIORITY);
        thread2.setPriority(Thread.MIN_PRIORITY);
        thread3.setPriority(Thread.MIN_PRIORITY);
        thread4.setPriority(Thread.MIN_PRIORITY);
        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
        Thread.sleep(5000); // 保证程序执行完
        Integer sum = 0; // 统计总数
        for (Map.Entry<String, Integer> entry : threadMap.entrySet()) {
            System.out.println("线程名称:" + entry.getKey() + ",调度次数:" + entry.getValue());
            sum += entry.getValue();
        }
        System.out.println("总共次数:" + sum);
    }
}

        以上结果表明,Java优先级并不是很靠谱,如果多次执行,获得的结果也不同。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

swadian2008

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

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

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

打赏作者

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

抵扣说明:

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

余额充值