Java创建线程的四种方式及其比较Java的线程状态及其相互转换

java Thread run 关闭 java thread start_并发编程

1、start()

功能说明

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

注意

start方法只是让线程从新建状态进入就绪队列排队,一旦轮到它来享用CPU资源时,就可以脱离创建它的线程独立开始自己的生命周期了。里面代码不一定立刻 运行(CPU 的时间片还没分给它)。每个线程对象的 start方法只能调用一次,如果调用了多次会出现
IllegalThreadStateException

举例

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.start();
    log.debug("do other things ...");
}

输出

19:41:30 [main] c.TestStart - do other things ...
19:41:30 [t1] c.TestStart - t1
19:41:30 [t1] c.FileReader - read [1.mp4] start ...
19:41:35 [t1] c.FileReader - read [1.mp4] end ... cost: 4542 ms

2、run()

功能说明

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

注意

(1)把需要处理的代码放到run()方法中,start()方法启动线程将自动调用run()方法,这个由java的内存机制规定的。并且run()方法必需是public访问权限,返回值类型为void。

(2)如果在构造 Thread 对象时传递了 Runnable 参数,则 线程启动后会调用 Runnable 中的 run 方法,否则默 认不执行任何操作。但可以创建 Thread 的子类对象, 来覆盖默认行为

(3)run()和start()之间的区别

  • 直接调用 run 是在主线程中执行了 run,没有启动新的线程
  • 使用 start 是启动新的线程,通过新的线程间接执行 run 中的代码
  • start只是启动了线程,使得它进入就绪状态,不一定运行

举例

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 ...");
}

输出

19:39:14 [main] c.TestStart - main
19:39:14 [main] c.FileReader - read [1.mp4] start ...
19:39:18 [main] c.FileReader - read [1.mp4] end ... cost: 4227 ms 19:39:18 [main] c.TestStart - do other things ...

3、sleep(long n)

功能说明

让当前执行的线 程休眠n毫秒, 休眠时让出 cpu 的时间片给其它 线程

注意

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

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

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

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

与wait区别

1、sleep方法属于Thread,而wait方法属于Object类
2、调用sleep的过程中,线程不会释放对象锁;而调用wait方法的时候,线程会放弃对象锁,进入等待此对象的等待对象池,只有此对象调用notify方法后本线程才进入对象锁定池准备获取对象锁进入运行状态
3、sleep(long)会导致线程进入TIMED-WATING状态,而wait()方法会导致当前线程进入WATING状态

4、interrupt()

功能说明

打断线程,其本意是给这个线程一个通知信号,会影响这个线程内部的一个中断标示位。这个线程本身并不会因此而改变状态(如阻塞,终止等)。

注意

如果被打断线程正在 sleep,wait,join 会导致被打断 的线程抛出 InterruptedException,并清除打断标记 ,使isInterrupted为false,如果这时我们通过代码捕获该异常,然后break跳出循环状态,然后让我们有机会结束这个线程的执行;如果打断的正在运行的线程,则会设置 打断标记 ;park 的线程被打断,也会设置 打断标记
值得注意的是:
1、调用该方法并不会终止一个正在运行(Running状态)的线程,仅仅改变内部维护的中断标示位而已
2、若调用sleep方法是线程处于TIMED-WATING状态,这时调用interrupt方法,会抛出InterruptedException,从而使线程提前结束TIIMED-WATING状态
3、通常很多人认为只要调用 interrupt 方法线程就会结束,实 际上是错的, 一定要先捕获 InterruptedException 异常之后通过 break 来跳出循环,才能正 常结束 run 方法
4、许多声明抛出 InterruptedException 的方法(如 Thread.sleep(long mills 方法)),抛出异
常前,都会清除中断标识位,所以抛出异常后,调用 isInterrupted()方法将会返回 false。
5、中断状态是线程固有的一个标识位,可以通过此标识位安全的终止线程。比如,你想终止 一个线程 thread 的时候,可以调用 thread.interrupt()方法,在线程的 run 方法内部可以 根据 thread.isInterrupted()的值来优雅的终止线程。

举例

public class ThreadSafe extends Thread {
    public void run() {
        while (!isInterrupted()){ //非阻塞过程中通过判断中断标志来退出 
            try{ 
                Thread.sleep(5*1000);//阻塞过程捕获中断异常来退出 
            }catch(InterruptedException e){ 
                e.printStackTrace(); 
 break;//捕获到异常之后,执行 break 跳出循环
            }
        }
    }
}

打断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());
}

输出

java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at java.lang.Thread.sleep(Thread.java:340)
    at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
    at cn.itcast.n2.util.Sleeper.sleep(Sleeper.java:8)
    at cn.itcast.n4.TestInterrupt.lambda$test1$3(TestInterrupt.java:59) 
    at java.lang.Thread.run(Thread.java:745)
21:18:10.374 [main] c.TestInterrupt -  打断状态: false

清除打断标记 ,使isInterrupted为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();
}

输出

20:57:37.964 [t2] c.TestInterrupt -  打断状态: true

打断park线程

不会清空打断状态

private static void test3() throws InterruptedException {
    Thread t1 = new Thread(() -> {
        log.debug("park...");
        LockSupport.park();
        log.debug("unpark...");
        log.debug("打断状态:{}", Thread.currentThread().isInterrupted()); 
    }, "t1");
    t1.start();


    sleep(0.5); 
    t1.interrupt();
}

输出

21:11:52.795 [t1] c.TestInterrupt - park... 
21:11:53.295 [t1] c.TestInterrupt - unpark... 
21:11:53.295 [t1] c.TestInterrupt - 打断状态:true

5、interrupted()

判断当前线程是否被打断,会被清除

6、isInterrupted

判断是否被打断,不会被清除

1、许多声明抛出InterruptedException的方法(如Thread.sleep(long mils方法)),抛出异常前都会清除中断标示位,所以抛出异常后,调用isinterrupted方法将会返回false
2、中断状态是线程固有的一个标示位,可以通过此标示位安全地终止线程。比如,你想终止一个线Thread时,可以调用thread.interrupt()方法,在线程的run方法内部根据thread.isInterrupted()优雅地终止线程

7、yield()

功能说明

提示线程调度器 让出当前线程对 CPU的使用

主要是为了测试和调试
线程让步,yield会使当前线程让出CPU执行时间片,与其他线程一起竞争CPU时间片。一般情况下,优先级高的线程有更大的可能性成功竞争到CPU时间片,但这又不是绝对的。有的操作系统对线程优先级不敏感。

注意

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

2、具体的实现依赖于操作系统的任务调度器

8、join()

功能说明

等待其他线程运行结束

join()方法使调用该方法使得当前线程转为阻塞状态,回到另一个线程结束,当前线程再由阻塞状态变为就绪状态,等待cpu分配时间片

原理

调用者轮询检查线程alive状态

t1.join();

等价于

synchronized (t1) {
    // 调用者线程进入 t1 的 waitSet 等待, 直到 t1 运行结束 
    while (t1.isAlive()) {
        t1.wait(0);
    }
}

举例

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.start();
log.debug("结果为:{}", r);
log.debug("结束");
}

因为主线程和线程 t1 是并行执行的,t1 线程需要 1 秒之后才能算出 r=10
而主线程一开始就要打印 r 的结果,所以只能打印出 r=0
join方法实现同步

static int result = 0;
private static void test1() throws InterruptedException {
log.debug("开始");
Thread t1 = new Thread(() -> {
log.debug("开始");
sleep(1);
log.debug("结束");
result = 10;
}, "t1");
t1.start();
t1.join();
log.debug("结果为:{}", result);
}

输出:

20:30:40.453 [main] c.TestJoin - 开始
20:30:40.541 [Thread-0] c.TestJoin - 开始
20:30:41.543 [Thread-0] c.TestJoin - 结束
20:30:41.551 [main] c.TestJoin - 结果为:10

9、join(long n)

等待线程运行结束,最多等待n秒

10、getId()

获取线程长整型的id(id唯一)

11、getName()

获取线程名

12、setName(String)

修改线程名

13、getPriority()

获取线程优先级

14、setPriority()

修改线程优先级

java中规定线程优先级是1~10 的整数,较大的优先级 能提高该线程被 CPU 调度的机率

15、getState()

获取线程状态

Java 中线程状态是用 6 个 enum 表示,分别为:NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED

16、currentThread()

获取当前正在执行的线程!

补充:wait()和notify()、notifyAll()

功能说明

这三个方法用于协调多个线程对共享数据的存取,所以必须在synchronized语句块内使用。synchronized关键字用于保护共享数据,阻止其他线程对共享数据的存取,但是这样程序的流程就很不灵活了,如何才能在当前线程还没退出synchronized数据块时让其他线程也有机会访问共享数据呢?此时就用这三个方法来灵活控制。

wait()方法使当前线程暂停执行并释放对象锁标示,让其他线程可以进入synchronized数据块,当前线程被放入对象等待池中。

当调用notify()方法后,将从对象的等待池中移走一个任意的线程并放到锁标志等待池中,只有锁标志等待池中线程能够获取锁标志,即唤醒在此对象监视器上等待的单个线程;如果锁标志等待池中没有线程,则notify()不起作用。

notifyAll()则从对象等待池中移走所有等待那个对象的线程并放到锁标志等待池中。

注意 这三个方法都是java.lang.Object的方法。

wait notify原理

java Thread run 关闭 java thread start_Java_02

Java创建线程的四种方式及其比较Java的线程状态及其相互转换