java线程基础整理

1、线程简介

1.1 什么是线程

操作系统在运行一个程序时,会为其创建一个进程。操作系统调度的最小单元是线程,在一个进程里可以创建多个线程,这些线程都各自有各自的计数器、堆栈和局部变量等属性,而且可以访问共享的内存变量。cpu在这些线程中不断高速的切换,让使用者感觉这些线程在同时执行

1.2 线程的优先级

操作系统基本上采用时分的形式调度运行的线程,操作系统的会划分一个个时间片,线程在执行的时候,就是拿到分配的时间片,当时间片用完后,就会切换到其他线程,并等待下次时间片的分配.线程分配到了多少时间片资源,就代表需要多少CPU资源,而线程优先级就是决定线程在执行时,分配时间片的优先程度,越早分配,就越早执行完成,也就代表该线程优先级越高

在java中,可以指定给线程优先级,如果不指定,则统一为默认的优先级,有CPU来决定执行顺序

 @Test
    public void learnThread1() throws Exception{
        for (int i = 0; i < 50; i++) {
            final  int h =i;
            Thread threadA = new Thread(() -> {
                System.out.println("线程A 执行 "+h);
            });
//            threadA.setPriority(Thread.MAX_PRIORITY);

            Thread threadB = new Thread(() -> {
                System.out.println("线程B 执行 "+h);
            });

            threadA.start();
            threadB.start();
            System.out.println();

        }
        Thread.sleep(1000);
    }

着这里执行 50次 的结果


线程B 执行 0
线程A 执行 0
线程B 执行 26
线程A 执行 26
线程B 执行 34
线程A 执行 34
线程A 执行 48
线程B 执行 48
线程A 执行 49
线程B 执行 49

可以看到A B两个线程的执行顺序是有发送顺序的差别的.
但是如果设置优先级 则就可以保证有些线程是优先执行的.
把上边的 threadA.setPriority(Thread.MAX_PRIORITY);
放开 则是执行结果都是顺序的. 在不同的JVM或者操作系统上,线程的优先级是会存在差异的,有些操作系统甚至会忽略对线程优先级的设定. 比我使用的环境是 <Mac Os 10.15.5 JDK 1.8> 这里是不生效的 大家可以看看这篇文章的内容 是对于优先级的讲解优先级讲解文章

1.3 线程的状态

线程在运行时会发生以下几种状态变化.

状态名称状态说明
NEW初始状态,线程被创建,但是还没有调用start()方法
RUNNABLE运行状态,JAVA线程将操作系统中的就绪和运行两种状态都称为 运行中 状态
BLOCKED阻塞状态,表示线程处于阻塞,一般在竞争资源时,等待锁的时候,会进入这个状态
WAITING等待状态,表示线程进入等待状态,比如等待其他线程的通知和中断,或者说调用await()方法时,就是在等待状态
TIME_WAITING超时等待状态,该状态不同于WAITING,这个状态可以再等待一段时间后,自行返回,也就是不是一直保持等待状态,不需要其他线程的通知或者中断
TERMINATED终止状态,表示线程已经执行完毕

在通过对于运行的java程序,使用Jstack 命令后可以得到这样的状态说明.

如图示例代码

 @Test
    public void testThread(){
        new Thread(()->{
            while (true){
                //该线程一直在进行睡眠, 这是为了验证超时等待的状态
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"TimeWaitingThread").start();

        final Object wait = new Object();
        new Thread(()-> {
            //加锁 只保证 有个线程能拿到锁,后续调用wait的时候是 释放锁,但是等待其他线程唤醒,此时线程就进入为 等待状态
            synchronized (wait){
                try {
                    wait.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"WaitingThread").start();

        final Object block = new Object();
        new Thread(()->{
            synchronized (block){
                while (true){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        },"BlockThread-1").start();


        new Thread(()->{
            synchronized (block){
                while (true){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        },"BlockThread-2").start();
        //防止线程关闭.
        while (true){

        }
    }

通过JPS 和jsatck看线程运行的栈信息
在这里插入图片描述
在这里插入图片描述
以下就是线程执行状态中,状态的流转图
在这里插入图片描述

1.4 守护线程

Daemon 线程是一种支持性线程,因为主要用作程序中后台调度以及支持性工作,如果JVM中不存在非Daemon线程的时候,JVM将退出.Daemon被称为守护线程.通过调用Thread.setDaemon(true)将线程设置为守护线程.

2、启动和终止线程

2.1 线程的构建和启动

线程的创建方式有两种

  • 实现Runable接口
  • 继承Thread类
    下边来看看Thread类的init方法
 private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        this.name = name;
        //当前线程为该线程的父线程
        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        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?
         */
        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }

        g.addUnstarted();

        this.group = g;
        //设置为父线程的对应属性
        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 (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                    ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* Set thread ID */
        tid = nextThreadID();
    }

线程的启动

线程的启动方式是调用start() 方法进行启动. 当前线程
同步告知JVM,只要线程规划器空闲,应立即调用start()方法的线程

2.2 线程中断

线程的中断是调用thread.interrupt(); 方法进行中断.

  • 中断可以理解为线程的一个标识属性,标识一个运行中的线程是否被其他线程进行了中断操作.好比说其他线程对该线程打了一个招呼,其他线程通过调用该线程的thread.interrupt(); 来进行对其中断操作
    线程通过调用threadA.isInterrupted(); 来判断是否被中断,也可以进行调用静态方法Thread.interrupted() 进行复位标识,如果线程已经终止,即使该线程被中断过,该线程的isInterrupted() 也会返回false.
    javaAPI中 许多声明抛出InterruptedException 的方法,这些方法在抛出InterruptedException 之前,JVM会先将该线程的中断标识位清除,然后抛出InterruptedException 此时 isInterrupted() 方法返回false
 @Test
    public void testInterrupte()  {
        Thread thread = new Thread(() -> {
            while (true) {
                try {
                    /**
                     * Sleep方法的注释说明. 会判断 是否中断 如果中断则抛出异常
                     *  * @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 static native void sleep ( long millis) throws InterruptedException;
                     */
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "Thread-1");
        thread.setDaemon(true);

        Thread thread2 = new Thread(() -> {
            while (true) {
//                System.out.println("----");
            }

        }, "thread-2");
        thread2.setDaemon(true);

        thread.start();;
        thread2.start();
        //等待 两个线程可以都获取到时间片运行
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        thread.interrupt();
        thread2.interrupt();

        System.out.println("thread-1 interrupt = " + thread.isInterrupted());
        System.out.println("thread-2 interrupt = " + thread2.isInterrupted());

    }

运行结果:

java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at chenyi.demo.javaThread.LearnJavaTread.lambda$testInterrupte$6(LearnJavaTread.java:104)
	at java.lang.Thread.run(Thread.java:748)
thread-1 interrupt = false
thread-2 interrupt = true
Disconnected from the target VM, address: '127.0.0.1:61190', transport: 'socket'

抛出的异常是因为,在sleep的时候会判断线程是否是interrupted

2.3 线程的暂停,恢复,和 停止

直接看代码:

@Test
    public void testThreadBc() throws InterruptedException {
        Thread thread = new Thread(() -> {
            while (true){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("我在生产---");
            }
        }, "Thread-C");
        thread.setDaemon(true);
        thread.start();
        Thread.sleep(5000);
        System.out.println("线程暂停 start");
        //方法暂停
        thread.suspend();
        System.out.println("线程暂停 end");

        Thread.sleep(5000);
        //线程方法恢复 执行
        thread.resume();
        System.out.println("线程恢复 end");

        Thread.sleep(5000);
        //线程方法结束
        thread.stop();
        System.out.println("线程结束 end");
    }

执行结果:

我在生产---
我在生产---
我在生产---
我在生产---
线程暂停 start
线程暂停 end
我在生产---
线程恢复 end
我在生产---
我在生产---
我在生产---
我在生产---
线程结束 end

线程的暂定 恢复使用 以及结束都是可以正常的生效.
注意:

但是这些方法被标注为过时,是因为在调用这些方法的时候,线程不会释放资源,而是一直持有资源,这样容易引发死锁问题.同样,stop方法在终结一个线程时,无法保证该线程持有的资源可以正常释放.只是给线程线程资源释放工作的机会.

2.4 线程的终止

上边说,线程的stop方法只是给线程资源释放的机会,不能保证资源一定被释放,那么线程该如何安全的终止呢?

2.2提到 中断状态是线程的标识位,而中断操作是一种简便的线程间交互方式,这种凡是最适合用来取消或者停止任务.

2.5 Thread.join()方法的使用

如果一个线程A执行了thread.join() 方法,表示的是,当前线程A等待thread线程终止后,才会从thread.join() 方法返回线程Thread提供了join() join(long minlls) 和 join(long minlls,int nanos)
下边我们具体展示一下是怎么使用的

@Test
    public void testJoin() throws InterruptedException {
        final Thread threadA = new Thread(() -> {
            System.out.println(this.getClass().getName() +"进入线程");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("I'm   A");
            System.out.println(this.getClass().getName() +"线程执行完毕");
        }, "thread-a");
        final Thread threadB = new Thread(() -> {
            try {
                //等待B 表示是一直等待 和 join方法是一样的
                threadA.join(0);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(this.getClass().getName() +"进入线程");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("I'm   B");
            System.out.println(this.getClass().getName() +"线程执行完毕");
        }, "thread-b");
        final Thread threadC = new Thread(() -> {
            try {
                //等待B 1000毫秒
                threadB.join(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(this.getClass().getName() +"进入线程");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("I'm   C");
            System.out.println(this.getClass().getName() +"线程执行完毕");
        }, "thread-c");
        final Thread threadD = new Thread(() -> {
            try {
                threadC.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(this.getClass().getName() +"进入线程");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("I'm   D");
            System.out.println(this.getClass().getName() +"线程执行完毕");
        }, "thread-d");

        threadA.start();
        threadB.start();
        threadC.start();
        threadD.start();

        Thread.sleep(20000);

    }

执行结果:

Connected to the target VM, address: '127.0.0.1:61442', transport: 'socket'
chenyi.demo.javaThread.LearnJavaTread进入线程
chenyi.demo.javaThread.LearnJavaTread进入线程
I'm   A
chenyi.demo.javaThread.LearnJavaTread线程执行完毕
chenyi.demo.javaThread.LearnJavaTread进入线程
I'm   C
chenyi.demo.javaThread.LearnJavaTread线程执行完毕
chenyi.demo.javaThread.LearnJavaTread进入线程
I'm   B
chenyi.demo.javaThread.LearnJavaTread线程执行完毕
I'm   D
chenyi.demo.javaThread.LearnJavaTread线程执行完毕
Disconnected from the target VM, address: '127.0.0.1:61442', transport: 'socket'

为什么顺序不是ABCD?
原因是 B线程一直等待A线程,也就是说需要2s后,才会到B线程执行,但是C线程等待B线程是有超时等待的,C只等待了1s,所以C在等待超时后,就先执行了. 然后C开始执行,这个时候B等A执行完毕后,开始执行. 但是D等待C执行完成后,就自动执行了.

书籍参考: <<Java并发编程的艺术>>

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值