多线程之常见方法使用

简介: 线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位;进程是系统资源分配的单位,线程是系统调度的单位。一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线;进程之间相互独立,进程之间不能共享资源,而线程共享所在进程的地址空间和其它资源。同时线程还有自己的栈和栈指针,程序计数器等寄存器。

多线程之常见方法使用

start和run

首先看调用run方法

代码示例:

package com.wxit.test02;

import lombok.extern.slf4j.Slf4j;

/**
 * @author wj
 * @date 2022.01.02 19:25
 */
@Slf4j(topic = "c.Test")
public class Test {

    public static void main(String[] args) {
        Thread thread = new Thread("t1") {
            @Override
            public void run() {
                log.debug("开始执行t1线程");
            }
        };

        thread.run();
        log.debug("主线程正在执行....");
    }
}

控制台输出

19:27:46.721 c.Test [main] - 开始执行t1线程
19:27:46.742 c.Test [main] - 主线程正在执行....

程序仍然在main线程中运行,而且是同步执行的


调用start方法

package com.wxit.test02;

import lombok.extern.slf4j.Slf4j;

/**
 * @author wj
 * @date 2022.01.02 19:25
 */
@Slf4j(topic = "c.Test")
public class Test {

    public static void main(String[] args) {
        Thread thread = new Thread("t1") {
            @Override
            public void run() {
                log.debug("开始执行t1线程");
            }
        };

        thread.start();
        log.debug("主线程正在执行....");
    }
}

控制台输出:

19:32:28.911 c.Test [main] - 主线程正在执行....
19:32:28.911 c.Test [t1] - 开始执行t1线程

可以看出程序在t1线程中运行了


小结

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

sleep与 yield

sleep

  1. 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)
  2. 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException
  3. 睡眠结束后的线程未必会立刻得到执行
  4. 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性

yield

  1. 调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程
  2. 具体的实现依赖于操作系统的任务调度器

线程优先级

  • 线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它
  • 如果 cpu 比较忙,那么优先级高的线程会获得更多的时间片,但 cpu 闲时,优先级几乎没作用

代码示例:

package com.wxit.test02;

/**
 * @author wj
 * @date 2022.01.02 19:40
 */
public class Test02 {
    public static void main(String[] args) {
        Runnable r1 = () -> {
            int count = 0;
            for (; ; ) {
                System.out.println("--------1" + count++);
            }
        };
        Runnable r2 = () -> {
            int count = 0;
            for (; ; ) {
//                Thread.yield();
                System.out.println("                --------2" + count++);
            }
        };

        Thread t1 = new Thread(r1, "t1");
        Thread t2 = new Thread(r2, "t2");

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

        t1.start();
        t2.start();
    }
}

查看源码发现线程优先级有三种

    /**
     * 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;

join方法

首先引出为什么要使用join

案例说明,执行的结果是?

package com.wxit.test02;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;

/**
 * @author wj
 * @date 2022.01.02 20:51
 */
@Slf4j(topic = "c.Test03")
public class Test03 {

    static int r = 0;
    public static void main(String[] args) {
        test01();
    }

    static private void test01(){
        log.debug("开始执行....");
        Thread t1 = new Thread(() -> {
            log.debug("开始执行线程中run方法....");
            sleep(1);
            log.debug("睡眠结束....");
            r = 10;
        });
        t1.start();
        log.debug("结果为:{}",r);
        log.debug("主线程执行结束");
    }

    public static void sleep(int i) {
        try {
            TimeUnit.SECONDS.sleep(i);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

执行结果:

20:58:02.833 c.Test03 [main] - 开始执行....
20:58:02.886 c.Test03 [Thread-0] - 开始执行线程中run方法....
20:58:02.886 c.Test03 [main] - 结果为:0
20:58:02.888 c.Test03 [main] - 主线程执行结束
20:58:03.894 c.Test03 [Thread-0] - 睡眠结束....

Process finished with exit code 0

分析

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

解决方法

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

在t1.start() 之后

调用join方法之后,控制台输出为:

21:01:12.347 c.Test03 [main] - 开始执行....
21:01:12.388 c.Test03 [Thread-0] - 开始执行线程中run方法....
21:01:13.389 c.Test03 [Thread-0] - 睡眠结束....
21:01:13.389 c.Test03 [main] - 结果为:10
21:01:13.389 c.Test03 [main] - 主线程执行结束

应用之同步

以调用方角度来讲,如果

  • 需要等待结果返回,才能继续运行就是同步
  • 不需要等待结果返回,就能继续运行就是异步

等待多个结果

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

package com.wxit.test02;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;

/**
 * @author wj
 * @date 2022.01.02 21:06
 */
@Slf4j(topic = "c.Test04")
public class Test04 {
    static int r1 = 0;
    static int r2 = 0;

    public static void main(String[] args) throws InterruptedException {
       log.debug("开始。。。");
        test01();
    }

    static private void test01() throws InterruptedException {
        Thread t1 = new Thread(() -> {
            sleep(1);
            r1 = 10;
        });
        Thread t2 = new Thread(() -> {
            sleep(2);
            r2 = 20;
        }, "t2");
        long start = System.currentTimeMillis();
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        long end = System.currentTimeMillis();
        log.debug("r1:{},r2:{},花费时间:{}",r1,r2,end - start);
    }

    public static void sleep(int i) {
        try {
            TimeUnit.SECONDS.sleep(i);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

控制台输出如下:

21:12:08.296 c.Test04 [main] - 开始。。。
21:12:10.352 c.Test04 [main] - r1:10,r2:20,花费时间:2009
相关文章
|
30天前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
20 3
|
30天前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
19 2
|
30天前
|
安全 Java
Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧
【10月更文挑战第20天】Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧,包括避免在循环外调用wait()、优先使用notifyAll()、确保线程安全及处理InterruptedException等,帮助读者更好地掌握这些方法的应用。
19 1
|
30天前
|
Java 开发者
Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点
【10月更文挑战第20天】Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点,重点解析为何实现Runnable接口更具灵活性、资源共享及易于管理的优势。
34 1
|
30天前
|
Java
在Java多线程编程中,`wait()`和`notify()`方法的相遇如同一场奇妙的邂逅
在Java多线程编程中,`wait()`和`notify()`方法的相遇如同一场奇妙的邂逅。它们用于线程间通信,使线程能够协作完成任务。通过这些方法,生产者和消费者线程可以高效地管理共享资源,确保程序的有序运行。正确使用这些方法需要遵循同步规则,避免虚假唤醒等问题。示例代码展示了如何在生产者-消费者模型中使用`wait()`和`notify()`。
28 1
|
30天前
|
安全 Java 开发者
Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用
本文深入解析了Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用。通过示例代码展示了如何正确使用这些方法,并分享了最佳实践,帮助开发者避免常见陷阱,提高多线程程序的稳定性和效率。
38 1
|
30天前
|
Java
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是线程间通信的核心机制。
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是线程间通信的核心机制。它们通过基于锁的方式,使线程在条件不满足时进入休眠状态,并在条件成立时被唤醒,从而有效解决数据一致性和同步问题。本文通过对比其他通信机制,展示了 `wait()` 和 `notify()` 的优势,并通过生产者-消费者模型的示例代码,详细说明了其使用方法和重要性。
25 1
|
1月前
|
监控 Java
在实际应用中选择线程异常捕获方法的考量
【10月更文挑战第15天】选择最适合的线程异常捕获方法需要综合考虑多种因素。没有一种方法是绝对最优的,需要根据具体情况进行权衡和选择。在实际应用中,还需要不断地实践和总结经验,以提高异常处理的效果和程序的稳定性。
22 3
|
1月前
|
监控 Java
捕获线程执行异常的多种方法
【10月更文挑战第15天】捕获线程执行异常的方法多种多样,每种方法都有其特点和适用场景。在实际开发中,需要根据具体情况选择合适的方法或结合多种方法来实现全面有效的线程异常捕获。这有助于提高程序的健壮性和稳定性,减少因线程异常带来的潜在风险。
22 1