史上最全的Java并发系列之Java多线程(一)

简介: 前言文本已收录至我的GitHub仓库,欢迎Star:github.com/bin39232820…种一棵树最好的时间是十年前,其次是现在

前言


文本已收录至我的GitHub仓库,欢迎Star:github.com/bin39232820…

种一棵树最好的时间是十年前,其次是现在

絮叨


今天我们讲并发编程的基础,也就是多线程的基础,没有多线程的话,就没有并发对吧,下面是前面的章节链接:

线程的简介


  • 什么是线程

线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

  • 为什么要使用多线程

目前的处理器核心越来越多,使用多线程能有更快的响应时间,并能有更好的编程模型。

  • 线程优先级

现代操作系统基本采用时分的形式调度运行的线程,操作系统分出每一个时间片会根据线程的优先级来分配,优先级越高的最先获取执行资源。

在Java线程中,通过一个整型成员变量priority来控制优先级,优先级的范围从1~10,在线程构建的时候可以通过setPriority(int)方法来修改优先级,默认优先级是5,优先级高的线程分配时间片的数量要多于优先级低的线程。


线程的状态

  • NEW 初始状态
  • RUNNABLE 运行状态
  • BLOCKED 阻塞状态
  • WAITING 等待状态
  • TIME_WAITING 超时等待状态
  • TERMINATED 终止状态

Thead类源码解读


Java所有多线程的实现,均通过封装Thread类实现,所以深入Thread类,对深入理解java多线程很有必要

继承关系


主要属性

/*
 * 线程名字,通过构造参数来指定  
 */
private volatile String name;
/*
 * 表示线程的优先级,优先级越高,越优先被执行(最大值为10,最小值为1,默认值为5)
 */
private int  priority;
/*
 * 线程是否是守护线程:当所有非守护进程结束或死亡后,程序将停止
 */   
private boolean  daemon = false;
/*
 * 将要执行的任务
 */  
private Runnable target;
/*
 * 线程组表示一个线程的集合。此外,线程组也可以包含其他线程组。线程组构成一棵树,在树中,除了初始线程组外,每个线程组都有一个父线程组。
 */ 
private ThreadGroup group;
/*
 * Thread ID
 */ 
private long tid;
/*
 * 用来生成Thread ID使用
 */ 
private static long threadSeqNumber;
/*
 * 第几个线程,在init初始化线程的时候用来赋给thread.name
 */ 
private static int threadInitNumber
/*
 * 线程从创建到最终的消亡,要经历若干个状态。 
 * 一般来说,线程包括以下这几个状态:创建(new)、就绪(runnable)、运行(running)、阻塞(blocked)、time waiting、waiting、消亡(dead)
 */
private volatile int threadStatus = 0;
复制代码


构造函数

//传入Runnable接口实现
Thread(Runnable target)
//传入Runnable接口实现,传入线程名
Thread(Runnable target, String name) 
//设置当前线程用户组
Thread(ThreadGroup group, Runnable target)
//设置用户组,传入线程名
Thread(ThreadGroup group, Runnable target, String name)
//设置用户组,传入线程名,设置当前线程栈大小
Thread(ThreadGroup group, Runnable target, String name, long stackSize) 
复制代码


这边提一下创建线程的几种方式,大体来说有4种吧,我这边代码就不上了,不然就写的太长了

  • 第一种,便是继续Thread类  ,重写run方法,其实这个run方法也是重写Runnable的,
  • 第二种,便是去实现Runnable,也是一样
  • 第三种是 通过FutureTask 来创建一个线程,这个的原理是啥呢?其实一样这个类的父类继承Runnable,这个比较特殊的一点是线程执行完毕之后,可以有返回值返回。
  • 第四个就是通过线程池来创建一个线程(生产环境都这么用)

线程默认名称生产规则:

// 当前缺省线程名:"Thread-" + nextThreadNum()
public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
---
// nextThreadNum 同步方法,线程安全,不会出现重复的threadInitNumber
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
    return threadInitNumber++;
}
复制代码


线程私有化实现,好好讲讲这个初始化方法

/**
     * Initializes a Thread.
     *
     * @param g the Thread group
     * @param target the object whose run() method gets called
     * @param name the name of the new Thread
     * @param stackSize the desired stack size for the new thread, or
     *        zero to indicate that this parameter is to be ignored.
     * @param acc the AccessControlContext to inherit, or
     *            AccessController.getContext() if null
     */
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
    //线程必须设置名称,否则抛出空指针异常
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }
        this.name = name.toCharArray();
    //父线程
        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        //如果线程组为null
        if (g == null) {
            /* Determine if it's an applet or not */
            //如果有安全管理器,从安全管理器获得线程组
            /* If there is a security manager, ask the security manager
               what to do. */
            if (security != null) {
                g = security.getThreadGroup();
            }
            //如果安全管理器也不知道线程组,那么从父线程获取线程组,也就是线程必须归属于某个线程组
            /* If the security doesn't have a strong opinion of the matter
               use the parent thread group. */
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }
        /* checkAccess regardless of whether or not threadgroup is
           explicitly passed in. */
        g.checkAccess();
        /*
         * Do we have the required permissions?
         //校验是否有安全校验权利,如果重写了getContextClassLoader和setContextClassLoader
         */
        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }
        //线程组添加未启动线程
        g.addUnstarted();
        this.group = g;
    /* 设置当前线程是否为守护线程,默认是和当前类的ThreadGroup设置相
        * 同。如果是守护线程的话,当前线程结束会随着主线程的退出而退出。
        *jvm退出的标识是,当前系统没有活跃的非守护线程。
        */
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        this.target = target;
        setPriority(priority);
        if (parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /*设置指定的栈大小,如果未指定大小,将在jvm 初始化参数中声明:Xss参数进行指定*/
        this.stackSize = stackSize;
        /* Set thread ID */
        tid = nextThreadID();
    }
复制代码


start方法

从源码中得出:一个线程一旦已经被start了就不能再次执行start方法。被start过的线程,线程状态已经不是0了

/*  导致此线程开始执行; Java Virtual Machine调用此线程的run方法。
    结果是两个线程同时运行:当前线程(从调用返回到start方法)和另一个线程(执行其run方法)。
    不止一次启动线程永远不合法。
    特别是,一旦完成执行,线程可能无法重新启动。
    @exception IllegalThreadStateException如果线程已经启动。
    @see #run()
    @see #stop()*/
 public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        //此判断当前线程只能被启动一次,不能被重复启动
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        /*通知组该线程即将启动
          *这样它就可以添加到组的线程列表中
         *并且该组的未启动计数可以递减。*/
        group.add(this);
        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                // 如果线程启动失败,从线程组里面移除该线程
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }
复制代码


其实,这个源码也简单,就是启动线程 然后让那个组里面待启动的线程减1,如果失败,就把他加入到失败的数组里面。

join() 方法源码分析:

让父线程等待子线程结束之后才能继续运行。

/**
     * Waits at most {@code millis} milliseconds for this thread to
     * die. A timeout of {@code 0} means to wait forever.
     *
     * <p> This implementation uses a loop of {@code this.wait} calls
     * conditioned on {@code this.isAlive}. As a thread terminates the
     * {@code this.notifyAll} method is invoked. It is recommended that
     * applications not use {@code wait}, {@code notify}, or
     * {@code notifyAll} on {@code Thread} instances.
     *
     * @param  millis
     *         the time to wait in milliseconds
     *
     * @throws  IllegalArgumentException
     *          if the value of {@code millis} is negative
     *
     * @throws  InterruptedException
     *          if any thread has interrupted the current thread. The
     *          <i>interrupted status</i> of the current thread is
     *          cleared when this exception is thrown.
     */
    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) {
        // 如果millis == 0 线程将一直等待下去
            while (isAlive()) {
                wait(0);
            }
        } else {
            // 指定了millis ,等待指定时间以后,会break当前线程
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }
复制代码


其实这个也简单,随便说说,首先Join方法呢?有几个重载的方法,如果你不传参数,那么就表示你的超时时间是永不过期,只要调用join方法的线程不执行完成,当前线程就会一直等待,其底层实现还是Java object 的wait方法

注意点:

  • join方法是通过wait方法实现的,所以会释放对象锁。
  • 调用方线程状态为WAITING。
  • join需要在start之后调用


interrupt方法

线程中断在之前的版本有stop方法,但是被设置过时了。现在已经没有强制线程终止的方法了!

由于stop方法可以让一个线程A终止掉另一个线程B

  • 被终止的线程B会立即释放锁,这可能会让对象处于不一致的状态。
  • 线程A也不知道线程B什么时候能够被终止掉,万一线程B还处理运行计算阶段,线程A调用stop方法将线程B终止,那就很无辜了~

总而言之,Stop方法太暴力了,不安全,所以被设置过时了。

我们一般使用的是interrupt来请求终止线程~

  • 要注意的是:interrupt不会真正停止一个线程,它仅仅是给这个线程发了一个信号告诉它,它应该要结束了(明白这一点非常重要!)
  • 也就是说:Java设计者实际上是想线程自己来终止,通过上面的信号,就可以判断处理什么业务了。
  • 具体到底中断还是继续运行,应该由被通知的线程自己处理
Thread t1 = new Thread( new Runnable(){
    public void run(){
        // 若未发生中断,就正常执行任务
        while(!Thread.currentThread.isInterrupted()){
            // 正常任务代码……
        }
        // 中断的处理代码……
        doSomething();
    }
} ).start();
复制代码


再次说明:调用interrupt()并不是要真正终止掉当前线程,仅仅是设置了一个中断标志。这个中断标志可以给我们用来判断什么时候该干什么活!什么时候中断由我们自己来决定,这样就可以安全地终止线程了!

//除非中断自己,checkAccess都将被调用
    /**
     * Interrupts this thread.
     *
     * <p> Unless the current thread is interrupting itself, which is
     * always permitted, the {@link #checkAccess() checkAccess} method
     * of this thread is invoked, which may cause a {@link
     * SecurityException} to be thrown.
     *
   //如果一个线程阻塞在wat方法,或者线程的join方法,再或者sleep方法上,
   //线程的中断状态被清空设置为false,并且被interrupt的线程将收到一个中断异常。
     * <p> If this thread is blocked in an invocation of the {@link
     * Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link
     * Object#wait(long, int) wait(long, int)} methods of the {@link Object}
     * class, or of the {@link #join()}, {@link #join(long)}, {@link
     * #join(long, int)}, {@link #sleep(long)}, or {@link #sleep(long, int)},
     * methods of this class, then its interrupt status will be cleared and it
     * will receive an {@link InterruptedException}.
     *
   //如果线程阻塞在IO操作,channel将被关闭,并且线程的中断状态会被设置为true,
   //并且被interrupt的线程将收到一个ClosedByInterruptException异常。
     * <p> If this thread is blocked in an I/O operation upon an {@link
     * java.nio.channels.InterruptibleChannel InterruptibleChannel}
     * then the channel will be closed, the thread's interrupt
     * status will be set, and the thread will receive a {@link
     * java.nio.channels.ClosedByInterruptException}.
     *
   //如果线程阻塞在selector方法,中断线程的中断状态将设置为true,并且从select操作立即返回,
   //只有selector的wakeup方法被调用可能返回一个非0值。
     * <p> If this thread is blocked in a {@link java.nio.channels.Selector}
     * then the thread's interrupt status will be set and it will return
     * immediately from the selection operation, possibly with a non-zero
     * value, just as if the selector's {@link
     * java.nio.channels.Selector#wakeup wakeup} method were invoked.
     *//如果不是上面说的几种情况,线程的中断状态将被设置。
     * <p> If none of the previous conditions hold then this thread's interrupt
     * status will be set. </p>
     *
     * <p> Interrupting a thread that is not alive need not have any effect.
     *
     * @throws  SecurityException
     *          if the current thread cannot modify this thread
     *
     * @revised 6.0
     * @spec JSR-51
     */
    public void interrupt() {
        if (this != Thread.currentThread())
            checkAccess();
        synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
                interrupt0();           // Just to set the interrupt flag
                b.interrupt(this);
                return;
            }
        }
        interrupt0();
    }
复制代码


interrupt线程中断还有另外两个方法(检查该线程是否被中断):

  • 静态方法interrupted()-->会清除中断标志位
  • 实例方法isInterrupted()-->不会清除中断标志位

sleep方法(long millis)

public static native void sleep(long millis) throws InterruptedException;
复制代码


注意点:

  • sleep是指线程被调用时,占着CPU不工作,形象地说明为“占着CPU睡觉”,此时,系统的CPU部分资源被占用,其他线程无法进入。
  • sleep方法不会释放锁。

yield()方法

public static native void yield();
复制代码


注意点:

  • 调用yield方法会让当前线程交出CPU权限,让拥有相同优先级的线程有获取CPU执行时间的机会
  • 调用yield方法并不会让线程进入BLOCKED状态,而是让线程重回RUNNABLE状态, 但不能保证迅速转换。(自己也可以再次竞争的)
  • 不会释放锁

结尾


第四章的第一小节讲完了,讲的是Thread,不是很深,跟着学还是可以的,好了,今天就到这里了

因为博主也是一个开发萌新 我也是一边学一边写 我有个目标就是一周 二到三篇 希望能坚持个一年吧 希望各位大佬多提意见,让我多学习,一起进步。


相关文章
|
2天前
|
Java 程序员 开发者
Java社招面试题:一个线程运行时发生异常会怎样?
大家好,我是小米。今天分享一个经典的 Java 面试题:线程运行时发生异常,程序会怎样处理?此问题考察 Java 线程和异常处理机制的理解。线程发生异常,默认会导致线程终止,但可以通过 try-catch 捕获并处理,避免影响其他线程。未捕获的异常可通过 Thread.UncaughtExceptionHandler 处理。线程池中的异常会被自动处理,不影响任务执行。希望这篇文章能帮助你深入理解 Java 线程异常处理机制,为面试做好准备。如果你觉得有帮助,欢迎收藏、转发!
35 14
|
5天前
|
安全 Java 程序员
Java 面试必问!线程构造方法和静态块的执行线程到底是谁?
大家好,我是小米。今天聊聊Java多线程面试题:线程类的构造方法和静态块是由哪个线程调用的?构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节有助于掌握Java多线程机制。下期再见! 简介: 本文通过一个常见的Java多线程面试题,详细讲解了线程类的构造方法和静态块是由哪个线程调用的。构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节对掌握Java多线程编程至关重要。
34 13
|
6天前
|
安全 Java 开发者
【JAVA】封装多线程原理
Java 中的多线程封装旨在简化使用、提高安全性和增强可维护性。通过抽象和隐藏底层细节,提供简洁接口。常见封装方式包括基于 Runnable 和 Callable 接口的任务封装,以及线程池的封装。Runnable 适用于无返回值任务,Callable 支持有返回值任务。线程池(如 ExecutorService)则用于管理和复用线程,减少性能开销。示例代码展示了如何实现这些封装,使多线程编程更加高效和安全。
|
1月前
|
监控 Java
java异步判断线程池所有任务是否执行完
通过上述步骤,您可以在Java中实现异步判断线程池所有任务是否执行完毕。这种方法使用了 `CompletionService`来监控任务的完成情况,并通过一个独立线程异步检查所有任务的执行状态。这种设计不仅简洁高效,还能确保在大量任务处理时程序的稳定性和可维护性。希望本文能为您的开发工作提供实用的指导和帮助。
109 17
|
2月前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
|
1月前
|
缓存 安全 算法
Java 多线程 面试题
Java 多线程 相关基础面试题
|
6月前
|
安全 Java 调度
解锁Java并发编程高阶技能:深入剖析无锁CAS机制、揭秘魔法类Unsafe、精通原子包Atomic,打造高效并发应用
【8月更文挑战第4天】在Java并发编程中,无锁编程以高性能和低延迟应对高并发挑战。核心在于无锁CAS(Compare-And-Swap)机制,它基于硬件支持,确保原子性更新;Unsafe类提供底层内存操作,实现CAS;原子包java.util.concurrent.atomic封装了CAS操作,简化并发编程。通过`AtomicInteger`示例,展现了线程安全的自增操作,突显了这些技术在构建高效并发程序中的关键作用。
92 1
|
3月前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
3月前
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####
|
5月前
|
Java API 容器
JAVA并发编程系列(10)Condition条件队列-并发协作者
本文通过一线大厂面试真题,模拟消费者-生产者的场景,通过简洁的代码演示,帮助读者快速理解并复用。文章还详细解释了Condition与Object.wait()、notify()的区别,并探讨了Condition的核心原理及其实现机制。

热门文章

最新文章