JavaRush /Java 博客 /Random-ZH /你不能用线程破坏 Java:第一部分 - 线程
Viacheslav
第 3 级

你不能用线程破坏 Java:第一部分 - 线程

已在 Random-ZH 群组中发布

介绍

多线程从最早的时候就内置在 Java 中。那么让我们快速了解一下多线程是什么。 你不能用线程毁掉 Java:第一部分 - 线程 - 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:第一部分 - 线程 - 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:第一部分 - 线程 - 3第一个数字是 PID 或进程 ID,即进程标识符。什么是流程?
Процесс — это совокупность codeа и данных, разделяющих общее виртуальное addressное пространство.
借助进程,不同程序的执行相互隔离:每个应用程序使用自己的内存区域,而不会干扰其他程序。我建议您更详细地阅读这篇文章:“ https://habr.com/post/164487/ ”。进程不能没有线程而存在,因此如果进程存在,则其中至少存在一个线程。在 Java 中这是如何发生的?当我们运行 Java 程序时,它的执行以main. 我们某种程度上进入了程序,所以这种特殊的方法main被称为入口点,或者“入口点”。该方法main必须始终public static void如此,以便 Java 虚拟机 (JVM) 可以开始执行我们的程序。有关更多详细信息,请参阅“为什么 Java main 方法是静态的? ”。事实证明,java启动器(java.exe或javaw.exe)是一个简单的应用程序(简单的C应用程序):它加载各种DLL,这些DLL实际上是JVM。Java 启动器进行一组特定的 Java 本机接口 (JNI) 调用。JNI 是连接 Java 虚拟机世界和 C++ 世界的机制。原来launcher并不是JVM,而是它的loader。它知道启动 JVM 时要执行的正确命令。知道如何使用 JNI 调用组织所有必要的环境。这种环境的组织还包括主线程的创建,通常称为main. 为了更清楚地了解 Java 进程中存在哪些线程,我们使用JDK 中包含的jvisualvm程序。知道进程的 pid,我们可以立即打开它的数据:jvisualvm --openpid айдипроцесса 你不能用线程毁掉 Java:第一部分 - 线程 - 4有趣的是,每个线程在内存中都有自己独立的区域分配给该进程。这种内存结构称为堆栈。堆栈由帧组成。框架是调用方法的点、执行点。框架也可以表示为 StackTraceElement(请参阅StackTraceElement的 Java API )。您可以在此处阅读有关分配给每个线程的内存的更多信息。如果我们查看Java API并搜索 Thread 这个词,我们会看到有一个类java.lang.Thread。正是这个类在 Java 中代表了一个流,我们必须使用这个类来工作。 Thread'ом Java не испортишь: Часть I — потоки - 5

java.lang.Thread

Java 中的线程表示为类的实例java.lang.Thread。值得立即理解的是,Java 中 Thread 类的实例本身并不是线程。这只是一种由 JVM 和操作系统管理的低级线程的 API。当我们使用 java 启动器启动 JVM 时,它会创建一个具有名称的主线程main和多个服务线程。正如 Thread 类的 JavaDoc 中所述: When a Java Virtual Machine starts up, there is usually a single non-daemon thread 线程有 2 种类型:守护进程和非守护进程。守护线程是在后台执行某些工作的后台线程(服务线程)。这个有趣的术语是对“麦克斯韦妖”的引用,您可以在维基百科有关“恶魔”的文章中阅读更多相关信息。如文档中所述,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);
}
除以零将导致错误,该错误将被处理程序捕获。如果您自己不指定处理程序,则默认处理程序实现将起作用,它将在 StdError 中显示错误堆栈。您可以在评论中阅读更多内容“ http://pro-java.ru/java-dlya-opytnyx/obrabotchik-neperexvachennyx-isklyuchenij-java/ ”。此外,线程具有优先级。您可以在文章“多线程中的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。他们不应该混淆,因为…… 如果我们直接运行该方法run,则不会启动新线程。该方法start要求 JVM 创建一个新线程。具有 Thread 后代的选项是不好的,因为我们将 Thread 包含在类层次结构中。第二个缺点是我们开始违反SOLID“单独责任”的原则,因为 我们的类同时负责管理线程和必须在该线程中执行的某些任务。哪个是对的?答案就在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();
    }
}
从Java 1.8开始它也是Runnable一个函数式接口。这使您可以更加漂亮地编写线程的任务代码:
public static void main(String []args){
	Runnable task = () -> {
		System.out.println("Hello, World!");
	};
	Thread thread = new Thread(task);
	thread.start();
}

全部的

因此,我希望从这个故事中可以清楚地了解什么是流、它们如何存在以及可以用它们执行哪些基本操作。在下一部分中,有必要了解线程如何相互交互以及它们的生命周期是什么。#维亚切斯拉夫
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION