java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java启动线程

一文详解如何在Java中启动线程

作者:老谭说架构

今天要跟大家聊一聊Java多线程的原理和使用方式,Java多线程是提高程序性能的重要机制,了解线程生命周期、同步机制、线程池和线程间通信等概念,可以帮助我们编写出高效、可靠的多线程程序,需要的朋友可以参考下

1. 简介

今天要跟大家聊一聊Java多线程的原理和使用方式,Java多线程是提高程序性能的重要机制。了解线程生命周期、同步机制、线程池和线程间通信等概念,可以帮助我们编写出高效、可靠的多线程程序。

Java 线程的生命周期可以分为以下几个状态:

1. 新建 (New):  当使用 new Thread() 创建线程对象时,线程处于新建状态。此时线程尚未启动,还没有分配系统资源。

2. 就绪 (Runnable):  当调用线程对象的 start() 方法时,线程进入就绪状态。此时线程已准备好运行,但尚未获得 CPU 时间片,需要等待操作系统分配。

3. 运行 (Running):  线程获得 CPU 时间片,开始执行线程体代码,进入运行状态。

4. 阻塞 (Blocked):  当线程遇到以下情况时,会进入阻塞状态:

5. 终止 (Terminated):  线程执行完 run() 方法中的代码,或者遇到异常而退出,进入终止状态。

线程状态转换:

其他重要概念:

2. 运行线程的基础知识

我们可以利用Thread框架轻松的编写一些在并行线程中运行的逻辑。

让我们尝试一个基本的例子,通过扩展Thread类:

public class NewThread extends Thread {
    public void run() {
        long startTime = System.currentTimeMillis();
        int i = 0;
        while (true) {
            System.out.println(this.getName() + ": New Thread is running..." + i++);
            try {
                //Wait for one sec so it doesn't print too fast
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ...
        }
    }
}

现在我们编写第二个类来初始化并启动我们的线程:

public class SingleThreadExample {
    public static void main(String[] args) {
        NewThread t = new NewThread();
        t.start();
    }
}

我们应该 对处于NEW状态(相当于未启动)的线程 调用start()方法。否则,Java 将抛出IllegalThreadStateException异常的实例。

现在假设我们需要启动多个线程:

public class MultipleThreadsExample {
    public static void main(String[] args) {
        NewThread t1 = new NewThread();
        t1.setName("MyThread-1");
        NewThread t2 = new NewThread();
        t2.setName("MyThread-2");
        t1.start();
        t2.start();
    }
}

我们的代码看起来仍然非常简单并且与我们在网上找到的示例非常相似。

当然,这与可用于生产的代码还相去甚远,以正确的方式管理资源至关重要,以避免过多的上下文切换或过多的内存使用。

因此,为了做好生产准备,我们现在需要编写额外的样板来处理:

如果我们愿意,我们可以为所有这些情况甚至更多情况编写自己的代码,但我们为什么要重新发明轮子呢?

3. ExecutorService框架

ExecutorService实现了线程池设计模式(也称为复制工作器或工作组模型),并负责我们上面提到的线程管理,此外它还添加了一些非常有用的功能,如线程可重用性和任务队列 。

线程可重用性尤其重要:在大型应用程序中,分配和释放许多线程对象会产生大量内存管理开销。

利用工作线程,我们可以最大限度地减少线程创建造成的开销。 为了简化池配置,ExecutorService带有一个简单的构造函数和一些自定义选项,例如队列的类型、最小和最大线程数及其命名约定。

4. 使用执行器启动任务

有了这个强大的框架,我们可以把思维从启动线程转变为提交任务。

让我们看看如何向执行器提交异步任务:

ExecutorService executor = Executors.newFixedThreadPool(10);
...
executor.submit(() -> {
    new Task();
});

我们可以使用两种方法:execute(不返回任何内容)和submit(返回封装了计算结果的Future )。

5. 使用CompletableFutures启动任务

要从Future对象中检索最终结果,我们可以使用 该对象中可用的get 方法,但这会阻塞父线程,直到计算结束。

或者,我们可以通过在任务中添加更多逻辑来避免阻塞,但我们必须增加代码的复杂性。 Java 1.8 在Future构造之上引入了一个新框架,以便更好地处理计算结果:CompletableFuture。

CompletableFuture 实现了CompletableStage,它增加了大量方法来附加回调并避免了在结果准备好后运行操作所需的所有管道。

提交任务的实现就简单多了:

CompletableFuture.supplyAsync(() -> "Hello");

supplyAsync接受一个Supplier,其中包含我们想要异步执行的代码——在我们的例子中是 lambda 参数。

该任务现在隐式提交给 ForkJoinPool.commonPool() ,或者我们可以指定我们喜欢的Executor作为第二个参数。

6. 运行延迟或周期性任务

在处理复杂的 Web 应用程序时,我们可能需要在特定时间(或许是定期)运行任务。

Java 有一些工具可以帮助我们运行延迟或重复的操作:

6.1. 计时器

计时器是一种用于安排任务在后台线程中将来执行的工具。

任务可以被安排一次性执行,或者定期重复执行。

如果我们想在延迟一秒钟后运行任务,让我们看看代码是什么样的:

TimerTask task = new TimerTask() {
    public void run() {
        System.out.println("Task performed on: " + new Date() + "n" 
          + "Thread's name: " + Thread.currentThread().getName());
    }
};
Timer timer = new Timer("Timer");
long delay = 1000L;
timer.schedule(task, delay);复制

现在让我们添加一个重复的计划:

timer.scheduleAtFixedRate(repeatedTask, delay, period);复制

这一次,任务将在指定的延迟后运行,并且在经过一段时间后重复运行。

6.2. ScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor具有与 Timer类 类似的方法:

ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2);
ScheduledFuture<Object> resultFuture
  = executorService.schedule(callableTask, 1, TimeUnit.SECONDS);

为了结束我们的示例,我们使用 scheduleAtFixedRate() 来执行重复任务:

ScheduledFuture<Object> resultFuture
 = executorService.scheduleAtFixedRate(runnableTask, 100, 450, TimeUnit.MILLISECONDS);

上述代码将在初始延迟 100 毫秒后执行一项任务,之后每 450 毫秒执行一次相同的任务。

如果处理器不能在下一次发生之前及时完成处理任务,则 ScheduledExecutorService 将等到当前任务完成后再开始下一个任务。

为了避免这段等待时间,我们可以使用 scheduleWithFixedDelay() ,正如其名称所述,它可以保证任务迭代之间的固定长度延迟。

6.3. 哪个工具更好?

如果我们运行上述例子,计算的结果看起来是一样的。

那么, 我们如何选择正确的工具

当一个框架提供多种选择时,了解底层技术以做出明智的决定非常重要。

让我们尝试更深入地探究一下。

计时器:

ScheduledThreadPoolExecutor:

7. Future和ScheduledFuture之间的区别

在我们的代码示例中,我们可以观察到ScheduledThreadPoolExecutor 返回特定类型的Future:ScheduledFuture。

ScheduledFuture 扩展了Future和Delayed接口,因此继承了额外的方法getDelay  ,该方法返回与当前任务相关的剩余延迟。它由RunnableScheduledFuture扩展,并添加了一个方法来检查任务是否是周期性的。

ScheduledThreadPoolExecutor 通过内部类ScheduledFutureTask实现所有这些构造,并使用它们来控制任务生命周期。

8. 结论

Java 多线程在各种应用场景中都发挥着重要作用,可以显著提高程序效率和响应速度。以下列举一些常见的 Java 多线程使用场景:

1. 并发处理:

2. 用户界面响应:

3. 并行计算:

4. 任务调度:

5. 异步操作:

6. 其他应用场景:

以上就是一文详解如何在Java中启动线程的详细内容,更多关于Java启动线程的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文