【JUC】- Java线程

发布于:2025-06-17 ⋅ 阅读:(9) ⋅ 点赞:(0)

创建和运行线程

方法1:直接使用Thread

@Slf4j(topic = "c.Test01")
public class Test01 {
    public static void main(String[] args) {
        Thread t = new Thread() {
            @Override
            public void run() {
                log.debug("running");
            }
        };
        t.setName("t1"); // 指定名字
        t.start();
        log.debug("running");
    }
}

使用lambda简化:

@Slf4j(topic = "c.Test01")
public class Test01 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
                log.debug("running");
            });
        t.setName("t1"); // 指定名字
        t.start();
        log.debug("running");
    }
}

方法2:使用Runnable配合Thread(推荐)

@Slf4j(topic = "c.Test02")
public class Test02 {
    public static void main(String[] args) {
        Runnable r = new Runnable() {
            @Override
            public void run() {
                log.debug("running");
            }
        };
        Thread t2 = new Thread(r, "t2");
        t2.start();
    }
}

使用lambda简化:

@Slf4j(topic = "c.Test02")
public class Test02 {
    public static void main(String[] args) {
        Runnable r = () -> {
                log.debug("running");
            };
        Thread t2 = new Thread(r, "t2");
        t2.start();
    }
}

Thread和Runnable的关系

  • Thread是把线程和任务合并在一起;Runnable是把线程和任务分开了
  • 用Runnable更容易与线程池等高级API配合
  • 用Runnable让任务类脱离了Thread继承体系,更灵活

方法3:FutureTask配合Thread

@Slf4j(topic = "c.Test03")
public class Test03 {
    public static void main(String[] args) throws Exception {
        FutureTask<Integer> task = new FutureTask<>(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                log.debug("running...");
                Thread.sleep(1000);
                return 100;
            }
        });
        Thread thread = new Thread(task, "t1");
        thread.start();
        Integer res = task.get(); // 获取返回结果
        log.debug("{}", res);
    }
}

主线程调用get(),就会在这里等待t1线程返回结果

查看进程线程的命令

windows

tasklist:查看进程
taskkill:杀死进程

linux

ps -ef:查看所有进程
ps -fT -p <PID>:查看某个进程
kill:杀死进程
top:按大写H切换是否显示线程
top -H -p <PID>:查看某个进程(PID)的所有线程

Java

jps:查看所有Java进程
jstack <PID>:查看某个Java进程(PID)的所有线程状态
jconsole:查看某个java进程中线程的运行情况(图形界面)

线程运行原理

栈和栈帧

每个线程启动,虚拟机就会为其分配一块栈空间

  1. 每个栈由多个栈帧(Frame)组成,对应每次方法调用时所占用的内存
  2. 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法

只有一个线程

public class TestFrames {
    public static void main(String[] args) {
    	method1(10);
    }

    private static void method1(int x) {
        int y = x + 1;
        Object m = method2();
        System.out.println(m);
    }

    private static Object method2() {
        Object n = new Object();
        return n;
    }
}
  1. 进入主线程的时候,会产生一个main栈帧,在堆中分配一个new String[],让栈帧里的局部变量args指向它
  2. 主线程调用method1()方法,就会产生一个method1栈帧,并且在栈帧中记录局部变量(x、y)的值,并调用method2方法
  3. method1()方法调用method2()方法时,就会产生一个method2()的栈帧,并且在堆中分配一个new Object(),让栈帧里的局部变量n指向它,然后将n返回给method1()。
    在这里插入图片描述

有两个线程

public class TestFrames {
    public static void main(String[] args) {
        Thread t1 = new Thread(){
            @Override
            public void run() {
                method1(20);
            }
        };
        t1.setName("t1");
        t1.start();
        method1(10);
    }

    private static void method1(int x) {
        int y = x + 1;
        Object m = method2();
        System.out.println(m);
    }

    private static Object method2() {
        Object n = new Object();
        return n;
    }
}

栈帧是以线程为单位,他们之间相互是独立的。主线程和t1线程都有自己的栈帧。

线程上下文切换

CPU不再执行当前的线程,转而执行另一个线程的代码。

  • 线程的CPU时间片用完
  • 垃圾回收(暂停当前所有的工作线程,让垃圾回收的线程去回收垃圾)
  • 有更高优先级的线程需要运行
  • (主动)线程自己调用了sleep、yield、wait、join、park、synchronized、lock等方法

上下文切换发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态(程序计数器:记住下一条JVM指令的执行地址,是线程私有的)
状态包括:程序计数器、虚拟机中每个栈帧的信息(局部变量、操作数栈、返回地址…)
线程数并不是越多越好,频繁的上下文切换会影响性能

常见方法

start() 与 run()

@Slf4j(topic = "c.Test4")
public class Test4 {
    public static void main(String[] args) {
        Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                log.debug("running...");
                FileReader.read(Constants.MP4_PATH);
            }
        };
		t1.run(); // 仍然在主线程执行
        t1.start(); // t1线程执行
        log.debug("do other things...");
    }
}

线程还没调用start()之前是NEW状态,当调用start()方法后就会变成RUNNABLE状态
start()方法不能被多次调用(线程只要变成RUNNABLE状态后,就不能再次被调用了)

sleep() 与 yield()

sleep:

  • 调用sleep会让线程从Running进入Timed Waiting状态
@Slf4j(topic = "c.Test6")
public class Test6 {

    public static void main(String[] args) {
        Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        t1.start();
        log.debug("t1 state: {}", t1.getState()); // RUNNABLE

        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        log.debug("t1 state: {}", t1.getState()); // TIMED_WAITING
    }
}
  • 其他线程可以使用interrupt方法打断正在睡眠的线程,这时sleep方法会抛出InterruptedException
@Slf4j(topic = "c.Test7")
public class Test7 {

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                log.debug("enter sleep...");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    log.debug("wake up...");
                    e.printStackTrace();
                }
            }
        };
        t1.start();

        Thread.sleep(1000);
        log.debug("interrupt...");
        t1.interrupt();
    }
}
  • 睡眠结束后的线程未必会立刻得到执行(需要任务调度器把时间片分配给这个线程才行)
  • 建议使用TimeUnit的sleep代替Thread的sleep来获得更好的可读性
TimeUnit.SECONDS.sleep(1); // 睡眠1s

yield:

  • 调用yield会让当前线程从Running进入Runnable状态,然后调度执行其他同优先级的线程。如果这时没有同优先级的线程,那么不能保证让当前线程暂停的效果(想让,没让出去)
  • 具体的实现依赖于操作系统的任务调度器
@Slf4j(topic = "c.Test9")
public class Test9 {

    public static void main(String[] args) {
        Runnable task1 = () -> {
            int count = 0;
            for (;;) {
                System.out.println("---->1 " + count++);
            }
        };
        Runnable task2 = () -> {
            int count = 0;
            for (;;) {
               Thread.yield();
               System.out.println("              ---->2 " + count++);
            }
        };
        Thread t1 = new Thread(task1, "t1");
        Thread t2 = new Thread(task2, "t2");
        t1.start();
        t2.start();
    }
}

线程1比线程2的count值增加的快(因为线程2使用yield()让出执行权的次数多)

线程优先级

  • 线程优先级会提示调度器优先调度该线程,但是它仅仅是一个提示,调度器可以忽略它
  • 如果cpu比较忙,优先级高的线程会获得更多的时间片;cpu空闲时,优先级几乎没有作用
@Slf4j(topic = "c.Test9")
public class Test9 {

    public static void main(String[] args) {
        Runnable task1 = () -> {
            int count = 0;
            for (;;) {
                System.out.println("---->1 " + count++);
            }
        };
        Runnable task2 = () -> {
            int count = 0;
            for (;;) {
                System.out.println("              ---->2 " + count++);
            }
        };
        Thread t1 = new Thread(task1, "t1");
        Thread t2 = new Thread(task2, "t2");
        t1.setPriority(Thread.MIN_PRIORITY); // 给线程1最低的优先级
        t2.setPriority(Thread.MAX_PRIORITY); // 给线程2最高的优先级
        t1.start();
        t2.start();
    }
}

线程2比线程1的count值增加的快(因为线程2的优先级比线程1大)

注意】:不管是yield()还是设置线程的优先级,都无法控制线程的调度。最终还是由操作系统的任务调度器来决定哪个线程分到最多的时间片。

join()

等待哪个线程运行结束,就用哪个线程调用join()方法。

@Slf4j(topic = "c.Test10")
public class Test10 {
    static int r = 0;
    public static void main(String[] args) throws InterruptedException {
        test1();
    }
    private static void test1() throws InterruptedException {
        log.debug("开始");
        Thread t1 = new Thread(() -> {
            log.debug("开始");
            sleep(1);
            log.debug("结束");
            r = 10;
        },"t1");
        t1.start();
        t1.join(); // 10
        log.debug("结果为:{}", r);
        log.debug("结束");
    }
}

如果没有加t1.join(),主线程和线程t1是并行执行,t1线程需要1s后才能算出r = 10;而主线程并不会等待t1线程返回结果,直接就打印r的值,所以打印出r的值为0。
在这里插入图片描述

有时效的join(long n)

join(long n):最多等待n毫秒
如果线程提前结束,join也会提前结束;如果在最大等待时间内还没结束,就不再等待这个线程。

interrupt()

打断sleep、wait、join线程(阻塞状态)

@Slf4j(topic = "c.Test11")
public class Test11 {

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            log.debug("sleep...");
            try {
                Thread.sleep(5000); // wait, join
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"t1");

        t1.start();
        Thread.sleep(1000);
        log.debug("interrupt");
        t1.interrupt(); // 打断操作
        Thread.sleep(1000);
        log.debug("打断标记:{}", t1.isInterrupted()); // false
        
    }
}

如果是在sleep()、wait()、join()的状态下被打断,那么打断标记就是false。
获取打断标记:

  • t1.isInterrupted():判断是否被打断,不会清除打断标记
  • Thread.interrupted():判读当前线程是否被打断,会清除打断标记

打断标记可以用来判断这个线程被打断后是继续运行,还是直接终止。

打断正常运行的线程

@Slf4j(topic = "c.Test12")
public class Test12 {

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            while(true) {
                boolean interrupted = Thread.currentThread().isInterrupted();// 打断标记
                if(interrupted) {
                    log.debug("被打断了, 退出循环");
                    break;
                }
            }
        }, "t1");
        t1.start();

        Thread.sleep(1000);
        log.debug("interrupt");
        t1.interrupt();
    }
}

主线程打断正常运行的线程后,打断标记为true,此时正常运行的线程就可以获取打断标记。判断是要自己结束运行,还是继续运行。

【多线程】两阶段终止模式

在线程t1中优雅的终止线程t2。
错误的思路

  • 使用线程对象的stop()方法停止线程:stop()方法会真正杀死线程,如果此时线程锁住了共享资源,那么它被杀死后就再也没有机会释放锁,其他线程将永远无法获取锁。
  • 使用Systen.exit(int)方法停止线程:这种做法会让整个程序都停止。

正确的做法:两阶段终止模式
在这里插入图片描述

@Slf4j(topic = "c.TwoPhaseTermination")
public class Test04 {
    public static void main(String[] args) throws InterruptedException {
        TwoPhaseTermination tpt = new TwoPhaseTermination();
        tpt.start();
        Thread.sleep(3500);
        tpt.stop();
    }
}
@Slf4j(topic = "c.TwoPhaseTermination")
class TwoPhaseTermination {
    private Thread monitor;

    // 启动监控线程
    public void start() {
        // 创建监控对象
        monitor = new Thread(()->{
            while(true) {
                Thread current = Thread.currentThread(); // 当前线程
                boolean interrupted = current.isInterrupted(); // 打断状态
                if(interrupted) {// 被打断了
                    // 处理后边的操作
                    log.debug("料理后事");
                    break;
                }
                try {
                    Thread.sleep(1000); // 情况1:睡眠期间被打断(打断标记:true)
                    log.debug("执行监控记录"); // 情况2:正常运行时被打断(打断标记:false)
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    // 重新设置打断标记(对接情况2)
                    current.interrupt();
                }
            }
        });
        monitor.start();
    }

    // 停止监控线程
    public void stop() {
        // 打断
        monitor.interrupt();
    }
}

主线程与守护线程

默认情况:java进程需要所有等待的线程都运行结束,才会结束
守护线程:只要它的非守护线程结束了,即使守护线程的代码没有执行完,也会强制结束。

@Slf4j(topic = "c.Test15")
public class Test15 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            while (true) {
                if (Thread.currentThread().isInterrupted()) {
                    break;
                }
            }
            log.debug("结束");
        }, "t1");
        t1.setDaemon(true); // t1变为守护线程
        t1.start();

        Thread.sleep(1000);
        log.debug("结束");
    }
}

垃圾回收器线程就是一种守护线程。
Tomcat中的Acceptor和Poller线程都是守护线程,Tomcat收到shutdow命令后,不会等待他们处理完当前请求。

线程状态

五种状态(操作系统层面)

在这里插入图片描述

  • 初始状态:只在语言层面创建了线程对象,还未与操作系统线程关联
  • 可运行状态(就绪状态):该线程已被创建(与操作系统关联),可以由CPU调度执行
  • 运行状态:获得CPU时间片运行中的状态
    • 当CPU时间片用完时,会从“运行状态”转化为“可运行状态”,导致线程上下文的切换
  • 阻塞状态:
    • 如果调用了阻塞API(BIO读写文件), 此时不会用到CPU,进入阻塞状态
    • 等BIO执行完毕后,会由操作系统唤醒阻塞的线程,转化到“可运行状态”
    • 阻塞状态的线程,只要他们一直不唤醒,调度器就一直不会考虑调度他们。
  • 终止状态:线程已经执行完毕,生命周期已经结束,不会再转为其他状态

六种状态(Thread.State枚举)

在这里插入图片描述

public enum State {
    NEW,
    RUNNABLE,
    BLOCKED,
    WAITING,
    TIMED_WAITING,
    TERMINATED;
}
  • NEW:线程刚被创建,还没调用start()方法
  • RUNNABLE:当调用了start()方法之后,这个RUNNABLE状态包含了操作系统层面的:“可运行状态”、“运行状态”、“阻塞状态”(由于BIO导致的线程阻塞,在Java里无法区分,仍然认为是可运行的)
  • BLOCKED、WAITING、TIME_WAITING:Java层面对“阻塞状态的细分”
    • BLOCKED:synchronized
    • WAITING:join()
    • TIME_WAITING:sleep()
  • TERMINATED:线程代码结束后
@Slf4j(topic = "c.TestState")
public class TestState {
    public static void main(String[] args) throws IOException {
        Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                log.debug("running...");
            }
        };

        Thread t2 = new Thread("t2") {
            @Override
            public void run() {
                while(true) { // runnable

                }
            }
        };
        t2.start();

        Thread t3 = new Thread("t3") {
            @Override
            public void run() {
                log.debug("running...");
            }
        };
        t3.start();

        Thread t4 = new Thread("t4") {
            @Override
            public void run() {
                synchronized (TestState.class) {
                    try {
                        Thread.sleep(1000000); // timed_waiting
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t4.start();

        Thread t5 = new Thread("t5") {
            @Override
            public void run() {
                try {
                    t2.join(); // waiting(等待t2结束)
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        t5.start();

        Thread t6 = new Thread("t6") {
            @Override
            public void run() {
                synchronized (TestState.class) { // blocked
                    try {
                        Thread.sleep(1000000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t6.start();

        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug("t1 state {}", t1.getState()); // NEW
        log.debug("t2 state {}", t2.getState()); // RUNNABLE
        log.debug("t3 state {}", t3.getState()); // TERMINATED
        log.debug("t4 state {}", t4.getState()); // TIME_WAITING
        log.debug("t5 state {}", t5.getState()); // WAITING
        log.debug("t6 state {}", t6.getState()); // BLOCKED
        System.in.read();
    }
}

网站公告

今日签到

点亮在社区的每一天
去签到