CodeGym /Java 博客 /随机的 /更好的结合:Java 和 Thread 类。第一部分——执行线程
John Squirrels
第 41 级
San Francisco

更好的结合:Java 和 Thread 类。第一部分——执行线程

已在 随机的 群组中发布

介绍

多线程从一开始就内置在 Java 中。那么,让我们简单地看一下这个叫做多线程的东西。 更好的结合:Java 和 Thread 类。 第一部分 — 执行线程 - 1我们以 Oracle 的官方课程作为参考点:“课程:“Hello World!”应用程序”。我们将稍微更改 Hello World 程序的代码,如下所示:

class HelloWorldApp {
    public static void main(String[] args) {
        System.out.println("Hello, " + args[0]);
    }
}
args是程序启动时传递的输入参数数组。将此代码保存到名称与类名称匹配且扩展名为.java. 使用javac实用程序编译它:javac HelloWorldApp.java. 然后,我们用一些参数运行我们的代码,例如,“Roger”:java HelloWorldApp Roger 更好的结合:Java 和 Thread 类。 第一部分 — 执行线程 - 2我们的代码目前有一个严重的缺陷。如果你不传递任何参数(即只执行“java HelloWorldApp”),那么我们会得到一个错误:

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0
        at HelloWorldApp.main(HelloWorldApp.java:3)
名为“main”的线程中发生异常(即错误)。那么,Java有线程吗?这是我们旅程的起点。

Java 和线程

要了解什么是线程,您需要了解 Java 程序是如何启动的。让我们改变我们的代码如下:

class HelloWorldApp {
    public static void main(String[] args) {
		while (true) { 
			// Do nothing
		}
	}
}
现在让我们用javac. 为方便起见,我们将在单独的窗口中运行我们的 Java 代码。在 Windows 上,这可以像这样完成start java HelloWorldApp:现在我们将使用jps实用程序查看 Java 可以告诉我们哪些信息: 更好的结合:Java 和 Thread 类。 第一部分 — 执行线程 - 3第一个数字是 PID 或进程 ID。什么是流程?

A process is a combination of code and data sharing a common virtual address space.
对于进程,不同的程序在运行时彼此隔离:每个应用程序在内存中使用自己的区域而不会干扰其他程序。要了解更多信息,我建议阅读本教程:进程和线程。一个进程不能没有线程存在,所以如果一个进程存在,那么它至少有一个线程。但是这在 Java 中是如何实现的呢?当我们启动一个 Java 程序时,执行从main方法开始。就好像我们是在步入程序,所以这个特殊的main方法就叫做入口点。该main方法必须始终是“public static void”,这样 Java 虚拟机 (JVM) 才能开始执行我们的程序。有关详细信息,为什么 Java main 方法是静态的?. 事实证明,Java 启动程序(java.exe 或 javaw.exe)是一个简单的 C 应用程序:它加载实际构成 JVM 的各种 DLL。Java 启动器进行一组特定的 Java 本机接口 (JNI) 调用。JNI 是一种连接 Java 虚拟机世界和 C++ 世界的机制。因此,启动器不是 JVM 本身,而是一种加载它的机制。它知道要执行的正确命令以启动 JVM。它知道如何使用 JNI 调用来设置必要的环境。设置这个环境包括创建主线程,当然它被称为“main”。为了更好地说明 Java 进程中存在哪些线程,我们使用jvisualvm工具,它包含在 JDK 中。知道了一个进程的 pid,我们就可以立即看到关于该进程的信息:jvisualvm --openpid <process id> 更好的结合:Java 和 Thread 类。 第一部分 — 执行线程 - 4有趣的是,每个线程在分配给该进程的内存中都有自己独立的区域。这种内存结构称为栈。堆栈由帧组成。帧表示方法的激活(未完成的方法调用)。框架也可以表示为 StackTraceElement(请参阅StackTraceElement的 Java API )。您可以在此处的讨论中找到有关分配给每个线程的内存的更多信息:“ Java (JVM) 如何为每个线程分配堆栈”。如果您查看Java API并搜索“Thread”一词,您会找到java.lang.Thread班级。这是在 Java 中代表线程的类,我们需要使用它。 更好的结合:Java 和 Thread 类。 第一部分 — 执行线程 - 5

java.lang.线程

在 Java 中,线程由类的实例表示java.lang.Thread。您应该立即明白 Thread 类的实例本身并不是执行线程。这只是一种由 JVM 和操作系统管理的低级线程的 API。当我们使用 Java 启动器启动 JVM 时,它会创建一个main名为“main”的线程和一些其他内务处理线程。正如 Thread 类的 JavaDoc 中所述: When a Java Virtual Machine starts up, there is usually a single non-daemon thread. 有两种类型的线程:守护进程和非守护进程。守护线程是在后台执行某些工作的后台(内务处理)线程。“恶魔”一词指的是麦克斯韦妖。您可以在这篇维基百科文章中了解更多信息。如文档中所述,JVM 会继续执行程序(进程),直到:
  • 调用Runtime.exit ()方法
  • 所有非守护线程都完成了它们的工作(没有错误或抛出异常)
一个重要的细节由此而来:守护线程可以在任何时候终止。因此,无法保证其数据的完整性。因此,守护线程适用于某些内务处理任务。例如,Java 有一个线程负责处理finalize()方法调用,即与垃圾收集器(gc) 相关的线程。每个线程都是组 ( ThreadGroup ) 的一部分。并且组可以是其他组的一部分,形成一定的层次结构或结构。

public static void main(String[] args) {
	Thread currentThread = Thread.currentThread();
	ThreadGroup threadGroup = currentThread.getThreadGroup();
	System.out.println("Thread: " + currentThread.getName());
	System.out.println("Thread Group: " + threadGroup.getName());
	System.out.println("Parent Group: " + threadGroup.getParent().getName());
}
组为线程管理带来秩序。除了组之外,线程还有自己的异常处理程序。看一个例子:

public static void main(String[] args) {
	Thread th = Thread.currentThread();
	th.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
		@Override
		public void uncaughtException(Thread t, Throwable e) {
			System.out.println("An error occurred: " + e.getMessage());
		}
	});
    System.out.println(2/0);
}
除以零将导致错误,该错误将被处理程序捕获。如果您不指定自己的处理程序,那么 JVM 将调用默认处理程序,它将异常的堆栈跟踪输出到 StdError。每个线程也有一个优先级。您可以在本文中阅读有关优先级的更多信息:多线程中的 Java 线程优先级

创建线程

如文档中所述,我们有两种创建线程的方法。第一种方法是创建自己的子类。例如:

public class HelloWorld{
    public static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("Hello, World!");  
        }
    }
    
    public static void main(String[] args) {
        Thread thread = new MyThread();
        thread.start();
    }
}
如您所见,任务的工作发生在run()方法中,但线程本身是在start()方法中启动的。不要混淆这些方法:如果我们un()直接调用 r 方法,则不会启动新线程。它是start()请求 JVM 创建新线程的方法。我们继承 Thread 的这个选项已经很糟糕了,因为我们将 Thread 包含在我们的类层次结构中。第二个缺点是我们开始违反“单一责任”原则。也就是说,我们的类同时负责控制线程和在该线程中执行的某些任务。什么是正确的方法?答案在run()我们覆盖的相同方法中找到:

public void run() {
	if (target != null) {
		target.run();
	}
}
target是一些java.lang.Runnable,我们可以在创建 Thread 类的实例时传递它。这意味着我们可以这样做:

public class HelloWorld{
    public static void main(String[] args) {
        Runnable task = new Runnable() {
            public void run() {
                System.out.println("Hello, World!");
            } 
        };
        Thread thread = new Thread(task);
        thread.start();
    }
}
Runnable从 Java 1.8 开始也是函数式接口。这使得为​​线程的任务编写更漂亮的代码成​​为可能:

public static void main(String[] args) {
	Runnable task = () -> { 
		System.out.println("Hello, World!");
	};
	Thread thread = new Thread(task);
	thread.start();
}

结论

我希望这个讨论能够阐明线程是什么、线程是如何产生的以及线程可以执行哪些基本操作。在下一部分中,我们将尝试了解线程如何相互交互并探索线程生命周期。 更好的结合:Java 和 Thread 类。第二部分 — 同步 更好地结合:Java 和 Thread 类。第 III 部分 — 更好地交互:Java 和 Thread 类。第 IV 部分 — Callable、Future 和朋友 更好地结合在一起:Java 和 Thread 类。第五部分 — Executor、ThreadPool、Fork/Join 更好地结合在一起:Java 和 Thread 类。第六部分——开火!
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION