• JUC笔记(二) --- Java线程


    2.Java线程:

    2.1开启Java线程:

    new Thread( Runnable {
    	@Overrided
    	public void run(){
    		// 
    	}
    }).start();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    或者自己实现Runnable 接口 , 关于Callable的使用后面讲解

    new Thread(()->{}).start();
    
    • 1

    个人见解:Runnable 配合ThreadPool 非常灵活

    FutureTask类的使用

    // 创建任务对象
    FutureTask task3 = new FutureTask<>(() -> {
     log.debug("hello");
     return 100;
    });
    // 参数1 是任务对象; 参数2 是线程名字,推荐
    new Thread(task3, "t3").start();
    // 主线程阻塞,同步等待 task 执行完毕的结果
    Integer result = task3.get();
    log.debug("结果是:{}", result);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述
    在这里插入图片描述

    可以看到FutureTask同时继承了Runnable接口和Future接口

        /**
         * Waits if necessary for the computation to complete, and then
         * retrieves its result.
         *
         * @return the computed result
         * @throws CancellationException if the computation was cancelled
         * @throws ExecutionException if the computation threw an
         * exception
         * @throws InterruptedException if the current thread was interrupted
         * while waiting
         */
        V get() throws InterruptedException, ExecutionException;
    
        /**
         * Waits if necessary for at most the given time for the computation
         * to complete, and then retrieves its result, if available.
         *
         * @param timeout the maximum time to wait
         * @param unit the time unit of the timeout argument
         * @return the computed result
         * @throws CancellationException if the computation was cancelled
         * @throws ExecutionException if the computation threw an
         * exception
         * @throws InterruptedException if the current thread was interrupted
         * while waiting
         * @throws TimeoutException if the wait timed out
         */
        V get(long timeout, TimeUnit unit)
            throws InterruptedException, ExecutionException, TimeoutException;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    可以看到:get 是同步等待结果,也就是说TaskFuture是同步等待,调用线程会阻塞直到返回结果 除非调用被打断。还可以传入Callable类型的参数

    2.2线程运行原理

    栈与祯栈
    • 每个线程只能有一个活动祯栈,对应正在调用某个方法

    • 栈由多个祯栈组成,对应每个方法调用的所占 内存

    上下文切换(Thread Context Switch

    因为以下一些原因导致 cpu 不再执行当前的线程,转而执行另一个线程的代码

    • 线程的 cpu 时间片用完
    • 垃圾回收
    • 有更高优先级的线程需要运行

    线程自己调用了 sleep、yield、wait、join、park、synchronized、lock 等方法

    当 Context Switch 发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java 中对应的概念

    就是程序计数器(Program Counter Register),它的作用是记住下一条 jvm 指令的执行地址,是线程私有的

    • 状态包括程序计数器、虚拟机栈中每个栈帧的信息,如局部变量、操作数栈、返回地址等
    • Context Switch 频繁发生会影响性能

    这里感觉可以连着计组的指令执行联系起来记忆,JVM还没有深入学习不太能解释

    2.3常用方法:

    start:启动一个新线程,在新的线程运行 run 方法中的代码

    start 方法只是让线程进入就绪,里面代码不一定立刻运行(CPU 的时间片还没分给它)。每个线程对象的

    start方法只能调用一次,如果调用了多次会出现IllegalThreadStateException

    调用run:

    public static void main(String[] args) {
     Thread t1 = new Thread("t1") {
     @Override
     public void run() {
     log.debug(Thread.currentThread().getName());
     FileReader.read(Constants.MP4_FULL_PATH);
     }
     };
     t1.run();
     log.debug("do other things ...");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    会直接在主线程中调用run方法,并不是开启一个新的线程

    run :新线程启动后会调用的方法

    如果在构造 Thread 对象时传递了 Runnable 参数,则线程启动后会调用 Runnable 中的 run 方法,否则默

    认不执行任何操作。但可以创建 Thread 的子类对象,来覆盖默认行为

    join: 等待调用线程运行结束
    • 不带参数:等待调用线程的运行结束
    • 代用参数:等待线程运行结束,最多等待 n 毫秒
    private static void test2() throws InterruptedException {
            Thread t1 = new Thread(() -> {
                sleep(1);
                r1 = 10;
            });
            Thread t2 = new Thread(() -> {
                sleep(2);
                r2 = 20;
            });
            t1.start();
            t2.start();
            long start = System.currentTimeMillis();
            log.debug("join begin");
            t2.join();
            log.debug("t2 join end");
            t1.join();
            log.debug("t1 join end");
            long end = System.currentTimeMillis();
            log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    sleep: 让线程进入阻塞状态
    • 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)
    • 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException
    • 睡眠结束后的线程未必会立刻得到执行- eg.进入阻塞队列后,单核处理机处理其他线程sleep线程需要重新就绪排队
    • 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性
    • 不会释放锁
    yield:让出当前处理机的使用权
    • 调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程,
    • 具体的实现依赖于操作系统的任务调度器
    线程优先级: 为线程分配权重,从而获得更多的时间片
    • 线程优先级会提示**(hint)调度器**优先调度该线程,但它仅仅是一个提示,调度器可以忽略它
    • 如果 cpu 比较忙,那么优先级高的线程会获得更多的时间片,但 cpu 闲时,优先级几乎没作用
    interrupt:打断sleep,wait,join的线程

    sleep,wait,join会让线程进入阻塞状态

    打断sleep状态线程会清空打断状态

    private static void test1() throws InterruptedException {
     Thread t1 = new Thread(()->{
         sleep(1);
     }, "t1");
         t1.start();
         sleep(0.5);
         t1.interrupt();
         log.debug(" 打断状态: {}", t1.isInterrupted());
    }
    
    //  out: 打断状态:false
    private static void test2() throws InterruptedException {
         Thread t2 = new Thread(()->{
         while(true) {
         Thread current = Thread.currentThread();
         boolean interrupted = current.isInterrupted();
         if(interrupted) {
         log.debug(" 打断状态: {}", interrupted);
         break;
         }
         }
         }, "t2");
         t2.start();
         sleep(0.5);
         t2.interrupt();
    }    
        
    // out : 打断状态:true
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    park: 打断 park 线程, 不会清空打断状态
    private static void test4() {
            Thread t1 = new Thread(() -> {
                for (int i = 0; i < 5; i++) {
    //                log.debug("打断状态:{}", Thread.currentThread().isInterrupted());
                    log.debug("park...");
                    LockSupport.park();
                    log.debug("打断状态:{}", Thread.interrupted());
                }
            });
            t1.start();
    
    
            sleep(1);
            t1.interrupt();
            log.info("interrupt");
        }
        out:
    21:34:38.603 c.Test14 [Thread-0] - park...
    21:34:39.604 c.Test14 [main] - interrupt
    21:34:39.604 c.Test14 [Thread-0] - 打断状态:true
    21:34:39.606 c.Test14 [Thread-0] - park...
        
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    在这里插入图片描述

        private static void test4() {
            Thread t1 = new Thread(() -> {
                for (int i = 0; i < 5; i++) {
    //                log.debug("打断状态:{}", Thread.currentThread().isInterrupted());
                    log.debug("park...");
                    LockSupport.park();
                    log.debug("打断状态:{}", Thread.currentThread().isInterrupted());
                }
            });
            t1.start();
    
    
            sleep(1);
            t1.interrupt();
            log.info("interrupt");
        }
    
    out:
    21:36:38.590 c.Test14 [Thread-0] - park...
    21:36:39.604 c.Test14 [main] - interrupt
    21:36:39.604 c.Test14 [Thread-0] - 打断状态:true
    21:36:39.605 c.Test14 [Thread-0] - park...
    21:36:39.605 c.Test14 [Thread-0] - 打断状态:true
    21:36:39.605 c.Test14 [Thread-0] - park...
    21:36:39.605 c.Test14 [Thread-0] - 打断状态:true
    21:36:39.605 c.Test14 [Thread-0] - park...
    21:36:39.605 c.Test14 [Thread-0] - 打断状态:true
    21:36:39.605 c.Test14 [Thread-0] - park...
    21:36:39.606 c.Test14 [Thread-0] - 打断状态:true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    **conclusion:**可以使用 Thread.interrupted() 清除打断状态.

    park:当线程打断状态为false 时会陷入等待状态

    终止模式之两阶段终止模式:

    如何"优雅"地在一个线程终结另外一个线程?

    错误:

    • 使用线程对象的 stop() 方法停止线程 :stop 方法会真正杀死线程,如果这时线程锁住了共享资源,那么当它被杀死后就再也没有机会释放锁,其它线程将永远无法获取锁 . 也就是可能造成死锁等状态
    • 使用 System.exit(int) 方法停止线程:目的仅是停止一个线程,但这种做法会让整个程序都停止

    方案:

    • 通过设置公共信号量进行控制线程的生命
    • 通过interrupt判断位来终结,和1)类似
    import lombok.extern.slf4j.Slf4j;
    
    @Slf4j(topic = "c.TestTwoPhaseTermination")
    public class TestTwoPhaseTermination {
        public static void main(String[] args) throws InterruptedException {
            TPTVolatile t = new TPTVolatile();
            t.start();
    
            Thread.sleep(3500);
            log.debug("stop");
            t.stop();
        }
    }
    @Slf4j(topic = "c.TPTInterrupt")
    class TPTInterrupt {
        private Thread thread;
    
        public void start(){
            thread = new Thread(() -> {
                while(true) {
                    Thread current = Thread.currentThread();
                    if(current.isInterrupted()) {
                        log.debug("料理后事");
                        break;
                    }
                    try {
                        Thread.sleep(1000);
                        log.debug("将结果保存");
                    } catch (InterruptedException e) {
                        current.interrupt();
                    }
    
                }
            },"监控线程");
            thread.start();
        }
    
        public void stop() {
            thread.interrupt();
        }
    }
    @Slf4j(topic = "c.TPTVolatile")
    class TPTVolatile {
        private Thread thread;
        private volatile boolean stop = false;
    
        public void start(){
            thread = new Thread(() -> {
                while(true) {
                    Thread current = Thread.currentThread();
                    if(stop) {
                        log.debug("料理后事");
                        break;
                    }
                    try {
                        Thread.sleep(1000);
                        log.debug("将结果保存");
                    } catch (InterruptedException e) {
                    }
                }
            },"监控线程");
            thread.start();
        }
    
        public void stop() {
            stop = true;
            thread.interrupt();
        }
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    2.4主线程与守护线程

    默认情况下,Java 进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其它非守

    护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。

    @Slf4j(topic = "c.TestDaemon")
    public class TestDaemon {
        public static void main(String[] args) {
            log.debug("开始运行...");
            Thread t1 = new Thread(() -> {
                log.debug("开始运行...");
                sleep(2);
                log.debug("运行结束...");
            }, "daemon");
            // 设置该线程为守护线程
            t1.setDaemon(true);
            t1.start();
    
            sleep(1);
            log.debug("运行结束...");
        }
    }
    
    out:
    08:26:38.123 [main] c.TestDaemon - 开始运行... 
    08:26:38.213 [daemon] c.TestDaemon - 开始运行... 
    08:26:39.215 [main] c.TestDaemon - 运行结束...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    注意

    • 垃圾回收器线程就是一种守护线程

    • Tomcat 中的 Acceptor 和 Poller 线程都是守护线程,所以 Tomcat 接收到 shutdown 命令后,不会等待它们处理完当前请求

    2.5线程的状态

    从OS层面:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IkDd3EOW-1666872901513)(assets/image-20221025214719430.png)]

    从Java层面:

    在这里插入图片描述

    Object.wait() : 放弃当前持有的锁进入waiting状态

  • 相关阅读:
    开发技术-前后端(vue+java)加密传输数据
    【数据结构】——顺序表(增删查改)
    程序包org.apache.commons.XXX不存在
    java计算机毕业设计高校请假管理系统MyBatis+系统+LW文档+源码+调试部署
    大数据面试题之MapReduce(1)
    Python搭建自己的VPN
    打字机效果的实现与应用
    使用 GRPC C++ API连接Java的RPC服务
    数字电路基础02(用2选1MUX实现与、或、非、与非、或非、异或、同或)
    2024年宝鸡市国家级、省级、市级科技企业孵化器申报奖励补贴标准及申报条件
  • 原文地址:https://blog.csdn.net/qq_57115378/article/details/127559886