前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >张三并发编程实践:掌握多线程技巧,打造高性能应用!

张三并发编程实践:掌握多线程技巧,打造高性能应用!

原创
作者头像
GoBoy
发布2024-05-05 13:19:10
1470
发布2024-05-05 13:19:10
举报
文章被收录于专栏:GoboyGoboy

线程(Thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一个进程中可以有多个线程,它们共享进程的资源,如内存空间、文件句柄等。线程相较于进程,具有更小的资源开销,创建和切换线程的速度也更快。

线程的故事

有一天,一个程序员在开发一个应用程序,这个应用程序需要处理大量的任务。程序员决定使用多线程来提高程序的执行效率。

于是,程序员创建了一个线程池,线程池中有很多线程。当有新任务到来时,线程池中的一个空闲线程会被分配任务去执行。在执行过程中,线程可能会遇到一些阻塞操作,如等待文件读写、等待网络请求等。此时,线程会进入阻塞状态,线程池会将这个线程置换出去,让其他线程继续执行任务。当阻塞操作完成后,线程会重新回到线程池中,等待分配新任务。

有一次,线程池中的线程都在忙碌,但应用程序仍然有大量任务需要处理。这时,线程池决定扩容,增加更多的线程。新来的线程迅速地接管了任务,提高了程序的处理能力。

另一次,线程池中的线程数量过多,导致系统资源紧张。这时,线程池决定缩容,减少一些线程。被减少的线程会完成当前任务后,自动退出。

通过这个故事,我们可以了解到线程的基本概念和作用。在实际开发中,我们需要根据应用程序的需求和系统资源情况,合理地使用多线程来提高程序的执行效率。

线程的状态

线程在执行过程中会经历不同的状态:

新建(New):

当用new关键字创建一个线程对象时,线程处于新建状态。此时,线程对象已经创建,但线程还没有开始执行。

代码语言:javascript
复制
Thread thread = new Thread();

就绪(Runnable):

当线程对象调用了start()方法后,线程进入可运行状态。此时,线程已经准备好执行,等待系统分配资源。

代码语言:javascript
复制
thread.start();

运行(Running):

当线程获得系统资源后,线程开始执行run()方法中的代码,此时线程处于运行状态。

代码语言:javascript
复制
public void run() {
    // 线程执行的代码
}

阻塞(Blocked):

线程在运行过程中,可能会因为某些原因暂时无法继续执行,如等待 I/O 操作完成、等待获取锁等。此时,线程进入阻塞状态。

代码语言:javascript
复制
synchronized (lock) {
    // 等待获取锁
}

阻塞的三种分类:

等待阻塞(Waiting for I/O):

线程在等待 I/O 操作完成,如等待文件读写、网络请求等。此时,线程会被挂起,不占用 CPU 资源。当 I/O 操作完成后,线程会重新进入可运行状态。

代码语言:javascript
复制
FileInputStream fis = new FileInputStream("file.txt");
int data = fis.read(); // 等待文件读取完成

同步阻塞(Synchronization Blocked):

线程在等待获取锁。当一个线程试图访问被synchronized关键字修饰的同步代码块时,它需要获取锁。如果锁已经被其他线程持有,当前线程会被阻塞,等待锁被释放。

代码语言:javascript
复制
synchronized (lock) {
    // 等待获取锁
}

其他阻塞(Other Blocked):

线程在等待某些系统资源,如等待操作系统分配内存、等待线程调度等。此时,线程会被挂起,不占用 CPU 资源。当系统资源可用时,线程会重新进入可运行状态。

代码语言:javascript
复制
Thread.sleep(1000); // 等待1秒

了解这三种阻塞情况有助于我们更好地理解多线程编程中的问题和解决方案。在实际开发中,我们需要根据具体需求合理地控制线程状态,以实现高效的并发编程。同时,要注意避免死锁、资源竞争等问题。

等待(Waiting):

线程在运行过程中,主动调用了wait()join()park()方法,暂时放弃 CPU 资源,进入等待状态。

代码语言:javascript
复制
lock.wait();

超时等待(Timed Waiting):

线程在等待状态的基础上,设置了等待超时时间。

代码语言:javascript
复制
lock.wait(timeout);

终止(Terminated):

线程执行完run()方法中的代码,或者因为异常而终止,线程进入终止状态。

线程状态之间的转换关系如下:

  • 新建 -> 可运行:调用start()方法
  • 可运行 -> 运行:获得系统资源
  • 运行 -> 阻塞:等待 I/O 操作完成、等待获取锁等
  • 运行 -> 等待:调用wait()join()park()方法
  • 运行 -> 超时等待:调用带有超时参数的wait()方法
  • 阻塞、等待、超时等待 -> 运行:获得锁、I/O 操作完成、超时等待结束等
  • 运行 -> 终止:执行完run()方法或发生异常

了解线程的状态有助于我们更好地理解多线程编程中的问题和解决方案。在实际开发中,我们需要根据具体需求合理地控制线程状态,以实现高效的并发编程。

线程调整优先级

在 Java 中,我们可以通过调整线程的优先级来影响线程调度。线程优先级是一个整数值,范围在 1(最低优先级)到 10(最高优先级)之间。默认情况下,新创建的线程优先级与其父线程相同。这些优先级常量分别由 Thread 类中的 MAX_PRIORITYNORM_PRIORITYMIN_PRIORITY 定义。

Thread.MAX_PRIORITY(10):表示线程的最高优先级。当一个线程的优先级设置为最高优先级时,它具有更高的概率被调度执行。然而,这并不意味着最高优先级的线程总是优先执行。线程调度仍然取决于操作系统和 JVM 的实现。

Thread.NORM_PRIORITY(5):表示线程的默认优先级。当创建一个新线程时,如果没有显式设置优先级,那么它将继承父线程的优先级。默认优先级适用于大多数线程,它不会导致线程饥饿,也不会导致过分的线程切换开销。

Thread.MIN_PRIORITY(1):表示线程的最低优先级。当一个线程的优先级设置为最低优先级时,它具有较低的概率被调度执行。这可以用于确保低优先级线程不会影响到高优先级线程的执行。

代码语言:javascript
复制
Thread thread = new Thread(() -> {
    // 线程执行的代码
});

// 设置线程优先级
thread.setPriority(Thread.MAX_PRIORITY); // 设置为最高优先级
thread.setPriority(Thread.NORM_PRIORITY); // 设置为默认优先级
thread.setPriority(Thread.MIN_PRIORITY); // 设置为最低优先级

需要注意的是,线程优先级并不能保证线程按照预期的顺序执行。线程调度仍然取决于操作系统和 JVM 的实现。此外,过分依赖线程优先级可能导致程序难以维护和理解。在实际开发中,我们应该根据应用程序的需求和系统资源情况,合理地设置线程优先级,以实现高效的并发编程。同时,要注意避免死锁、资源竞争等问题。

线程调度策略

线程调度是操作系统用来决定哪个线程应该获得处理器资源的过程。线程调度策略会影响程序的执行效率和响应时间。

常见的线程调度策略:

  1. 协同式多线程(Cooperative Multithreading):协同式多线程是一种非抢占式的线程调度策略。在这种策略中,线程需要主动地让出处理器资源,以便其他线程可以执行。这种调度策略的优点是实现简单,但缺点是可能导致线程饥饿(一个线程长时间得不到执行)。
  2. 抢占式多线程(Preemptive Multithreading):抢占式多线程是一种抢占式的线程调度策略。在这种策略中,操作系统可以在任何时候暂停一个正在执行的线程,将处理器资源分配给其他线程。这种调度策略可以避免线程饥饿,但实现相对复杂。
  3. 优先级调度(Priority Scheduling):优先级调度是一种基于线程优先级的调度策略。线程可以被分配一个优先级,优先级较高的线程更有可能获得处理器资源。优先级调度可以确保重要的线程优先执行,但可能导致低优先级线程饥饿。
  4. 时间片轮转调度(Round-Robin Scheduling):时间片轮转调度是一种将处理器资源分配给线程的公平策略。每个线程都有一个时间片,当时间片用完时,线程会被挂起,让其他线程执行。这种调度策略可以确保每个线程都有机会执行,但可能导致线程切换频繁,增加上下文切换开销。
  5. 最高响应比优先调度(Highest Response Ratio Next, HRRN):最高响应比优先调度是一种既考虑线程等待时间又考虑线程优先级的调度策略。线程的响应比定义为(等待时间 + 服务时间)/ 服务时间,响应比较高的线程更有可能获得处理器资源。这种调度策略可以在保证公平性的同时,尽量减少线程的等待时间。

线程的基本方法

?start()

启动线程。这个方法会调用线程的 run() 方法,使线程开始执行。

代码语言:javascript
复制
Thread thread = new Thread(() -> {
    // 线程执行的代码
});
thread.start(); // 启动线程

?run()

线程执行的代码。这个方法需要在实现 Runnable 接口的类中重写,或者在继承 Thread 类的子类中重写。

代码语言:javascript
复制
class MyRunnable implements Runnable {
    @Override
    public void run() {
        // 线程执行的代码
    }
}

Thread thread = new Thread(new MyRunnable());
thread.start();

或者:

代码语言:javascript
复制
class MyThread extends Thread {
    @Override
    public void run() {
        // 线程执行的代码
    }
}

MyThread thread = new MyThread();
thread.start();

?join()

等待线程执行完成。这个方法会阻塞当前线程,直到被调用的线程执行完成。

代码语言:javascript
复制
Thread thread = new Thread(() -> {
    // 线程执行的代码
});
thread.start();
thread.join(); // 等待线程执行完成

?sleep(long millis)

使当前线程暂停执行指定的时间(以毫秒为单位)。这个方法需要在 try 块中调用,因为它可能抛出 InterruptedException 异常。

代码语言:javascript
复制
try {
    Thread.sleep(1000); // 使当前线程暂停1秒
} catch (InterruptedException e) {
    e.printStackTrace();
}

?interrupt()

中断线程。这个方法会设置线程的中断标志,线程可以通过检查 isInterrupted() 方法来响应中断。

代码语言:javascript
复制
Thread thread = new Thread(() -> {
    while (!Thread.currentThread().isInterrupted()) {
        // 线程执行的代码
    }
});
thread.start();
thread.interrupt(); // 中断线程

?isAlive()

检查线程是否仍在运行。如果线程已经启动并且尚未终止,则返回 true

代码语言:javascript
复制
Thread thread = new Thread(() -> {
    // 线程执行的代码
});
thread.start();
boolean isAlive = thread.isAlive(); // 检查线程是否仍在运行

?getId()

获取线程的唯一标识符。

代码语言:javascript
复制
Thread thread = new Thread(() -> {
    // 线程执行的代码
});
thread.start();
long threadId = thread.getId(); // 获取线程的唯一标识符

?getName()

setName(String name)获取和设置线程的名称。

代码语言:javascript
复制
Thread thread = new Thread(() -> {
    // 线程执行的代码
});
thread.setName("MyThread"); // 设置线程名称
String threadName = thread.getName(); // 获取线程名称

?getPriority()

setPriority(int priority):获取和设置线程的优先级。

代码语言:javascript
复制
Thread thread = new Thread(() -> {
    // 线程执行的代码
});
thread.setPriority(Thread.MAX_PRIORITY); // 设置线程优先级
int threadPriority = thread.getPriority(); // 获取线程优先级

?currentThread()

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

代码语言:javascript
复制
Thread currentThread = Thread.currentThread(); // 获取当前正在执行的线程

?yield()

当线程调用 yield() 方法时,它会主动放弃当前的 CPU 时间片,让自己从运行状态(RUNNING)变为就绪状态(RUNNABLE)。这样,其他具有相同优先级的线程就有机会获得 CPU 时间片并执行。

需要注意的是,yield() 方法并不保证一定会使当前线程立即停止执行。线程调度器可以自由决定是否立即调度其他线程。实际上,yield() 方法的效果往往取决于具体的操作系统和 JVM 实现。在某些情况下,调用 yield() 可能只是让当前线程稍作休息,然后很快再次获得 CPU 时间片。

yield() 方法的使用场景相对较少,通常不推荐过度依赖它来控制线程的执行顺序。更好的做法是使用其他同步工具,如 wait()notify()SemaphoreLock 等,来实现线程间的协调和控制。

t1 线程在循环到 2 时调用 yield(),可能会让出 CPU 给 t2 线程执行。但是,这并不是绝对的,取决于线程调度器的决策。

代码语言:javascript
复制
public class YieldExample {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("Thread t1: " + i);
                if (i == 2) {
                    Thread.yield(); // 放弃CPU时间片
                }
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("Thread t2: " + i);
            }
        });

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

线程类型

用户线程(User Thread)

用户线程是由程序员创建和管理的线程。它们通常用于执行特定任务,如处理用户输入、执行计算任务等。用户线程的创建和管理完全由程序员控制。

代码语言:javascript
复制
Thread userThread = new Thread(() -> {
    // 线程执行的代码
});
userThread.start();

守护线程(Daemon Thread)

守护线程是一种特殊类型的线程,其主要作用是为其他线程提供服务。守护线程在后台运行,不会阻止 JVM 的正常终止。当所有非守护线程(即用户线程)都结束时,守护线程会自动终止。守护线程通常用于执行后台任务,如垃圾回收、内存管理等。

代码语言:javascript
复制
Thread daemonThread = new Thread(() -> {
    // 线程执行的代码
});
daemonThread.setDaemon(true); // 设置为守护线程
daemonThread.start();

主线程(Main Thread)

主线程是 Java 程序的入口点。当 Java 程序启动时,JVM 会创建一个主线程来执行 main 方法。主线程通常负责启动其他线程、初始化程序等任务。当主线程结束时,JVM 会等待所有非守护线程都结束后才会终止。

代码语言:javascript
复制
public class MainThreadExample {
    public static void main(String[] args) {
        // 主线程执行的代码
    }
}

线程池线程(Thread Pool Thread)

线程池线程是由线程池管理的线程。线程池是一种用于管理和复用线程的机制,它可以提高系统性能和资源利用率。线程池线程在线程池中被创建和管理,当有任务需要执行时,线程池会分配一个空闲的线程池线程来执行任务。任务完成后,线程池线程会返回线程池,等待下一个任务。

代码语言:javascript
复制
ExecutorService threadPool = Executors.newFixedThreadPool(5);
threadPool.execute(() -> {
    // 线程执行的代码
});
threadPool.shutdown();

定时器线程(Timer Thread)

定时器线程是用于执行定时任务的线程。Java 提供了 java.util.Timer 类来实现定时任务。定时器线程会在指定的时间间隔内执行任务,或者在指定的时间点执行任务。

代码语言:javascript
复制
Timer timer = new Timer();
timer.schedule(new TimerTask() {
    @Override
    public void run() {
        // 线程执行的代码
    }
}, 1000); // 延迟1秒后执行任务

写在最后

并发编程是一种编程范式,它允许多个任务在同一时间段内独立运行。在Java中,并发编程主要关注如何在多个线程之间有效地共享资源和协调操作,以实现高性能和响应能力。

在现代软件开发中,随着硬件技术的发展,多核处理器已经成为主流。为了充分利用多核处理器的性能,我们需要编写并发程序。并发编程不仅可以提高程序的性能,还可以提高程序的响应能力和资源利用率。这对于提高用户体验和系统吞吐量具有重要意义。

我们需要根据具体需求和场景选择合适的并发编程技巧和工具。通过深入学习和实践并发编程,我们可以更好地理解Java并发编程的原理和应用,从而在实际项目中实现高性能、可扩展的应用程序。在未来的工作中,我将继续关注并发编程的最新动态和技术,以便为项目和团队提供更好的支持和指导。

我正在参与2024腾讯技术创作特训营最新征文,快来和我瓜分大奖!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 线程的故事
  • 线程的状态
    • 新建(New):
      • 就绪(Runnable):
        • 运行(Running):
          • 阻塞(Blocked):
            • 阻塞的三种分类:
          • 等待(Waiting):
            • 超时等待(Timed Waiting):
              • 终止(Terminated):
              • 线程调整优先级
              • 线程调度策略
              • 线程的基本方法
                • ?start()
                  • ?run()
                    • ?join()
                      • ?sleep(long millis)
                        • ?interrupt()
                          • ?isAlive()
                            • ?getId():
                              • ?getName()
                                • ?getPriority()
                                  • ?currentThread()
                                    • ?yield()
                                    • 线程类型
                                      • 用户线程(User Thread)
                                        • 守护线程(Daemon Thread)
                                          • 主线程(Main Thread)
                                            • 线程池线程(Thread Pool Thread)
                                              • 定时器线程(Timer Thread)
                                              • 写在最后
                                              领券
                                              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档


                                              http://www.vxiaotou.com