Java多线程专题之进阶学习Thread(含源码分析)

简介: Java多线程专题之进阶学习Thread(含源码分析)

前言

大家好,一直以来我都本着用最通俗的话理解核心的知识点, 我认为所有的难点都离不开 基础知识 的铺垫。目前正在出一个Java多线程专题长期系列教程,从入门到进阶, 篇幅会较多, 喜欢的话,给个关注❤️ ~


适合人群

  • 有一定的Java基础
  • 想学习或了解多线程开发
  • 想提高自己的同学

大佬可以绕过 ~


背景

之前给大家讲了一些框架的使用,这些都属于业务层面的东西,你需要熟练掌握它并在项目中会运用它即可,但这些对自身技术的积累是远远不够的,如果你想要提高自己,对于语言本身你需要花更多的时间去挖掘而不是局限于框架的使用,所以之前为什么跟大家一直强调基础的重要性,框架可以千变万化,层出不穷,但是基础它是不变的,不管是学java还是前端或者是其它语言, 这一点大家还是需要认清的。


接下来的几期会专门讲多线程这一块,篇幅会较多,耐心看完你一定会有收获~


情景回顾

上期带大家学习了什么是入门学习了Thread类和Runable接口,本期带大家进阶学习Thread的用法,我们一起来看一下吧~


Thread类的常用方法

在了解之前,我们先看下它的初始化方法


构造函数

  • 无参数
public Thread() {
      init(null, null, "Thread-" + nextThreadNum(), 0);
  }
复制代码


  • 有参数
public Thread(Runnable target) {
      init(null, target, "Thread-" + nextThreadNum(), 0);
  }
  Thread(Runnable target, AccessControlContext acc) {
      init(null, target, "Thread-" + nextThreadNum(), 0, acc, false);
  }
   .... 
复制代码


其实我们从源码可以发现,构造函数内部,都调用了初始化方法init

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
                        ...
}
复制代码


其实要给大家讲的是它这几个参数的意思,可以了解一下

  • g  线程组,指定这个线程是在哪个线程组下
  • target  target:指定要执行的任务
  • name 线程的名字,多个线程的名字是可以重复的
  • acc  用于初始化私有变量inheritedAccessControlContext
  • inheritThreadLocals  可继承的ThreadLocal


这个大家了解下就行, 下面我们看看常用的方法


currentThread

  • currentThread():native静态方法,返回对当前正在执行的线程对象的引用;
public static native Thread currentThread();
复制代码


public static void main(String[] args) {
        Thread t = new Thread(() -> {
            System.out.println("hello");
        });
        System.out.println(t.currentThread());
        t.start();
        System.out.println(t.currentThread());
}
复制代码


输出

Thread[main,5,main]
Thread[main,5,main]
hello
复制代码


start

  • start():开始执行线程的方法,java虚拟机会调用线程内的run()方法;
public synchronized void start() {
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
        group.add(this);
        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
            }
        }
    }
复制代码

同时会加入线程组,这里执行的start0();是一个native方法


yield

  • yield() 指的是当前线程愿意让出对当前处理器的占用,这个怎么理解呢,看一个例子
Thread t = new Thread(() -> {
    Thread.yield();
    System.out.println("hello1");
});
Thread t1 = new Thread(() -> {
    System.out.println("hello2");
});
Thread t2= new Thread(() -> {
    System.out.println("hello3");
});
t.start();
t1.start();
t2.start();
复制代码


实际输出:

hello2
hello3
hello1
复制代码


可以看到t调用了 yield方法,最后输出,我们大体可以猜到,线程t让步,线程t1先抢到cpu实际开始执行所以,hello2先输出


sleep

  • sleep():native静态方法,使当前线程睡眠一段时间,单位是 ms, 类似js的setTimeout,但sleep非异步,会阻塞当前线程
public static native void sleep(long millis) throws InterruptedException;
复制代码


其实还有一个非native的方法,但是内部调的还是native, 可以用于指定时间范围内的休眠

public static void sleep(long millis, int nanos)
throws InterruptedException {
    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }
    if (nanos < 0 || nanos > 999999) {
        throw new IllegalArgumentException(
                            "nanosecond timeout value out of range");
    }
    if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
        millis++;
    }
    sleep(millis);
}
复制代码

在很久之前的网站,有些老板会使用骚操作,开会员少休眠几秒,不开会员响应给你慢点


join

  • join():使当前线程等待另一个线程执行完毕之后再继续执行,内部调用的是Object类的wait方法实现的;这个需要大家好好理解
public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            System.out.println("hello1");
        });
        t.join();
        Thread t1 = new Thread(() -> {
            System.out.println("hello2");
        });
        Thread t2= new Thread(() -> {
            System.out.println("hello3");
        });
        t.start();
        t1.start();
        t2.start();
    }
复制代码


这里的join是一个实例方法,我们看下时间输出

hello2
hello1
hello3
复制代码


发现t线程在t1线程后执行, 跟我们之前讲的 yield还不太一样, 虽然也有让步的意思,但是让完就执行了,就是说,等待另一个线程也就是t1执行完再执行, 而yield是让出cpu时间片, 那我t2也让一下,是不是t3就先执行了,是这样吗❓我们来看一下

Thread t = new Thread(() -> {
    System.out.println("hello1");
});
t.join();
Thread t1 = new Thread(() -> {
    System.out.println("hello2");
});
t1.join();
Thread t2= new Thread(() -> {
    System.out.println("hello3");
});
t.start();
t1.start();
t2.start();
复制代码


实际输出

hello2
hello1
hello3
复制代码


发现结果还是没变化,这是怎么回事呢?t1join之后,应该是t3先执行才对,想知道原因还得看它的实现,我们看下源码

public final void join() throws InterruptedException {
      join(0);
  }
复制代码


发现内部是调了一个join方法

public final synchronized void join(long millis)
throws InterruptedException {
    long base = System.currentTimeMillis();
    long now = 0;
    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }
    if (millis == 0) {
        while (isAlive()) {
            wait(0);
        }
    } else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}
复制代码


这个方法加了一个synchronized 同步锁, synchronized在同一时刻只允许一个线程执行,如果有其它线程执行,需要等待当前线程执行完毕后再执行,具体的用法,后边给大家讲的时候讲。


这个方法可以传入指定时间,就是说等待n毫秒让该线程终止,如果为millis为0,会先判断线程是否处于存活状态,存在就wait(0)等待0毫秒。如果millis大于0,同样会先判断线程是否处于存活状态,并延迟deplay,当delay小于等于0跳出循环,其实循环到这,也相当于等待millis时间了,因为这个过程需要一直判断线程的状态

isAlive()方法是一个native方法

public final native boolean isAlive();
复制代码


如果线程已启动且尚未死亡,则该线程处于活动状态。如果该线程还活着,则为true ;否则false 。


wait 也是一个native方法:

public final native void wait(long timeout) throws InterruptedException;
复制代码


使当前线程等待,直到另一个线程为此对象调用notify()方法或notifyAll()方法,或者经过了指定的时间量。 timeout – 等待的最长时间(以毫秒为单位)。这里其实涉及到线程状态切换的知识了,这个我们放到后边讲


结束语

本期到这里就结束了, 总结一下,本节主要讲了Thread类的常用方法,建议大家可以自己多试试~


下期预告

有时候,我们需要异步获取数据,我们之前给大家讲的都是线程执行完就结束了,那么我想获去结果怎么实现呢❓ 下期就带大家学习Callable、Future与FutureTask。关注公众号加群,一起学习进步。关注我,不迷路, 下期不见不散 ~

相关文章
|
1天前
|
JavaScript Java 测试技术
基于Java的精品课程在线学习系统的设计与实现(源码+lw+部署文档+讲解等)
基于Java的精品课程在线学习系统的设计与实现(源码+lw+部署文档+讲解等)
15 1
|
1天前
|
JavaScript Java 测试技术
基于Java的中文学习系统的设计与实现(源码+lw+部署文档+讲解等)
基于Java的中文学习系统的设计与实现(源码+lw+部署文档+讲解等)
14 0
|
1天前
|
安全 Java
java多线程(一)(火车售票)
java多线程(一)(火车售票)
|
1天前
|
安全 Java 调度
Java并发编程:深入理解线程与锁
【4月更文挑战第18天】本文探讨了Java中的线程和锁机制,包括线程的创建(通过Thread类、Runnable接口或Callable/Future)及其生命周期。Java提供多种锁机制,如`synchronized`关键字、ReentrantLock和ReadWriteLock,以确保并发访问共享资源的安全。此外,文章还介绍了高级并发工具,如Semaphore(控制并发线程数)、CountDownLatch(线程间等待)和CyclicBarrier(同步多个线程)。掌握这些知识对于编写高效、正确的并发程序至关重要。
|
2天前
|
安全 Java 程序员
Java中的多线程并发编程实践
【4月更文挑战第18天】在现代软件开发中,为了提高程序性能和响应速度,经常需要利用多线程技术来实现并发执行。本文将深入探讨Java语言中的多线程机制,包括线程的创建、启动、同步以及线程池的使用等关键技术点。我们将通过具体代码实例,分析多线程编程的优势与挑战,并提出一系列优化策略来确保多线程环境下的程序稳定性和性能。
|
2天前
|
缓存 分布式计算 监控
Java并发编程:深入理解线程池
【4月更文挑战第17天】在Java并发编程中,线程池是一种非常重要的技术,它可以有效地管理和控制线程的执行,提高系统的性能和稳定性。本文将深入探讨Java线程池的工作原理,使用方法以及在实际开发中的应用场景,帮助读者更好地理解和使用Java线程池。
|
2天前
|
存储 安全 Java
Java中的容器,线程安全和线程不安全
Java中的容器,线程安全和线程不安全
10 1
|
3天前
|
Java 开发者
Java中多线程并发控制的实现与优化
【4月更文挑战第17天】 在现代软件开发中,多线程编程已成为提升应用性能和响应能力的关键手段。特别是在Java语言中,由于其平台无关性和强大的运行时环境,多线程技术的应用尤为广泛。本文将深入探讨Java多线程的并发控制机制,包括基本的同步方法、死锁问题以及高级并发工具如java.util.concurrent包的使用。通过分析多线程环境下的竞态条件、资源争夺和线程协调问题,我们提出了一系列实现和优化策略,旨在帮助开发者构建更加健壮、高效的多线程应用。
3 0
|
3天前
|
缓存 监控 Java
Java并发编程:线程池与任务调度
【4月更文挑战第16天】Java并发编程中,线程池和任务调度是核心概念,能提升系统性能和响应速度。线程池通过重用线程减少创建销毁开销,如`ThreadPoolExecutor`和`ScheduledThreadPoolExecutor`。任务调度允许立即或延迟执行任务,具有灵活性。最佳实践包括合理配置线程池大小、避免过度使用线程、及时关闭线程池和处理异常。掌握这些能有效管理并发任务,避免性能瓶颈。
|
4天前
|
设计模式 运维 安全
深入理解Java并发编程:线程安全与性能优化
【4月更文挑战第15天】在Java开发中,多线程编程是提升应用程序性能和响应能力的关键手段。然而,它伴随着诸多挑战,尤其是在保证线程安全的同时如何避免性能瓶颈。本文将探讨Java并发编程的核心概念,包括同步机制、锁优化、线程池使用以及并发集合等,旨在为开发者提供实用的线程安全策略和性能优化技巧。通过实例分析和最佳实践的分享,我们的目标是帮助读者构建既高效又可靠的多线程应用。