您当前的位置: 首页 >  Java

Dongguo丶

暂无认证

  • 2浏览

    0关注

    472博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

Java线程相关知识

Dongguo丶 发布时间:2021-09-14 10:51:33 ,浏览量:2

创建和运行线程 方法一,直接使用 Thread
package com.dongguo.thread;

/**
 * @author Dongguo
 * @date 2021/9/10 0010-21:55
 * @description:
 */
public class ThreadDemo {
    public static void main(String[] args) {
        // 构造方法的参数是给线程指定名字,推荐
        Thread t1 = new Thread("t1") {
            @Override
            // run 方法内实现了要执行的任务
            public void run() {
                System.out.println(Thread.currentThread().getName()+" run");
            }
        };
        t1.start();
    }
}
运行结果
t1 run
方法二,使用 Runnable 配合 Thread

把【线程】和【任务】(要执行的代码)分开 Thread 代表线程 Runnable 可运行的任务(线程要执行的代码)

package com.dongguo.thread;

/**
 * @author Dongguo
 * @date 2021/9/10 0010-21:55
 * @description:
 */
public class ThreadDemo1 {
    public static void main(String[] args) {
        // 创建任务对象
        Runnable task = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+" run");
            }
        };
        // 参数1 是任务对象; 参数2 是线程名字,推荐
        Thread t1 = new Thread(task, "t1");
        t1.start();

        //Lambda表达式
        Thread t2 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " run");
        }, "t2");
        t2.start();
    }
}
运行结果
t1 run
t2 run
方法三,FutureTask 配合 Thread

FutureTask 能够接收 Callable 类型的参数,用来处理有返回结果的情况

image-20210904070823338

package com.dongguo.thread;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * @author Dongguo
 * @date 2021/9/10 0010-21:55
 * @description:
 */
public class ThreadDemo2 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 创建任务对象
        FutureTask task = new FutureTask(() -> {
            System.out.println(Thread.currentThread().getName() + " run");
            return 100;
        });
        // 参数1 是任务对象; 参数2 是线程名字,推荐
        new Thread(task, "t1").start();
        // 主线程阻塞,同步等待 task 执行完毕的结果
        Integer result = task.get();
        System.out.println("结果是:"+ result);
    }
}
运行结果:
t1 run
结果是:100
方法四,线程池
package com.dongguo.thread;

import java.util.concurrent.*;

/**
 * @author Dongguo
 * @date 2021/9/10 0010-21:55
 * @description:
 */
public class ThreadDemo3 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 创建自定义线程池
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                2,
                5,
                2L,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );
        try {
            pool.execute(()->{
                System.out.println(Thread.currentThread().getName() + " run");
            });
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            pool.shutdown();
        }
    }
}
运行结果
pool-1-thread-1 run
观察多个线程同时运行
package com.dongguo.thread;

/**
 * @author Dongguo
 * @date 2021/9/10 0010-22:58
 * @description: 演示多个线程并发交替执行
 */
public class MultiThreadDemo {
    public static void main(String[] args) {
        new Thread(()->{
           while (true){
               System.out.println(Thread.currentThread().getName() + " run");
           }
        },"t1").start();

        new Thread(()->{
            while (true){
                System.out.println(Thread.currentThread().getName() + " run");
            }
        },"t2").start();
    }
}

运行结果

image-20210910230212732

可以看到两个线程交替执行

交替执行 谁先谁后,不由我们控制 ,是由操作系统的任务调度器决定

查看进程线程的方法 windows

​ 1.任务管理器可以查看进程和线程数,也可以用来杀死进程

image-20210910231035893

​ 2.cmd命令行

tasklist 查看进程

image-20210911072631175

taskkill 杀死进程

/F 强制

image-20210911073129824

linux

ps -fe 查看所有进程

image-20210911073428608

ps -fT -p 查看某个进程(PID)的所有线程 kill 杀死进程

image-20210911073458270

top 动态的显示CPU使用的情况 ,按大写 H 切换是否显示线程 top -H -p 查看某个进程(PID)的所有线程

Java

jps 命令查看所有 Java 进程 jstack 查看某个 Java 进程(PID)的所有线程状态

image-20210911073903487

jconsole 来查看某个 Java 进程中线程的运行情况(图形界面)

jconsole 本地监控配置

直接启动

jconsole 远程监控配置(监控linux环境) 需要以如下方式运行你的 java 类

java -Djava.rmi.server.hostname=`ip地址` -Dcom.sun.management.jmxremote -
Dcom.sun.management.jmxremote.port=`连接端口` -Dcom.sun.management.jmxremote.ssl=是否安全连接 -Dcom.sun.management.jmxremote.authenticate=是否认证 java类

修改 /etc/hosts 文件将 127.0.0.1 映射至主机名

image-20210911074442506

如果要认证访问,还需要做如下步骤 复制 jmxremote.password 文件 修改 jmxremote.password 和 jmxremote.access 文件的权限为 600 即文件所有者可读写 连接时填入 controlRole(用户名),R&D(密码)

这里就不设置认证和安全连接。 不同输入用户名和口令就可以直接连接

image-20210911074610024

image-20210911074705530

注意关闭linux防火墙

image-20210911074736958

线程运行的原理 栈与栈帧

Java Virtual Machine Stacks (Java 虚拟机栈) 我们都知道 JVM 中由堆、栈、方法区所组成,其中栈内存是给谁用的呢?其实就是线程,每个线程启动后,虚拟 机就会为其分配一块栈内存。 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法

查看一个线程执行的情况

package com.dongguo.thread;

/**
 * @author Dongguo
 * @date 2021/9/11 0011-7:51
 * @description:
 */
public class FramesDemo {
    public static void main(String[] args) {
        m1(1);
    }

    private static void m1(int x) {
        int y = x + 1;
        Object object = m2();
        System.out.println(object);
    }

    private static Object m2() {
        Object o = new Object();
        return o;
    }
}

在m1(1)出打上断点以debug方式运行

image-20210911075721327

Frames就是栈帧 此时main线程就是一个栈帧入栈

点击下一步

image-20210911075931592

m1栈帧入栈

运行进入m2,m2栈帧入栈

image-20210911080115226

当m2执行完后,跳回m1,m2出栈

image-20210911080245813

m1执行完,跳回main,m1出栈

image-20210911080439492

代码执行完 main出栈

方法的调用就是一个个栈帧入栈到出栈的过程

image-20210911080851659

查看多个线程执行的情况(t1、main)

package com.dongguo.thread;

/**
 * @author Dongguo
 * @date 2021/9/11 0011-7:51
 * @description:
 */
public class FramesDemo {
    public static void main(String[] args) {
        new Thread(()->{
            m1(2);
        },"t1").start();
        m1(1);
    }

    private static void m1(int x) {
        int y = x + 1;
        Object object = m2();
        System.out.println(object);
    }

    private static Object m2() {
        Object o = new Object();
        return o;
    }
}

在main、t1调用m1方法打上断点

将debug模式改为线程模式 右键断点处,不然就无法看到线程间的调用过程

image-20210911081418171

debug走起

运行到main线程的m1()

image-20210911081557132

点开下拉列表

image-20210911081645260

选择t1线程,也在m1方法处 。两个线程都处于running(就绪、运行都属于running)且互不影响

image-20210911081720999

栈是线程私有的,栈帧以线程为单位,不同线程的栈帧之间相互独立。

线程上下文切换(Thread Context Switch)

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

​ 线程的cpu 时间片用完 ​ 垃圾回收 ​ 有更高优先级的线程需要运行 ​ 线程自己调用了 sleep、yield、wait、join、park、synchronized、lock 等方法 当 Context Switch 发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java 中对应的概念 就是程序计数器(Program Counter Register),它的作用是记住下一条 jvm 指令的执行地址,是线程私有的 ​ 状态包括程序计数器、虚拟机栈中每个栈帧的信息,如局部变量、操作数栈、返回地址等 ​ Context Switch 频繁发生会影响性能

即使是单核处理器也支持多线程执行代码,CPU通过给每个线程分配CPU时间片来实现这个机制。时间片是CPU分配给各个线程的时间,因为时间片非常短,所以CPU通过不停地切换线程执行,让我们感觉多个线程是同时执行的,时间片一般是几十毫秒(ms)。CPU通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务。但是,在切换前会保存上一个任务的状态,以便下次切换回这个任务时,可以再加载这个任务的状态。所以任务从保存到再加载的过程就是一次上下文切换。

这就像我们同时读两本书,当我们在读一本英文的技术书时,发现某个单词不认识,于是便打开中英文字典,但是在放下英文技术书之前,大脑必须先记住这本书读到了多少页的第多少行,等查完单词之后,能够继续读这本书。这样的切换是会影响读书效率的,同样上下文切换也会影响多线程的执行速度。

常见方法 方法名static功能说明注意start()启动一个新线 程,在新的线程 运行 run 方法 中的代码start 方法只是让线程进入就绪,里面代码不一定立刻 运行(CPU 的时间片还没分给它)。每个线程对象的 start方法只能调用一次,如果调用了多次会出现 IllegalThreadStateExceptionrun()新线程启动后会 调用的方法如果在构造 Thread 对象时传递了 Runnable 参数,则 线程启动后会调用 Runnable 中的 run 方法,否则默 认不执行任何操作。但可以创建 Thread 的子类对象, 来覆盖默认行为join()等待线程运行结 束join(long n)等待线程运行结 束,最多等待 n 毫秒getId()获取线程长整型 的 idid 唯一getName()获取线程名setName(String)修改线程名getPriority()获取线程优先级setPriority(int)修改线程优先级java中规定线程优先级是1~10 的整数,较大的优先级 能提高该线程被 CPU 调度的机率getState()获取线程状态Java 中线程状态是用 6 个 enum 表示,分别为: NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATEDisInterrupted()判断是否被打 断,不会清除 打断标记isAlive()线程是否存活 (还没有运行完 毕)interrupt()打断线程如果被打断线程正在 sleep,wait,join 会导致被打断 的线程抛出 InterruptedException,并清除 ;如果打断的正在运行的线程,则会设置 ;park 的线程被打断,也会设置 打断标记interrupted()static判断当前线程是 否被打断会清除 打断标记currentThread()static获取当前正在执 行的线程sleep(long n)static让当前执行的线 程休眠n毫秒, 休眠时让出 cpu 的时间片给其它 线程yield()static提示线程调度器 让出当前线程对 CPU的使用主要是为了测试和调试 start 与 run

start表示启动一个线程,run则是启动线程后,执行的代码

直接调用 run 是在主线程中执行了 run,没有启动新的线程 使用 start 是启动新的线程,通过新的线程间接执行 run 中的代码

一个线程 不能多次调用start 方法。

直接调用 run
package com.dongguo.thread;

import java.util.concurrent.*;

/**
 * @author Dongguo
 * @date 2021/9/10 0010-21:55
 * @description:
 */
public class ThreadDemo4 {
    public static void main(String[] args)  {
        Thread t1 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " run1");
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t1");
        t1.run();
        System.out.println(Thread.currentThread().getName() + " run2");
    }
}
运行结果
main run1
main run2

这里是main线程调用了run方法而非t1线程(t1还没有start创建)

调用 start

将上述代码的 t1.run() 改为 t1.start()

package com.dongguo.thread;

import java.util.concurrent.*;

/**
 * @author Dongguo
 * @date 2021/9/10 0010-21:55
 * @description:
 */
public class ThreadDemo4 {
    public static void main(String[] args)  {
        Thread t1 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " run1");
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t1");
        t1.start();
        System.out.println(Thread.currentThread().getName() + " run2");
    }
}

运行结果
main run2
t1 run1

t1线程和main线程并行执行

sleep 与 yield sleep
  1. 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)

  2. 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException

  3. 睡眠结束后的线程未必会立刻得到执行

  4. 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性

package com.dongguo.thread;

import java.util.concurrent.TimeUnit;

/**
 * @author Dongguo
 * @date 2021/9/10 0010-21:55
 * @description:
 */
public class ThreadDemo5 {
    public static void main(String[] args)  {
        Thread t1 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " run1");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t1");
        t1.start();
        System.out.println(t1.getState());//获取t1线程的状态
        try {
            TimeUnit.SECONDS.sleep(1);//睡眠1秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " run2");
        System.out.println(t1.getState());//获取t1线程的状态
        t1.interrupt();//打断正在睡眠的线程
        System.out.println(t1.getState());//获取t1线程的状态
    }
}
运行结果
RUNNABLE
t1 run1
main run2
TIMED_WAITING
TERMINATED
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.dongguo.thread.ThreadDemo5.lambda$main$0(ThreadDemo5.java:15)
	at java.lang.Thread.run(Thread.java:748)

RUNNABLE 运行状态

TIMED_WAITING 超时等待

TERMINATED 终止

yield
  1. 调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程

  2. 具体的实现依赖于操作系统的任务调度器 。 线程放弃执行的机会,有可能任务调度器还会调度这个线程

线程优先级

线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器会根据情况决定调度哪个线程, 如果 cpu 比较忙,那么优先级高的线程会获得更多的时间片,但 cpu 闲时,优先级几乎没作用

	/**
     * 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; //最大优先级

/**
 * Changes the priority of this thread.
 * 

* First the checkAccess method of this thread is called * with no arguments. This may result in throwing a * SecurityException. *

* Otherwise, the priority of this thread is set to the smaller of * the specified newPriority and the maximum permitted * priority of the thread's thread group. * * @param newPriority priority to set this thread to * @exception IllegalArgumentException If the priority is not in the * range MIN_PRIORITY to * MAX_PRIORITY. * @exception SecurityException if the current thread cannot modify * this thread. * @see #getPriority * @see #checkAccess() * @see #getThreadGroup() * @see #MAX_PRIORITY * @see #MIN_PRIORITY * @see ThreadGroup#getMaxPriority() */ public final void setPriority(int newPriority) { ThreadGroup g; checkAccess(); if (newPriority > MAX_PRIORITY || newPriority g.getMaxPriority()) { newPriority = g.getMaxPriority(); } setPriority0(priority = newPriority); } }

创建两个线程,每个线程死循环count++。理想情况下输出count的值应该是差不多的

package com.dongguo.thread;

import java.util.concurrent.TimeUnit;

/**
 * @author Dongguo
 * @date 2021/9/10 0010-21:55
 * @description:
 */
public class ThreadDemo6 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            int count = 0;
            for (; ; ) {
                System.out.println(Thread.currentThread().getName() + "----" + count++);
            }
        }, "t1");
        Thread t2 = new Thread(() -> {
            int count = 0;
            for (; ; ) {
//                Thread.yield();
                System.out.println(Thread.currentThread().getName() + "----" + count++);
            }
        }, "t2");

//        t1.setPriority(Thread.MIN_PRIORITY);
//        t2.setPriority(Thread.MIN_PRIORITY);
        t1.start();
        t2.start();
    }
}

image-20210911102534496

如果t2线程Thread.yield();让出运行的机会,

package com.dongguo.thread;

import java.util.concurrent.TimeUnit;

/**
 * @author Dongguo
 * @date 2021/9/10 0010-21:55
 * @description:
 */
public class ThreadDemo6 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            int count = 0;
            for (; ; ) {
                System.out.println(Thread.currentThread().getName() + "----" + count++);
            }
        }, "t1");
        Thread t2 = new Thread(() -> {
            int count = 0;
            for (; ; ) {
                Thread.yield();
                System.out.println(Thread.currentThread().getName() + "----" + count++);
            }
        }, "t2");

//        t1.setPriority(Thread.MIN_PRIORITY);
//        t2.setPriority(Thread.MIN_PRIORITY);
        t1.start();
        t2.start();
    }
}

image-20210911102731873

设置两个线程不同的优先级

        t1.setPriority(Thread.MIN_PRIORITY);
        t2.setPriority(Thread.MAX_PRIORITY);
package com.dongguo.thread;

import java.util.concurrent.TimeUnit;

/**
 * @author Dongguo
 * @date 2021/9/10 0010-21:55
 * @description:
 */
public class ThreadDemo6 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            int count = 0;
            for (; ; ) {
                System.out.println(Thread.currentThread().getName() + "----" + count++);
            }
        }, "t1");
        Thread t2 = new Thread(() -> {
            int count = 0;
            for (; ; ) {
//                Thread.yield();
                System.out.println(Thread.currentThread().getName() + "----" + count++);
            }
        }, "t2");

        t1.setPriority(Thread.MIN_PRIORITY);
        t2.setPriority(Thread.MAX_PRIORITY);
        t1.start();
        t2.start();
    }
}

image-20210911103115418

join 方法详解

为什么需要 join 下面的代码执行,打印 r 是什么?

package com.dongguo.thread;

import java.util.concurrent.TimeUnit;

/**
 * @author Dongguo
 * @date 2021/9/10 0010-21:55
 * @description:
 */
public class ThreadDemo7 {
    static int r = 0;

    public static void main(String[] args) {
        try {
            method1();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void method1() throws InterruptedException {
        Thread t1 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " run1");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            r = 10;
        }, "t1");
        t1.start();
//        t1.join();
        System.out.println(Thread.currentThread().getName() + "结果为:" + r);
    }
}
运行结果
main结果为:0
t1 run1

分析 因为主线程和线程 t1 是并行执行的,t1 线程需要 1 秒之后才能算出 r=10 而主线程一开始就要打印 r 的结果,所以只能打印出 r=0 解决方法 main线程也 sleep 行不行?为什么?

​ 可以,但是main线程不知道t1执行结束到底需要多长时间,所以不建议使用sleep

用 join,加在 t1.start() 之后即可

package com.dongguo.thread;

import java.util.concurrent.TimeUnit;

/**
 * @author Dongguo
 * @date 2021/9/10 0010-21:55
 * @description:
 */
public class ThreadDemo7 {
    static int r = 0;

    public static void main(String[] args) {
        try {
            method1();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void method1() throws InterruptedException {
        Thread t1 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " run1");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            r = 10;
        }, "t1");
        t1.start();
        t1.join();
        System.out.println(Thread.currentThread().getName() + "结果为:" + r);
    }
}
运行结果
t1 run1
main结果为:10

t1.join(); main线程必须等待t1线程运行结束才可以执行接下来的任务

等待多个结果

问,下面代码 cost 大约多少秒?

package com.dongguo.thread;

import java.util.concurrent.TimeUnit;

/**
 * @author Dongguo
 * @date 2021/9/10 0010-21:55
 * @description:
 */
public class ThreadDemo8 {
    static int r1 = 0;
    static int r2 = 0;

    public static void main(String[] args) {
        try {
            method1();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private static void method1() throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            r1 = 10;
        });
        Thread t2 = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            r2 = 20;
        });
        long start = System.currentTimeMillis();
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        long end = System.currentTimeMillis();
        System.out.println("r1:" + r1 + "  r2:" + r2);
        System.out.println("总共花费" + (end - start));
    }
}
运行结果
r1:10  r2:20
总共花费2001

分析如下 第一个 join:main线程等待 t1 时, t2 并没有停止, 而在运行 第二个 join:1s 后, t1线程执行完毕, t2 也运行了 1s, 因此main也只需再等待 1s

所以main线程一共等待了2s

如果颠倒两个 join 呢?

package com.dongguo.thread;

import java.util.concurrent.TimeUnit;

/**
 * @author Dongguo
 * @date 2021/9/10 0010-21:55
 * @description:
 */
public class ThreadDemo8 {
    static int r1 = 0;
    static int r2 = 0;

    public static void main(String[] args) {
        try {
            method1();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private static void method1() throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            r1 = 10;
        });
        Thread t2 = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            r2 = 20;
        });
        long start = System.currentTimeMillis();
        t1.start();
        t2.start();
        t2.join();
        t1.join();
        long end = System.currentTimeMillis();
        System.out.println("r1:" + r1 + "  r2:" + r2);
        System.out.println("总共花费" + (end - start));
    }
}
运行结果
r1:10  r2:20
总共花费2001

分析如下 第一个 join:main线程等待 t12时, t1 并没有停止, 而在运行 第二个 join:2s 后, t2线程执行完毕, t1 在1s之前就已经执行完毕了, 因此main只需等待 2s

所以main线程一共等待了2s

有时效的 join(time)

等够时间

package com.dongguo.thread;

import java.util.concurrent.TimeUnit;

/**
 * @author Dongguo
 * @date 2021/9/10 0010-21:55
 * @description:
 */
public class ThreadDemo9 {
    static int r = 0;

    public static void main(String[] args) {
        try {
            method1();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void method1() throws InterruptedException {
        Thread t1 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " run1");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            r = 10;
        }, "t1");
        t1.start();
        t1.join(2000);//等待两秒
        System.out.println(Thread.currentThread().getName() + "结果为:" + r);
    }
}
运行结果
t1 run1
main结果为:10

没等够时间

package com.dongguo.thread;

import java.util.concurrent.TimeUnit;

/**
 * @author Dongguo
 * @date 2021/9/10 0010-21:55
 * @description:
 */
public class ThreadDemo9 {
    static int r = 0;

    public static void main(String[] args) {
        try {
            method1();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void method1() throws InterruptedException {
        Thread t1 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " run1");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            r = 10;
        }, "t1");
        t1.start();
        t1.join(200);//等待200毫秒
        System.out.println(Thread.currentThread().getName() + "结果为:" + r);
    }
}
运行结果
t1 run1
main结果为:0
原理
public final void join() throws InterruptedException {
    join(0);
}
public final synchronized void join(long millis)
throws InterruptedException {
    long base = System.currentTimeMillis();
    long now = 0;

    if (millis 0
        while (isAlive()) {
            long delay = millis - now;
            if (delay  {
            //isDaemon()判断该线程是否是一个守护线程
            System.out.println(Thread.currentThread().getName() + "---" + Thread.currentThread().isDaemon());
            while (true){
                //死循环
            }
        },"thread1");
        thread.start();

        System.out.println(Thread.currentThread().getName()+"执行结束");
    }
}

用户自定义的线程默认是用户线程,

可以用Thread.currentThread().isDaemon()判断 true为守护线程 false为用户线程

因为写了一个死循环,所以thread1线程没有退出执行,则jvm作为守护线程也仍在运行。

img

package com.dongguo.n2;

/**
 * @author Dongguo
 * @date 2021/8/23 0023-18:34
 * @description: 用户线程和守护线程
 */
public class Main {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            //isDaemon()判断该线程是否是一个守护线程
            System.out.println(Thread.currentThread().getName() + "---" + Thread.currentThread().isDaemon());
            while (true){
                //死循环
            }
        },"thread1");
        thread.setDaemon(true);//设置为守护线程,需要在start()方法之前进行
        thread.start();

        System.out.println(Thread.currentThread().getName()+"执行结束");
    }
}

thread.setDaemon(true)将thread1线程设置为守护线程,设置守护线程,需要在start()方法之前进行

因为thread1和jvm都是守护线程 ,此时没有其他的用户线程,所以thread1线程和jvm会相继退出

当程序中所有用户线程执行完毕之后,不管守护线程是否结束,系统都会自动退出

如果用户线程全部结束了,意味着程序需要完成的业务操作已经结束了,系统可以退出 了。所以当系统只剩下守护进程的时候,java虚拟机会自动退出

img

线程的状态 五种

这是从 操作系统 层面来描述的

image-20210911124429996

【初始状态】仅是在语言层面创建了线程对象,还未与操作系统线程关联 【可运行状态】(就绪状态)指该线程已经被创建(与操作系统线程关联),可以由 CPU 调度执行 【运行状态】指获取了 CPU 时间片运行中的状态 当 CPU 时间片用完,会从【运行状态】转换至【可运行状态】,会导致线程的上下文切换 【阻塞状态】 如果调用了阻塞 API,如 BIO 读写文件,这时该线程实际不会用到 CPU,会导致线程上下文切换,进入 【阻塞状态】 等 BIO 操作完毕,会由操作系统唤醒阻塞的线程,转换至【可运行状态】 与【可运行状态】的区别是,对【阻塞状态】的线程来说只要它们一直不唤醒,调度器就一直不会考虑 调度它们 【终止状态】表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态

六种

这是从 Java API 层面来描述的

Java中线程的状态分为6种

  1. 初始(NEW):新创建了一个线程对象(new一个实例)出来,线程就进入了初始状态,还没有调用start()方法。

  2. 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。

    2.1. 就绪状态(RUNNABLE之READY) 就绪状态只是说你资格运行,如果调度程序没有挑选到你,你就永远是就绪状态。 1调用线程的start()方法,该状态的线程位于可运行线程池中,等待被线程调度选中获取CPU的使用权(CPU时间片),此时处于就绪状态(ready)。 2当前线程sleep()方法结束或其他线程join()结束,等待用户输入完毕,某个线程拿到对象锁,这些线程也将进入就绪状态。 3当前线程时间片用完了,调用当前线程的yield()方法,当前线程进入就绪状态。 2.2. 运行中状态(RUNNABLE之RUNNING) 线程调度程序从可运行池中选择一个线程作为当前线程 时线程所处的状态。

    就绪状态的线程在获得CPU时间片后变为运行中状态(running)。这也是线程进入运行状态的唯一的一种方式。

  3. 阻塞(BLOCKED):表示线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态。

    运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入**“锁池”**(同步队列)中。

  4. 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。

    运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”(等待队列)中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒,(不见不散),否则会处于无限期等待的状态。

  5. 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。

    运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(过时不候)

  6. 终止(TERMINATED):当线程的run()方法完成时,或者主线程的main()方法完成或者因异常退出了run()方法,该线程结束生命周期。

    线程一旦终止了,就不能复生。在一个终止的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。

这6种状态定义在Thread类的State枚举中,可查看源码进行一一对应。

Thread.java
    
public enum State {
        /**
         * Thread state for a thread which has not yet started.
         */
        NEW,

        /**
         * Thread state for a runnable thread.  A thread in the runnable
         * state is executing in the Java virtual machine but it may
         * be waiting for other resources from the operating system
         * such as processor.
         */
        RUNNABLE,

        /**
         * Thread state for a thread blocked waiting for a monitor lock.
         * A thread in the blocked state is waiting for a monitor lock
         * to enter a synchronized block/method or
         * reenter a synchronized block/method after calling
         * {@link Object#wait() Object.wait}.
         */
        BLOCKED,

        /**
         * Thread state for a waiting thread.
         * A thread is in the waiting state due to calling one of the
         * following methods:
         * 
    *
  • {@link Object#wait() Object.wait} with no timeout
  • *
  • {@link #join() Thread.join} with no timeout
  • *
  • {@link LockSupport#park() LockSupport.park}
  • *
* *

A thread in the waiting state is waiting for another thread to * perform a particular action. * * For example, a thread that has called Object.wait() * on an object is waiting for another thread to call * Object.notify() or Object.notifyAll() on * that object. A thread that has called Thread.join() * is waiting for a specified thread to terminate. */ WAITING, /** * Thread state for a waiting thread with a specified waiting time. * A thread is in the timed waiting state due to calling one of * the following methods with a specified positive waiting time: *

    *
  • {@link #sleep Thread.sleep}
  • *
  • {@link Object#wait(long) Object.wait} with timeout
  • *
  • {@link #join(long) Thread.join} with timeout
  • *
  • {@link LockSupport#parkNanos LockSupport.parkNanos}
  • *
  • {@link LockSupport#parkUntil LockSupport.parkUntil}
  • *
*/ TIMED_WAITING, /** * Thread state for a terminated thread. * The thread has completed execution. */ TERMINATED; }

image-20210903091413528

注意,Java API 层面的 RUNNABLE 状态涵盖了 操作系统 层面的 【可运行状态】、【运行状态】和【阻塞状态】(由于 BIO 导致的线程阻塞,在 Java 里无法区分,仍然认为 是可运行) BLOCKED , WAITING , TIMED_WAITING 都是 Java API 层面对【阻塞状态】的细分

等待队列

image-20210914104039326

WaitSet就是等待队列

EntryList就是同步队列

Owner就是对象锁持有者

当前线程想调用对象A的同步方法时,发现对象A的锁被别的线程占有,此时当前线程进入同步队列。简言之,同步队列里面放的都是想争夺对象锁的线程。 当一个线程1被另外一个线程2唤醒时,1线程进入同步队列,去争夺对象锁。 同步队列是在同步的环境下才有的概念,一个对象对应一个同步队列。 线程等待时间到了或被notify/notifyAll唤醒后,会进入同步队列竞争锁,如果获得锁,进入RUNNABLE状态,否则进入BLOCKED状态等待获取锁。

  • 调用obj的wait(), notify()方法前,必须获得obj锁,也就是必须写在synchronized(obj) 代码段内。
  • 与等待队列相关的步骤和图

img

1.线程1获取对象A的锁,正在使用对象A。 2.线程1调用对象A的wait()方法。 3.线程1释放对象A的锁,并马上进入等待队列。 4.锁池里面的对象争抢对象A的锁。 5.线程5获得对象A的锁,进入synchronized块,使用对象A。 6.线程5调用对象A的notifyAll()方法,唤醒所有线程,所有线程进入同步队列。若线程5调用对象A的notify()方法,则唤醒一个线程,不知道会唤醒谁,被唤醒的那个线程进入同步队列。 7.notifyAll()方法所在synchronized结束,线程5释放对象A的锁。 8.同步队列的线程争抢对象锁,但线程1什么时候能抢到就不知道了。

几个方法的比较

Thread.sleep(long millis),一定是当前线程调用此方法,当前线程进入TIMED_WAITING状态,但不释放对象锁,睡眠millis后线程自动苏醒进入就绪状态。

作用:给其它线程执行机会的最佳方式。

Thread.yield(),一定是当前线程调用此方法,当前线程放弃获取的CPU时间片,但不释放锁资源,由运行状态变为就绪状态,让OS操作系统再次选择线程。

作用:让相同优先级的线程轮流执行,但并不保证一定会轮流执行。实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield()不会导致阻塞。该方法与sleep()类似,只是不能由用户指定暂停多长时间。

thread.join()/thread.join(long millis),当前线程里调用其它线程t的join方法,当前线程进入WAITING/TIMED_WAITING状态,当前线程不会释放已经持有的对象锁。线程t执行完毕或者millis时间到,当前线程一般情况下进入RUNNABLE状态,也有可能进入BLOCKED状态(因为join是基于wait实现的)。

obj.wait(),当前线程调用对象的wait()方法,当前线程释放对象锁,进入等待队列。依靠notify()/notifyAll()唤醒或者wait(long timeout) timeout时间到自动唤醒。

obj.notify()唤醒在此对象监视器上等待的单个线程,选择是任意性的。notifyAll()唤醒在此对象监视器上等待的所有线程。

LockSupport.park()/LockSupport.parkNanos(long nanos),LockSupport.parkUntil(long deadlines), 当前线程进入WAITING/TIMED_WAITING状态。对比wait方法,不需要获得锁就可以让线程进入WAITING/TIMED_WAITING状态,需要通过LockSupport.unpark(Thread thread)唤醒。

大佬问我: notify()是随机唤醒线程么?

Java等待唤醒机制wait/notify深入解析

线程状态的转换

image-20210912123914984

假设有线程 Thread t

情况 1 NEW --> RUNNABLE

当调用 t.start() 方法时,由 NEW --> RUNNABLE

情况 2 RUNNABLE WAITING

t 线程用 synchronized(obj) 获取了对象锁后 调用 obj.wait() 方法时,t 线程从 RUNNABLE --> WAITING

调用 obj.notify() , obj.notifyAll() , t.interrupt() 时被唤醒 竞争锁成功,t 线程从 WAITING --> RUNNABLE 竞争锁失败,t 线程从 WAITING --> BLOCKED

package com.dongguo.waitnotify;

import java.util.concurrent.TimeUnit;

/**
 * @author Dongguo
 * @date 2021/9/12 0012-13:36
 * @description:
 */
public class WaitNotifyDemo {
    static Thread t1, t2;

    public static void main(String[] args) {
        Object obj = new Object();
        t1 = new Thread(() -> {
            synchronized (obj) {
                try {
                    obj.wait();
                    System.out.println("-----------");
                    System.out.println(t1 + "被notifyAll后 main线程释放锁后,状态" + t1.getState());
                    System.out.println(t2 + "被notifyAll后 main线程释放锁后,状态" + t2.getState());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "t1");
        t1.start();

        t2 = new Thread(() -> {
            synchronized (obj) {
                try {
                    obj.wait();
                    System.out.println("-----------");
                    System.out.println(t2 + "被notifyAll后 main线程释放锁后,状态" + t2.getState());
                    System.out.println(t1 + "被notifyAll后 main线程释放锁后,状态" + t1.getState());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "t2");
        t2.start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(t1 + "wait后状态" + t1.getState());
        System.out.println(t2 + "wait后状态" + t2.getState());
        synchronized (obj) {
            obj.notifyAll();
            System.out.println("-----------");
            System.out.println(t1 + "被notifyAll后 main线程没有释放锁,状态" + t1.getState());
            System.out.println(t2 + "被notifyAll后 main线程没有释放锁,状态" + t2.getState());
        }
    }
}
运行结果
Thread[t1,5,main]wait后状态WAITING
Thread[t2,5,main]wait后状态WAITING
-----------
Thread[t1,5,main]被notifyAll后 main线程没有释放锁,状态BLOCKED
Thread[t2,5,main]被notifyAll后 main线程没有释放锁,状态BLOCKED
-----------
Thread[t2,5,main]被notifyAll后 main线程释放锁后,状态RUNNABLE
Thread[t1,5,main]被notifyAll后 main线程释放锁后,状态BLOCKED
-----------
Thread[t1,5,main]被notifyAll后 main线程释放锁后,状态RUNNABLE
Thread[t2,5,]被notifyAll后 main线程释放锁后,状态TERMINATED

t1、t2执行obj.wait()后状态都是WAITING状态

当主线程执行 obj.notifyAll(),由于main线程没有释放obj锁,t1、t2线程都处于BLOCKED状态

当main线程释放obj锁时,t1、t2竞争obj锁,t2线程竞争锁成功处于RUNNABLE状态,t1线程竞争失败处于BLOCKED状态

当t2线程释放锁,t1线程就获得锁。此时t1线程处于RUNNABLE状态,t2线程已经执行完毕处于TERMINATED状态

情况 3 RUNNABLE WAITING

当前线程调用 t.join() 方法时,当前线程从 RUNNABLE --> WAITING 注意是当前线程在t 线程对象的监视器上等待 t 线程运行结束,或调用了当前线程的 interrupt() 时,当前线程从 WAITING --> RUNNABLE

情况 4 RUNNABLE WAITING

当前线程调用 LockSupport.park() 方法会让当前线程从 RUNNABLE --> WAITING 调用 LockSupport.unpark(目标线程) 或调用了线程 的 interrupt() ,会让目标线程从 WAITING --> RUNNABLE

情况 5 RUNNABLE TIMED_WAITING

t 线程用 synchronized(obj) 获取了对象锁后 调用 obj.wait(long n) 方法时,t 线程从 RUNNABLE --> TIMED_WAITING t 线程等待时间超过了 n 毫秒,或调用 obj.notify() , obj.notifyAll() , t.interrupt() 时 竞争锁成功,t 线程从 TIMED_WAITING --> RUNNABLE 竞争锁失败,t 线程从 TIMED_WAITING --> BLOCKED

情况 6 RUNNABLE TIMED_WAITING

当前线程调用 t.join(long n) 方法时,当前线程从 RUNNABLE --> TIMED_WAITING 注意是当前线程在t 线程对象的监视器上等待 当前线程等待时间超过了 n 毫秒,或t 线程运行结束,或调用了当前线程的 interrupt() 时,当前线程从 TIMED_WAITING --> RUNNABLE

情况 7 RUNNABLE TIMED_WAITING

当前线程调用 Thread.sleep(long n) ,当前线程从 RUNNABLE --> TIMED_WAITING 当前线程等待时间超过了 n 毫秒,当前线程从 TIMED_WAITING --> RUNNABLE

情况 8 RUNNABLE TIMED_WAITING

当前线程调用 LockSupport.parkNanos(long nanos) 或 LockSupport.parkUntil(long millis) 时,当前线 程从 RUNNABLE --> TIMED_WAITING 调用 LockSupport.unpark(目标线程) 或调用了线程 的 interrupt() ,或是等待超时,会让目标线程从 TIMED_WAITING–> RUNNABLE

情况 9 RUNNABLE BLOCKED

t 线程用 synchronized(obj) 获取了对象锁时如果竞争失败,从 RUNNABLE --> BLOCKED 持 obj 锁线程的同步代码块执行完毕,会唤醒该对象上所有 BLOCKED 的线程重新竞争,如果其中 t 线程竞争 成功,从 BLOCKED --> RUNNABLE ,其它失败的线程仍然 BLOCKED

情况 10 RUNNABLE TERMINATED

当前线程所有代码运行完毕,进入 TERMINATED

wait/sleep的区别

sleep() 方法使当前线程进入阻塞状态,线程休眠但是不释放同步锁

当 sleep() 休眠时间期满后,该线程不一定会立即执行,因为其它线程可能正在运行而且没有被调度为放弃执行,除非此线程具有更高的优先级。

wait() 方法是 Object 类的,当一个线程执行到 wait() 方法时就进入到一个和该对象相关的等待池中,同时释放对象的锁(对于 wait(long timeout) 方法来说是暂时释放锁,因为超时时间到后还需要返还对象锁),其他线程可以访问。wait() 使用 notify() 或 notifyAll() 或者指定睡眠时间来唤醒当前等待池中的线程。wait() 必须放在 synchronized 块中使用,否则会在运行时抛出 IllegalMonitorStateException 异常。

区别

(1)sleep是Thread的静态方法,wait是Object的方法,任何对象实例都能调用。

(2)sleep不会释放锁,它也不需要占用锁。wait会释放锁,但调用它的前提是当前线程占有锁(即代码要在synchronized中)。

(3)sleep可在 任何地方使用,wait()方法依赖synchronized必须放在同步方法或者同步代码块中使用

关注
打赏
1638062488
查看更多评论
立即登录/注册

微信扫码登录

0.1216s