从基础了解Java线程


Java 线程是一种在一个程序上同时运行多个进程的机制。它也是一个术语,指的是java.lang.Thread类,用于在Java中使用线程。

嗯,很多工作,如果人们分工,可以很快完成。通过为每个任务分配人员,甚至可以同时完成不同的任务。当然,这比单独工作更有效率。

同样的,如果你在一个程序中同时运行多个进程,你可以在短时间内高效地处理它们。机制是线程。线程现在很常见。

本文将从思想的基础和Java中如何使用线程,使用线程时应该注意的事项,以及与线程相关的话题,重点为初学者讲解。

本文基于Java 13的语言规范和API。该示例已确认可在Java 13环境中工作。

1. 1. 线程的基本思想和用法

让我们快速浏览一下线程并体验Java线程。在Java中,创建和运行线程非常容易。 你所要做的就是像普通方法一样创建你想在线程中运行的进程,并要求java.lang.Thread “运行这个进程”。

1-1 线程思维

线程允许程序根据程序员的需要增加执行处理的单元数。

线程主要用于最大化计算机的处理能力(尤其是多核和多处理器) 。例如,程序中线程的使用如下。

  • 同时做不同的事情:一个线程可以与网络通信,而另一个线程可以处理用户执行的屏幕操作。
  • 减少处理时间:将大问题分成可以同时处理的小问题,在线程中同时处理小问题,以减少整体时间。
  • 增加单位时间的处理量:如果你的计算机具备同时运行线程的能力,可以增加单位时间的处理量。

如果您想到您周围的软件,您可能会认为您正在同时运行多个进程。现在使用线程在Web浏览器中运行任何东西已经司空见惯。

1-1-1 如果您将线程与便利店进行比较

如果您想到程序之外的线程,请想到便利店。尤其是喜欢中午人满为患的便利店。如果便利店只有一台收银台,可以立即排队结账。但是,如果您有多个收银员,您可以同时进行多次付款,排队时间会更短。此外,如果您有多个店员,您可以同时做不同的工作(检查、拾取物品、清洁等)。

如果便利店是一个程序,那么店员就是一个线程。它们的共同点是它们都是做一些工作或处理的单位。并且虽然不能立即增加业务员的数量,但程序可以根据需要快速创建和移动线程。

1-2 继承 Thread 类并尝试使用它

现在让我们在Java中实际使用线程。使用线程的一种方法是创建一个继承java.lang.Thread类的类,实例化它并运行它。

1-2-1 创建一个线程并尝试移动它

在继承Thread的类中,在run方法中写下要在Thread中执行的进程。如果你能正常写入一个方法,你就可以做大部分的事情,比如读取文件、网络通信、数值计算等。

ThreadSample

class ThreadSample extends Thread {
public void run() {
System.out.println(" Running in thread ");
}
}

要运行线程,请创建继承此线程的类的实例并调用start方法。仅此一项,就会创建一个新线程,并且可以在该线程中运行run方法中描述的进程。

ThreadExecutor

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

如果您尝试一次运行ThreadExecutor,您将看到以下输出。恭喜。我能够出色地使用线程。

在这里,即使我没有以编程方式调用ThreadSample运行,也会显示字符。换句话说,线程自动调用了run方法。

重要的一点是程序员自己不会调用run。您无法判断线程何时会调用run ,它将在线程准备好时调用。

程序员在使用线程时,有很多事情是无法控制的。与所谓的单线程程序有一些区别。

1-2-2 尝试同时运行多个线程

在之前的程序中,我只是创建了一个线程并运行它。但是,一个线程必须同时运行多个线程,不是吗?

那么接下来,让我们创建三个线程并同时运行它们。现在它变得更像多线程编程。

ThreadSample

class ThreadSample extends Thread {
public void run() {
System.out.println(" 线程 " + getName() + " 上运行 ");
}
}

如果输出消息相同,您将不知道是否有三个不同的线程同时运行,因此请尝试打印出分配给每个线程的唯一名称。

run 中调用的getName方法是Thread类的一个可以获取线程名称的方法。ThreadSample是Thread的子类,因此您可以从run调用getName。

ThreadExecutor

class ThreadExecutor {
	public static void main(String[] args) {
		ThreadSample t1 = new ThreadSample();
		ThreadSample t2 = new ThreadSample();
		ThreadSample t3 = new ThreadSample();
		t1.start();
		t2.start();
		t3.start();
	}
}

现在让我们再次运行ThreadExecutor 。

 线程 Thread-0 上运行     线程 Thread-2 上运行     线程 Thread-1 上运行

并且,像这样输出了3行不同的线程名称。即使程序相同,每个线程的执行结果也会不同。这意味着运行每个进程的线程是不同的 , 三个线程是从当前进程分支出来的,每个线程都是独立运行的 。

1-3 尝试将它与Thread 类和Runnable接口一起使用

使用线程的另一种方法是将要在线程中运行的进程创建为java.lang.Runnable的实现类,并让该Runnable由Thread类的实例运行。实际上,这是使用的。

Runnable 是一个接口,意思是“可以运行的东西”,唯一的抽象方法是返回值void和不带参数的运行。Runnable不仅限于线程,还经常用于表达和实现您希望在Java中运行的处理。

并且使用Runnable,您可以清楚地将线程本身与您希望线程运行的进程分开。这种角色划分也是一种思维方式,可以使程序具有良好的可见性。

1-3-1.1 尝试从一个线程调用

现在让我们来看看实际的程序。RunnableSample实现Runnable。您所做的与继承Thread相同,只是run方法的实现。

RunnableSample

class runnable sample implements runnable {
    public void run ( ) {
       string thread name = thread.currentthread ( ).getName ( );
       System.out.println ("线程" + threadName + "上运行);
       }
      }

 创建 RunnableSample 的实例并将其传递给Thread的构造函数,该构造函数将Runnable作为参数以创建Thread的实例。然后调用Thread的start方法。 

ThreadExecutor.java

class ThreadExecutor {
    public static void main(String[] args) {
        RunnableSample r = new RunnableSample();
        Thread t = new Thread(r); // 将 Runnable 传递给线程并实例化
        t.start();
    }
}

RunnableSample.run 不是以编程方式调用的,而是实际调用的。这与继承Thread时相同,从线程调用run方法。

1-3-2 尝试从一些线程调用

现在让我们从一些线程调用Runnable 。

我创建了三个Thread实例,方法是创建一个 RunnableSample 实例并将其传递给一个将Thread的Runnable作为参数的构造函数。之后,我们开始3 个线程。

ThreadExecutor

class ThreadExecutor {
    public static void main(String[] args) {
        RunnableSample r = new RunnableSample();
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        Thread t3 = new Thread(r);
        t1.start();
        t2.start();
        t3.start();
    }
}

这里,RunnableSample.run运行时的线程名称都是不同的。但是只有一个RunnableSample实例。这是什么意思。

这意味着由于运行了三个线程,每个线程调用了RunnableSample.run三次,结果为三行。 这意味着可以同时从多个线程调用一个方法。这不仅适用于实例方法,也适用于类方法。此外,实例和类字段也被多个线程同时读写。这些都是非常棘手的编程挑战,但我稍后会告诉你更多关于它们的信息。

2. 2. 如何使用线程方法

Thread 有很多有用的方法。其中,我会简单介绍如何使用程序中出现比较频繁的那些。

2-1 Thread.getId/getName获取线程的标识符/名称

java虚拟机给线程一个编号(标识符)来标识线程。除此之外,Java虚拟机会自动命名(程序员也可以命名)。

获取该线程的标识符和名称的方法是Thread.getId和getName。

ThreadExecutor.java

class ThreadExecutor {
    public static void main(String[] args) {
        Thread t1 = new Thread();
        Thread t2 = new Thread();
        //获取线程的标识符
        long t1Id = t1.getId();
        long t2Id = t2.getId();
        //获取线程的名称
        String t1Name = t1.getName();
        String t2Name = t2.getName();
        System.out.println("t1标识符为" + t1Id + "名称为" + t1Name );
        System.out.println("t2标识符为" + t2Id + "名称为" + t2Name );
    }
}

在程序中,有时您想知道当前进程在哪个线程中运行。然后,您可以使用这些方法找出正在运行的线程。

2-2 获取当前线程Thread.currentThread

从 Thread 继承的类可以很容易地看出你是谁作为一个线程。因为你是线程,所以可以直接调用自己的getId/getName。

ThreadSample.java

class ThreadSample extends Thread {
    public void run() {
        long id = getId();
        String name = getName();
       System.out.println("线程标识符:" + id + "name:" + name );
    }
}

另一方面,使用Runnable和从线程调用的方法,并不能立即清楚哪个线程正在运行。首先,您不能调用Thread.getId/getName,除非您以某种方式带入当前Thread。

在这种情况下,如果您调用Thread.currentThread ,您可以获得正在运行当前进程的Thread实例。您可以通过从那里调用 getId/getName找出哪个线程。

2-3 Thread.sleep暂停当前线程

有时,您可能想要暂停线程处理。例如,当你想等待一些处理时。在这种情况下,调用Thread.sleep。

您可以调用 Thread.sleep 以在参数指定的秒数内停止当前正在运行的线程。秒的单位是毫秒,重写的方法(Thread.sleep(long millis, int nanos))也可以以纳秒为单位指定。

ThreadSample.java

class ThreadSample extends Thread {
    public void run() {
        System.out.println("sleep开始");

        try {
            Thread.sleep(10000L); // 暂停当前​​线程10秒(10000ms)
        } catch (InterruptedException e) {
        }

        System.out.println("sleep结束");
    }
}

只有调用Thread.sleep的线程才会停止。其他线程将继续运行。此外,任何线程的执行都不能直接从另一个线程停止。

这是一个相当难以理解的点。如果sleep没有按预期工作,请使用getId或getName找出您正在睡觉的线程。

如果在停止的线程中发生稍后将描述的中断,则可以中途取消停止状态。在这种情况下,会引发捕获的异常InterruptedException。


最近在做自己的android项目,所有此文章也算是记录吧。