线程的核心原理

线程调度模型

1分时调度模型: 系统平均分配CPU时间片,所有线程轮流占用CPU.

2抢占式调度模型: 系统按照线程优先级来分配CPU时间片,优先级高的线程获取CPU执行时间相对多一些.

线程的优先级

Thread类里的这个属性private int priority代表线程的优先级.优先级值的范围为1-10.

    /**
     * The minimum priority that a thread can have.
     */
    public final static int MIN_PRIORITY = 1;

   /**
     * The default priority that is assigned to a thread.
     */
    public final static int NORM_PRIORITY = 5;

    /**
     * The maximum priority that a thread can have.
     */
    public final static int MAX_PRIORITY = 10;

获取线程优先级的方法

public final int getPriority() {
        return priority;
    }

设置线程优先级的方法

public final void setPriority(int newPriority) {
        ThreadGroup g;
        checkAccess();
        if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
            throw new IllegalArgumentException();
        }
        if((g = getThreadGroup()) != null) {
            if (newPriority > g.getMaxPriority()) {
                newPriority = g.getMaxPriority();
            }
            setPriority0(priority = newPriority);
        }
    }

这个set方法和我们写的实体类有点些许不一样呀.因为线程的优先级取值是1-10,进行了传参的校验.还进行了线程组优先级的校验.checkAccess()这个方法是对安全方面的校验.虽然很繁琐,但是我还是觉着思想是好的.有约束才能成方圆嘛.

了解到这里,java其实线程模型采用的也是抢占式调度.空说无凭,难以服众.上案例.

public class PriorityThreadDemo {

    static class PriorityThread extends Thread {
        static int threadName = 1;

        //线程的名称.
        public PriorityThread() {
            super("thread-" + threadName);
            threadName++;
        }

        //线程的执行次数.
        long threadExecuteNum = 0;

        @Override
        public void run() {
            //死循环,看线程执行的次数,
            for (int i = 0; ; i++) {
                threadExecuteNum++;
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        List<PriorityThread> threads = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            PriorityThread priorityThread = new PriorityThread();
            priorityThread.setPriority(i + 1);
            threads.add(priorityThread);
        }
        for (PriorityThread thread : threads) {
            thread.start();
        }

        Thread.sleep(1000);

        for (PriorityThread thread : threads) {
            thread.stop();
        }

        for (PriorityThread thread : threads) {
            System.out.println("线程优先级" + thread.getPriority() + "线程获取时间片" + thread.threadExecuteNum);
        }
    }
}

结果我就不贴出来.可以直接复制代码跑起来,就能看到结果.不过有一点还是要说明,优先级高并不代表一定就会百分百高于其他线程,就像抢占式解释的那样,只是概率高.

线程的生命周期

Thread类里的属性private volatile threadStatusint = 0;代表线程的状态.

NEW状态:创建成功但是还没有调用start方法.

RUNNABLE状态:调用了start方法以后,处于一个就绪状态或者执行状态.(就绪状态就是还未获得CPU执行权,运行态就是获得了CPU执行权)

TERMINATED状态: run方法执行完成以后变成终止状态,当线程执行过程中发生了异常没有被捕获也会进入终止状态.

TIMED_WAITING状态:线程进入限时等待状态.

处于限时等待状态的情况.

Thread.sleep(long n);使得线程进入限时等待,时间为n毫秒.

Object .wait();带时限的抢占对象monitor锁.

Thread.join();带时限的合并线程.

LockSupport.parkNanos();让线程等待时间为纳秒.

LockSupport.parkUntil();让线程等待时间可以灵活设置.

public class StatueThread {
    static List<Thread> threadList = new ArrayList<>();

    //线程名称.
    static int threadNum = 0;

    private static void printThreadStatues() {
        for (Thread threadDemo : threadList) {
            System.out.println("线程" + threadDemo.getName() + "的状态为" + threadDemo.getState());
        }
    }

    private static void addThreadStatues(Thread thread) {
        threadList.add(thread);
    }

    static class StatuesThreadDemo extends Thread {
        public StatuesThreadDemo() {
            super("线程--" + (++threadNum));
            //将自己加入集合中.
            threadList.add(this);
        }

        @Override
        public void run() {
            System.out.println("线程--" + this.getName() + "的状态为" + this.getState());
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                printThreadStatues();
            }
            System.out.println("线程--" + this.getName() + "运行结束");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        addThreadStatues(Thread.currentThread());
        StatuesThreadDemo threadDemo1 = new StatuesThreadDemo();
        System.out.println(threadDemo1.getName() + "--状态为--" + threadDemo1.getState());
        StatuesThreadDemo threadDemo2 = new StatuesThreadDemo();
        System.out.println(threadDemo2.getName() + "--状态为--" + threadDemo1.getState());
        StatuesThreadDemo threadDemo3 = new StatuesThreadDemo();
        System.out.println(threadDemo3.getName() + "--状态为--" + threadDemo1.getState());

        threadDemo1.start();
        Thread.sleep(5000);

        threadDemo2.start();
        Thread.sleep(5000);

        threadDemo3.start();
        Thread.sleep(1000);
    }
}

线程的基本操作

1线程名称在启动之前设置.但也允许为运行的线程设置名称.

2允许两个线程的名字相同,但应该避免.

3如果没有指定线程的名称,系统会自动设置线程的名称.

public class ThreadDemo {

    static class ThreadNameTarget implements Runnable {

        @Override
        public void run() {
            for (int i = 0; i < 3; i++) {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "线程执行的轮次" + i);
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ThreadNameTarget threadNameTarget = new ThreadNameTarget();
        new Thread(threadNameTarget).start();
        new Thread(threadNameTarget).start();
        new Thread(threadNameTarget, "线程--A").start();
        new Thread(threadNameTarget, "线程--A").start();
        Thread.sleep(Integer.MAX_VALUE);
    }
}
线程的sleep操作

睡眠我的作用是让当前线程休眠,把cpu的执行权让给其他线程.线程状态从运行态变成阻塞状态.

Thread类中sleep方法是一组重载的静态方法.会有InterruptedException受检异常.

public static native void sleep(long millis) throws InterruptedException;

public static void sleep(long millis, int nanos) throws InterruptedException

具体的例子就不往出贴了.小伙伴们可以自行尝试体验下.

线程的interrupted操作.

java当中已经提供了stop()方法作为线程的停止操作,但是被设置为过时的操作.是因为无法知道当前线程处于什么状态,有可能持有某把锁,或者正在进行数据库操作,导致属于一致性问题等.才不建议使用stop()方法.

interrupted()方法的作用,如果线程处于阻塞状态(如果调用了Object.wait()方法),就会立马退出阻塞.并且抛出InterruptedException异常,线程可以捕获并且进行处理.Thread.join()和Thread.sleep()方法也会有同样的情况.

如果线程处于运行状态,线程就不会受任何影响,继续运行,只是线程中断标记被设置为true.在适当的位置调用isInterrupted()方法查看自己是否被中断,并执行退出操作.可以看出线程被打断抛出异常,停止执行后面的逻辑.(执行结果我就不贴出来了,小伙伴们可以自行复制代码体会,虽然案例很简单,但是思考的过程真的很重要,不仅仅是你们,我自己也是.)

public class InterruptedDemo {

    static int threadNum = 1;

    static class SleepThread extends Thread {
        //构造方法.
        public SleepThread() {
            super("sleepThread-" + threadNum);
            threadNum++;
        }

        @Override
        public void run() {
            System.out.println(getName() + "进入了睡眠");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
                System.out.println(getName() + "发生异常被打断");
                return;
            }
            System.out.println(getName() + "线程执行结束了.");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SleepThread thread1 = new SleepThread();
        thread1.start();
        SleepThread thread2 = new SleepThread();
        thread2.start();
        //主线程睡眠两秒.
        Thread.sleep(2000);
        //打断线程1.
        thread1.interrupt();
        //主线程等待五秒.
        Thread.sleep(50000);
        thread2.interrupt();

        Thread.sleep(1000);
        System.out.println("程序结束");
    }
}

 可以看出线程被打断抛出异常,停止执行后面的逻辑.(执行结果我就不贴出来了,小伙伴们可以自行复制代码体会,虽然案例很简单,但是思考的过程真的很重要,不仅仅是你们,我自己也是.)

public class RunThread {

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程开始执行了");
                while (true) {
                    System.out.println(Thread.currentThread().isInterrupted());
                    if (Thread.currentThread().isInterrupted()) {
                        System.out.println("线程结束了");
                        return;
                    }
                }
            }
        });
        thread.start();
        Thread.sleep(2000);
        thread.interrupt();
        Thread.sleep(2000);
        thread.interrupt();
    }
}

虽然run方法里是一个无条件不会停止的循环,但是通过标志位,还是会停止这个线程.

线程的join操作

线程join操作有三个版本. 

 public final void join() throws InterruptedException {
    }

 此方法会把当前线程变为WAITING状态.直到线程合并结束.

 public final synchronized void join(long millis)
    throws InterruptedException {}

 此方法会把当前线程变为TIME_WAITING状态,直到合并线程结束,或者被合并线程执行millis的时间.

public final synchronized void join(long millis, int nanos)
    throws InterruptedException {}

此方法会把当前线程变为TIME_WAITING状态,直到合并线程结束,或者被合并线程执行millis+nanos的时间.

join方法的要点.

1: join方法是一个实例方法,需要使用被合并线程的句柄(或者指针 变量)去调用,比如thread.join().

执行这段代码的当前线程为合并线程,会进入TIME_WAITING等待状态,让出CPU执行权,等待被合并线程执行完毕.

2: 如果设置了被合并线程的执行时间mills(或者mills+nanos),并不能保证当前线程一定会回复RUNNABLE状态.(java是抢占式调度,这样会好理解为什么).

3: 如果主动方合并线程在等待时被中断,就会抛出InterruptedException 受检异常.(受检异常和运行时异常可以研究研究,深入了解下)

说了这么多,感觉合并点还是有点模糊,合并点其实可以理解成就是A线程等待B线程执行完在开始的地方.

public class JoinThread {

    static int threadNum = 1;

    static class SleepThread extends Thread {
        //构造方法.
        public SleepThread() {
            super("sleepThread-" + threadNum);
            threadNum++;
        }

        @Override
        public void run() {
            System.out.println(getName() + "进入了睡眠");
            System.out.println(getName() + "线程执行结束了.");
        }
    }

    public static void main(String[] args) {
        SleepThread sleepThread1 = new SleepThread();
        System.out.println("线程启动.");
        sleepThread1.start();
        try {
            //主线程合并sleepThread1
            sleepThread1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        SleepThread sleepThread2 = new SleepThread();
        sleepThread2.start();
        try {
            //主线程合并sleepThread2
            sleepThread2.join(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "线程结束.");
    }
}

从这个例子中就能看出来,等线程一和线程二执行完毕以后,主线程才会打印线程结束,而每个join方法就是合并点.

线程的yield操作.

线程的yield(让步)操作就是把当前线程持有的CPU执行权让出来,让其他线程去持有CPU,但是线程状态依旧是RUNNABLE状态,对应的操作系统就是从执行态转变为就绪态,所以从这里可以想象到,它刚让出CPU执行权也可以重新抢占.

yield()方法是Thread类提供的一个静态方法,它会让当前执行的线程暂停,但又不会阻塞当前线程,让线程进入就绪状态.

public class YieldThreadDemo {

    /**
     * 执行次数.
     */
    public static final int MAX_TURN = 100;
    /**
     * 执行编号.
     */
    public static AtomicInteger index = new AtomicInteger(0);
    /**
     * 执行总次数.
     */
    private static Map<String, AtomicInteger> metric = new HashMap<>();

    /**
     * 输出线程的总次数.
     */
    private static void printMetric() {
        System.out.println("metric=" + metric);
    }

    static class YieldThread extends Thread {
        static int threadNum = 1;

        public YieldThread() {
            super("YieldThread--" + threadNum);
            threadNum++;
            metric.put(this.getName(), new AtomicInteger(0));
        }

        @Override
        public void run() {
            for (int i = 1; i < MAX_TURN && index.get() < MAX_TURN; i++) {
                System.out.println(getName() + "线程优先级:" + getPriority());
                index.getAndIncrement();
                metric.get(this.getName()).getAndIncrement();
                if (i % 2 == 0) {
                    Thread.yield();
                }
                printMetric();
                System.out.println(getName() + "运行结束");
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        YieldThread yieldThread1 = new YieldThread();
        yieldThread1.setPriority(Thread.MAX_PRIORITY);
        YieldThread yieldThread2 = new YieldThread();
        yieldThread2.setPriority(Thread.MIN_PRIORITY);
        System.out.println("启动线程.");
        yieldThread1.start();
        yieldThread2.start();
        Thread.sleep(1000);
    }
}

 线程的daemon操作

java里的线程主要分为守护线程和用户线程.守护线程可以理解为后台线程.主要在后台提供一下服务.比如垃圾回收线程.

1:守护线程的基本操作

实例属性daemon:保存一个线程实例的守护状态,默认为false.表示默认为用户线程.

实例方法setDaemon():获取线程的守护状态,判断这个线程是不是守护线程.

2:守护线程的操作演示

public class DaemonThread {
    //守护线程类.
    static class daemonThread extends Thread {
        public daemonThread() {
            super("daemonThread");
        }

        @Override
        public void run() {
            System.out.println("--daemon线程开始执行");
            for (int i = 1; ; i++) {
                System.out.println(">>轮次" + i);
                System.out.println("--守护状态为" + Thread.currentThread().isDaemon());
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        daemonThread daemonThread = new daemonThread();
        daemonThread.setDaemon(true);
        daemonThread.start();
        Thread thread = new Thread(() -> {
            System.out.println(">>用户线程开始执行");
            for (int i = 0; i < 4; i++) {
                System.out.println(">>轮次" + i);
                System.out.println(">>线程状态为" + Thread.currentThread().isDaemon());
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("用户线程结束.");
        }, "userThread");
        
        thread.start();
        System.out.println("守护状态为"+Thread.currentThread().isDaemon());
        System.out.println("线程结束");
    }
}

3:守护线程与用户线程的关系

从是否守护线程来分,分为用户线程和守护线程.本质区别上,二者与jvm虚拟机进程终止方向不同.用户线程和jvm进程属于主动关系.如果用户线程全部终止,jvm进程也会终止.守护线程和jvm进程属于被动关系,jvm进程终止了,所有的守护线程也会终止.

4:守护线程的要点

用户线程必须在启动之前就设置为守护线程,启动之后不能再将用户线程设置为守护线程,否则jvm会抛出一个InterruptedException异常.

守护线程存在被jvm强行终止的风险.所以守护线程尽量不要去访问系统资源,如文件句柄数据库连接等.

守护线程创建的线程也是守护线程.

好多事情在没有做之前总觉着很简单,但是做起来了才发现.总结才是最难得.但是还是有很多收获吧.坚持下去,虽然不懂,但是在次回首,会发现收获到了意向不到的快乐.

如果喜欢的话,大家可以搜索一下微信公众号.   心有九月星辰

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值