4

线程中的常见方法_0x3f3f3f3f的技术博客_51CTO博客

 1 year ago
source link: https://blog.51cto.com/u_15487307/5526387
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

start方法和run方法

start()start()start()方法用来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpucpucpu时间片,就开始执行run()run()run()方法。而直接调用run()run()run()方法,仅仅只是调用了一个类里的方法,其本质上还是在当前线程中执行的,因此只有使用start()start()start()方法来调用run()run()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");
            }
        };
        t1.run();
    }
}
//16:52:27.595 [main] DEBUG c.Test4 - running

上述代码是直接调用的run()run()run()方法。可以看到打印信息里,是mainmainmain线程执行了这个方法。

@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");
            }
        };
        t1.start();
    }
}
//16:58:22.740 [t1] DEBUG c.Test4 - running

而如果使用start()start()start()方法启动,才是真正的由t1t1t1线程执行的runrunrun方法。

需要注意的是,当ThreadThreadThread对象调用了start()start()start()方法后,就会进入就绪状态,处于就绪状态时无法再调用start()start()start()方法,否则就会抛出IllegalThreadStateExceptionIllegalThreadStateExceptionIllegalThreadStateException异常,如下代码所示

@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");
            }
        };
        t1.start();
        t1.start();
    }
}

异常信息

线程中的常见方法_线程

sleep方法与yield方法

sleep

  1. 调用sleep()sleep()sleep()方法会让当前线程从RunningRunningRunning状态变成TimeWaitingTime WaitingTimeWaiting状态(阻塞)
  2. 其它线程可以使用interruptinterruptinterrupt方法打断正在睡眠的线程,此时sleepsleepsleep方法会抛出InterruptedException
  3. 睡眠结束后的线程未必会立刻得到执行
  4. 建议用TimeUnitTimeUnitTimeUnit的sleepsleepsleep代替ThreadThreadThread的sleepsleepsleep来获得更好的可读性
@Slf4j(topic = "c.Test5")
public class Test5 {
    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());
        //让主线程休眠500ms
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug("t1 state {}", t1.getState());
    }
}
//17:13:21.729 [main] DEBUG c.Test5 - t1 state RUNNABLE
//17:13:22.245 [main] DEBUG c.Test5 - t1 state TIMED_WAITING

上述代码中,首先启动t1t1t1线程,此时打印线程的状态应该是处于RUNNABLERUNNABLERUNNABLE状态,而让主线程休眠是防止主线程先执行打印,但是还未进入到sleep()sleep()sleep()状态。当执行到run()run()run()里边的sleepsleepsleep方法时,线程进入TIMEDWAITINGTIMED WAITINGTIMEDWAITING状态

@Slf4j(topic = "c.Test6")
public class Thread6 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                try {
                    log.debug("enter sleep");
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    log.debug("wake up");
                    e.printStackTrace();
                }
            }
        };
        t1.start();
        Thread.sleep(1000);
        log.debug("interrupt t1");
        //被唤醒
        t1.interrupt();
    }
}

执行结果

线程中的常见方法_面试_02

上述代码中,当startstartstart方法启动后,t1t1t1线程进入睡眠状态,打印提示信息,睡眠时间为2s2s2s,在mainmainmain线程中睡眠1s1s1s后打断t1t1t1线程的睡眠,提示打断信息,并且调用interrupt()interrupt()interrupt()方法,此时线程被打断,抛出异常。

线程中的常见方法_java_03

TimeUnitTimeUnitTimeUnit类中新增了以什么单位去睡眠,可读性更好,但是本质上没区别,只是进行了单位换算

TimeUnit.SECONDS.sleep(1);//该语句作用是睡眠一秒

yield

  1. 调用yieldyieldyield会让当前进程从RunningRunningRunning进入到RunnableRunnableRunnable就绪状态,然后调度执行其他线程
  2. 具体的实现依赖于操作系统的任务调度器,(即当任务调度器中没有其他任务时,即使让出cpucpucpu,也会继续执行该线程)
  3. sleepsleepsleep执行后是进入阻塞状态,此时睡眠时间不结束,就不会分配cpucpucpu给该线程,但是yieldyieldyield是进入就绪状态,即如果没有其他线程需要执行,那么还会给该线程分配时间片,这是sleepsleepsleep和yieldyieldyield的最大区别

线程优先级

线程优先级会提示调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略他
如果cpucpucpu比较忙,那么优先级高的会获得更多的时间片,可cpucpucpu空闲时,优先级几乎没有

sleep的应用-防止cpu占用100%

在没有利用cpucpucpu来计算时,不要让while(true)while(true)while(true)空转浪费cpucpucpu,这时可以可以使用yieldyieldyield或者sleepsleepsleep来让cpucpucpu的使用权交给其他程序

while (true) {
    try {
        Thread.sleep(50);
    } catch (InterruptedException e) {
        e.printStackTrace();
  }
}

可以使用waitwaitwait或者条件变量达到类似的效果
不同的是后两者都需要加锁,并且需要相应的唤醒操作,一般适用于要进行同步的场景
sleepsleepsleep适用于无需锁同步的场景

join方法

以下程序的打印结果

@Slf4j(topic = "c.Test6")
public class Test6 {
    static int r = 0;
    public static void main(String[] args) {
        test();
    }
    private static void test() {
        log.debug("开始");
        Thread t = new Thread("t1") {
            @Override
            public void run() {
                log.debug("开始");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("结束");
                r = 10;
            }
        };
        t.start();
        log.debug("r的值是{}", r);
        log.debug("结束");
    }
}
线程中的常见方法_并发_04

因为主线程和t1t1t1线程是并行的,t1t1t1线程需要1s1s1s后才能计算出rrr的值,而主线程一开始就要打印出rrr的值,因此打印的值为0
解决方法
在t.start();t.start();t.start();后边加上t.join();t.join();t.join();即可。joinjoinjoin的作用是等待某线程运行结束。
以调用方的角度来说,需要等待结果返回才能继续执行就是同步,不需要等待返回结果就能继续执行的就是异步。

线程中的常见方法_面试_05

因此joinjoinjoin方法实际上是让其同步执行

有实效的等待

join(毫秒)join(毫秒)join(毫秒)方法里可以有一个参数是传入等待的时间,如果线程执行时间大于等待时间,则等待时间到了之后,就会停止等待。如果线程执行时间小于等待时间,则线程执行完毕之后,等待也会跟着结束。不会把设置的等待时间过完。

interrupt方法

打断sleep,wait,joinsleep, wait, joinsleep,wait,join的线程,即打断阻塞状态的线程
打断sleepsleepsleep的线程,会清空打断状态

@Slf4j(topic = "c.Test7")
public class Test7 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread("t1"){
            @Override
            public void run() {
                log.debug("sleep...");
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        t.start();
        Thread.sleep(1000);
        log.debug("interrupt");
        t.interrupt();
        log.debug("打断标记: {}", t.isInterrupted());
    }
}
线程中的常见方法_java_06

打断正常运行的线程,不会清空打断状态

因此我们可以在线程中判断打断标记,来决定是否被打断,以及执行被打断之前的收尾工作。

@Slf4j(topic = "c.Test8")
public class Test8 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread("t1"){
            @Override
            public void run() {
                while (true) {
                    if (Thread.currentThread().isInterrupted()) {
                        log.debug("线程被打断了");
                        break;
                    }
                }
            }
        };
        t.start();
        Thread.sleep(1000);
        log.debug("interrupt");
        t.interrupt();
    }
}

默认情况下,javajavajava需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其他非守护线程运行结束了,即使守护线程的代码没有执行完毕,也会强制结束。

@Slf4j(topic = "c.Test10")
public class Test10 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread("t1") {
            @Override
            public void run() {
                while (true) {

                }
            }
        };
        //设置线程为守护线程
        t.setDaemon(true);
        t.start();
        Thread.sleep(1000);
        log.debug("主线程结束");
    }
}

如果不把ttt设置为守护线程,则因为线程内部的死循环,导致程序不会结束运行。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK