二、多线程详解

一、线程状态图

15200008-264ac3218e837db3.png
线程状态图

二、详解

1.可运行状态

包括:就绪态运行中两种状态。
虽然调用了start()方法,这个线程看起来开始运行了,但是不一定会运行,要看cpu有没有给你这个线程分配时间片。如果分配了,当前就是运行中状态;如果没有分配时间片,则处于一个就绪中的状态,只不过是在随时等待着cpu分配时间片。
查看某个线程的状态

在jdk文件夹下的-->bin目录下,运行:jps -v命令可以看到当前机器下运行的java程序,接着用jsack [程序号]可以看到指定的程序中所有的线程的状态。

2.LockSupport

其中的一个park()是jdk底层的一个专门阻塞线程的方法。

3.等待状态和等待超时状态

  • 区别在于调用wait方法或者join方法的时候有没有带时间数,如果带了时间数或者调用sleep方法,则进入等待超时状态。如果没有特定地去唤醒这个线程的话,这个线程超过指定的时间就会自动回到可运行状态。
  • 而处于等待状态的线程,必须要专门的唤醒notify()或者notifiAll()方法,才能进入可运行状态。

4.阻塞

  • 当我们用synchronized关键字时,线程会进入一个阻塞状态。表现上和等待状态很像,只不过它是等待某个内置锁而触发的。当有多个线程共同运行时。
  • 实际开发中我们会用synchronized关键字对程序进行加锁,当有多个线程同时竞争这把锁的时候,在任意时刻,有且仅有一个线程能获取这把锁,其他的线程此时处于一种阻塞状态。当等待锁的线程获取到锁时,重新进入可运行状态。
5.Thread实现了Runnable接口

三、run()方法和start()方法

底层
start方法在同一线程中只能调用一次!!!
why?

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 */
            }
        }
    }
  • 可以看到,调用start方法时,会先判断threadStatus 是否为0,如果不为0,则会抛出一个IllegalThreadStateException异常。
  • 同一个线程只能调用start()方法一次,多次调用会抛出java.lang.IllegalThreadStateException。启动一个线程,需要调用start()方法而不是run()方法。此时,当前线程会被添加到线程组中,进入就绪状态,等待线程调度器的调用,若获取到了资源,则能进入运行状态,run()方法只是线程体,即线程执行的内容,若没调用start()方法,run()方法只是一个普通的方法。


    15200008-3d749d1b5ad0d440.png
    threadStatus
1.真正意义上的线程
 ThreadRun threadRun = new ThreadRun();

这个new方法只是说我们在jdk的虚拟机中new出了一个Thread类的实例,还没有和操作系统中的线程真正挂钩,只有执行了start方法才真正启动了一个线程。这个start方法不能重复地调用。当线程获取到时间片之后,才会真正执行run方法。run方法只是一个业务方法,它可以被重复地执行,也可以被单独地调用。
什么意思呢?看如下代码

15200008-1ca0ab131f7f035f.png
run和start

可以看到run方法本身只是一个实现业务逻辑的地方,真正和线程挂钩的方法还是start方法。

四、yield()方法

yield()用的很少,是一个原生方法,只是当前线程让出cpu的执行权,告诉操作系统,CPU我不用了。

 /**
     * A hint to the scheduler that the current thread is willing to yield
     * its current use of a processor. The scheduler is free to ignore this
     * hint.
     *
     * <p> Yield is a heuristic attempt to improve relative progression
     * between threads that would otherwise over-utilise a CPU. Its use
     * should be combined with detailed profiling and benchmarking to
     * ensure that it actually has the desired effect.
     *
     * <p> It is rarely appropriate to use this method. It may be useful
     * for debugging or testing purposes, where it may help to reproduce
     * bugs due to race conditions. It may also be useful when designing
     * concurrency control constructs such as the ones in the
     * {@link java.util.concurrent.locks} package.
     */
    public static native void yield();
ConcurrentHashMap
 /**
     * Initializes table, using the size recorded in sizeCtl.
     */
    private final Node<K,V>[] initTable() {
        Node<K,V>[] tab; int sc;
        while ((tab = table) == null || tab.length == 0) {
            if ((sc = sizeCtl) < 0)
                Thread.yield(); // lost initialization race; just spin
            else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                try {
                    if ((tab = table) == null || tab.length == 0) {
                        int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                        @SuppressWarnings("unchecked")
                        Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                        table = tab = nt;
                        sc = n - (n >>> 2);
                    }
                } finally {
                    sizeCtl = sc;
                }
                break;
            }
        }
        return tab;
    }

可以看到ConcurrentHashMap用到了yield()方法,在1.8的时候可以允许多个线程对这个结构初始化的,但是在初始化的时候只允许一个线程进行初始化。因此在初始化的时候需要其他的线程让出CPU的时间片交给其他需要执行初始化的线程去执行。
yield()方法将线程从运行中的状态转化为就绪状态。

五、join()方法

join()的作用就是将指定的线程加入到当前线程,将两个交替执行的线程合并为顺序执行。

  • Join方法,当前线程A等待thread线程终止之后才从thread.join()返回。线程Thread除了提供join()方法之外,还提供了join(long millis)和join(longmillis,int nanos)两个具备超时特性的方法。这两个超时方法表示,如果线程thread在给定的超时时间里没有终止,那么将会从该超时方法中返回。
1.代码实现
package com.tinner.thread;

import javax.sound.midi.Soundbank;

/**
 * @Author Tinner
 * @create 2019/9/18 16:31
 */
public class TestJoin {
    static class Goddess implements Runnable{

        private Thread thread;

        public Goddess(Thread thread) {
            this.thread = thread;
        }

        public Goddess() {
        }

        @Override
        public void run() {
            System.out.println("Goddess 开始排队打饭");
            try {
                if (thread != null){
                    thread.join();
                }
                Thread.sleep(2000);
                System.out.println(Thread.currentThread().getName() + "Goddess 打饭完成");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    static class GoddessBoyFriend implements Runnable{

        @Override
        public void run() {
            System.out.println("GoddessBoyFriend 开始排队打饭");
            try {
                Thread.sleep(2000);
                System.out.println(Thread.currentThread().getName() + " GoddessBoyFriend 打饭完成");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread lison = Thread.currentThread();
        GoddessBoyFriend goddessBoyFriend = new GoddessBoyFriend();
        Thread gbf = new Thread(goddessBoyFriend);
        Goddess goddess = new Goddess(gbf);
        Thread g = new Thread(goddess);

        g.start();
        gbf.start();
        System.out.println("lison开始排队打饭......");
        g.join();
        Thread.sleep(2000);
        System.out.println(Thread.currentThread().getName() + "lison打饭完成");
    }
}

以上代码是这样一个故事:lison有一个女神,女神有一个男神,有一天一起去食堂打饭,lison看到了自己女神,于是让女神排在了自己前面,而女神看到了自己的男神,于是又让自己的男神排到了自己前面,最后,男神先打完饭,女神次之,lison反倒成了最后一个打饭的,这就是join的用法


15200008-69f0b13e6f2c11dd.png
join例子
总结

join方法是将代码执行一半的时候强行插入另一个线程,只有当另一个线程执行完毕之后才执行当前线程后续的代码,让本来交替执行的线程变成顺序执行的状态。

六、线程优先级

如果要调整线程的优先级,在每一个线程new出来之后,这个thread有一个setPriority()方法,可以设置一个1~10的数字,如果不指定,默认是5,数字越高优先级越高。设置线程的优先级可以告诉OS我这个线程的优先级比较高,给我多分配点时间片,但是对于频繁操作流的线程,默认的os分配的优先级比较低,对于那种偏于计算之类的线程默认的优先级比较高。线程的优先级在java中基本上可以忽略不计,因为设置了优先级之后,真正映射到os上之后基本没啥用,设置线程的优先级只是一个安慰操作,对于不同的平台和不同的版本内部调度时间片的方式,都不一样。

七、守护线程

当我们在代码中new出一个线程之后,称之为用户线程,用户线程和main线程的级别是一样的。守护线程主要做辅助工作,如:后台的调度、资源的回收等操作,Java中典型的:Object中的finalize()方法就是一个守护线程。java程序中只要有一个用户线程存活,整个java程序是不会退出的,但是对于守护线程而,只要java程序中没有用户线程了,哪怕有一万个守护线程,java程序也会退出。
如何把当前线程设置为守护线程?
线程类中有一个方法:setDaemon(true),可以将一个线程设置为守护线程

Thread g = new Thread(goddess);
g.setDaemon(true);

注意
如果run方法中有finally代码块,如果当前线程是守护线程,则finally代码块中的代码不一定会执行。因为当程序中所有的用户线程执行完毕之后,JVM会强制地将所有的守护线程关闭,不管守护线程执行到什么情况,这种情况很有可能代码执行一半,CPU时间片不会给你分配,可能就不会让你执行finally从句。一般情况下不在守护线程的finally代码块中执行业务代码。这就是为什么只在守护线程中做一些后台调度或者内存清理的工作。

15200008-bd6e1008a2dc8e25.png
image.png

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值