多线程详解①----关于线程的创建及相关介绍

139 阅读14分钟

多线程

我们首先要了解什么是线程进程

  • 进程:
    • 进程是一个在内存中个独立运行的程序,每个进程都有一个独立的内存空间。
    • 进程是系统运行的基本单位,因此进程是动态的。

    任务管理器中的.exe文件都是一个单独的进程。

  • 线程:
    • 线程是进程的一个执行单元,负责当前进程的程序执行。
    • 每一个进程最少有一个线程。
    • 线程是一个比进程更小的单位。
    • 同类的进程共享堆和方法区资源,但是每个线程都有自己的程序计数器、虚拟机栈和本地方法栈。

    所以系统在产生一个线程,或是在各个线程之间切换工作时,负担比进程小得多。

当我们了解了线程和进程的关系之后。我们就要更加深入的去了解JAVA线程

  1. JAVA如何实现多线程
  2. 多线程开启
  3. 多线程需要注意的问题
    .....一会进行加

JAVA如何实现多线程

JAVA实现多线程有三种方法

  1. 继承Thread
  2. 实现Runnable接口
  3. 通过Callable接口
  • 我们先来看一下第①种方法
    Main
public class Main {
	public static void main(String[] args) {
		Thread a1 = new A1();
		a1.setName("A1");
		a1.start();
		for(int i = 1;i<100;i++) {
			System.out.println(Thread.currentThread().getName()+"==="+i);
		}
	}
}

A1

public class A1 extends Thread {
	@Override
	public void run() {
		// TODO Auto-generated method stub
		super.run();
		for(int i = 1;i<100;i++) {
			System.out.println(Thread.currentThread().getName()+"==="+i);
		}
	}
}

我们可以看一下输出结果,已经实现了最基本的多线程开启。但是我不提议用这个方法,因为

JAVA是单继承的

,所以你继承了Thread类,就无法继承了其他的类了,这在JAVA羡慕中时不可取的。当然我们还有其他的方法。

  • 现在来看看第②种方法
    Main
public class Main {
	public static void main(String[] args) {
		Runnable a1 = new A1();
		Thread thread = new Thread(a1);
		thread.setName("a1");
		thread.start();
		for(int i = 1;i<100;i++) {
			System.out.println(Thread.currentThread().getName()+"==="+i);
		}
	}
}

A1

public class A1 implements Runnable {
	@Override
	public void run() {
		for(int i = 1;i<100;i++) {
			System.out.println(Thread.currentThread().getName()+"==="+i);
		}
	}
}

同样把项目复制下来,去看看输出结果,我们就知道它同样实现多线程,这个方法要比第一个方法好很多,因为它是去实现接口,而不是继承。
现在这两个方法都有一个不是缺点的缺点,他们都没有一个输出,当然JAVA的设计者也想到了这个东西,我们继续来看看。

  • 最后的第③种方法 Main
public class Main {
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		Callable<Integer> a1 = new A1();
		
		FutureTask<Integer> iFutureTask = new FutureTask<>(a1);
		
		Thread thread = new Thread(iFutureTask,"a1");
		thread.start();
		for(int i = 1;i<100;i++) {
			System.out.println(Thread.currentThread().getName()+"==="+i);
		}
		System.out.println(iFutureTask.get());
	}
}

A1

public class A1 implements Callable<Integer> {
	@Override
	public Integer call() throws Exception {
		// TODO Auto-generated method stub
		Integer j = 0;
		for(int i = 1;i<100;i++) {
			j++;
			System.out.println(Thread.currentThread().getName()+"==="+i);
		}
		return j;
	}
}

这个Callable接口是Runnable接口的增强版,有返回值,有异常抛出。

多线程的类和接口介绍

我们已经了解了多线程是怎么开启的,但是对其内部的API我们还是缺少了解,所以我们现在讲更加深入的去观察。
这里我们就不放具体的类了,我们只是去把经常使用的方法和实例对象介绍一下

  • Runnable接口。
    我们首先来看看Runnable接口。
    • Runnable接口只有一个方法,就是run方法。
    • 它还有一个@FunctionalInterface注解,表面是一个函数式接口,可以用lombok表达式。

这里我们先来看一下它的注释代码

 * The <code>Runnable</code> interface should be implemented by any
 * class whose instances are intended to be executed by a thread. The
 * class must define a method of no arguments called <code>run</code>.

Runnable接口应该由实例打算由线程执行的任何类实现。该类必须定义一个没有参数的方法,称为run。

这里的解释解释就是我们要重写该接口的run()方法

 * <p>
 * This interface is designed to provide a common protocol for objects that
 * wish to execute code while they are active. For example,
 * <code>Runnable</code> is implemented by class <code>Thread</code>.
 * Being active simply means that a thread has been started and has not
 * yet been stopped.
 * <p>

该接口旨在为希望在活动状态下执行代码的对象提供通用协议。例如,Runnable由Thread类实现。处于活动状态仅表示线程已启动但尚未停止。

这个接口只是一个协议接口。实现Runnable接口的类,需要有Thread类去开启。

 * In addition, <code>Runnable</code> provides the means for a class to be
 * active while not subclassing <code>Thread</code>. A class that implements
 * <code>Runnable</code> can run without subclassing <code>Thread</code>
 * by instantiating a <code>Thread</code> instance and passing itself in
 * as the target.  In most cases, the <code>Runnable</code> interface should
 * be used if you are only planning to override the <code>run()</code>
 * method and no other <code>Thread</code> methods.
 * This is important because classes should not be subclassed
 * unless the programmer intends on modifying or enhancing the fundamental
 * behavior of the class.

另外,Runnable提供了一种在不继承Thread的情况下使类处于活动状态的方法。实现Runnable的类可以通过实例化Thread实例并将其自身作为目标传递而无需继承Thread的子类来运行。在大多数情况下,如果您仅打算覆盖run()方法而没有其他Thread方法,则应使用Runnable接口。这很重要,因为除非程序员打算修改或增强类的基本行为,否则不应将类归为子类。

介绍类Runnable接口的一些情况,在什么时候使用

现在,我们再来看看方法的注释

@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}

当使用实现Runnable接口的对象创建线程时,启动线程会导致在单独执行的线程中调用对象的run()方法。

说明启动线程会开启时,会开启对象中重写的run()方法。

  • Thread类 当进去后我们首先发现Thread类其实是实现了Runnable接口,这就说明了Thread类,为什么可以可以直接实现多线程。 现在我们来看一下Thread类的方法和注释
 *A <i>thread</i> is a thread of execution in a program. The Java
 * Virtual Machine allows an application to have multiple threads of
 * execution running concurrently.

Thread类是程序中的执行线程。 Java虚拟机允许应用程序具有多个并发运行的执行线程。

说明:JVM(java虚拟机)允许多个线程并发

 * <p>
 * Every thread has a priority. Threads with higher priority are
 * executed in preference to threads with lower priority. Each thread
 * may or may not also be marked as a daemon. When code running in
 * some thread creates a new <code>Thread</code> object, the new
 * thread has its priority initially set equal to the priority of the
 * creating thread, and is a daemon thread if and only if the
 * creating thread is a daemon.
 * <p>

每个线程都有一个优先级。 具有较高优先级的线程优先于具有较低优先级的线程执行。 每个线程可能会也可能不会被标记为守护程序。 当在某个线程中运行的代码创建新的Thread对象时,新线程的优先级最初设置为与创建线程的优先级相等,并且当且仅当创建线程是守护程序时,该线程才是守护程序线程。

说明:线程具有优先级,新创建出来的线程的优先级是一般的

 * When a Java Virtual Machine starts up, there is usually a single
 * non-daemon thread (which typically calls the method named
 * <code>main</code> of some designated class). The Java Virtual

当 Java 虚拟机启动时,通常都会有单个非守护线程(它通常会调用某个指定类的 main 方法)

当我们启动java程序时,程序会开启一个非守护线程。线程的名字为main

 * Machine continues to execute threads until either of the following
 * occurs:
 * <ul>
 * <li>The <code>exit</code> method of class <code>Runtime</code> has been
 *     called and the security manager has permitted the exit operation
 *     to take place.
 * <li>All threads that are not daemon threads have died, either by
 *     returning from the call to the <code>run</code> method or by
 *     throwing an exception that propagates beyond the <code>run</code>
 *     method.
 * </ul>

Java 虚拟机会继续执行线程,直到下列任一情况出现时为止:

  • 调用了Runtime类的exit方法,并且安全管理器允许退出操作发生。
  • 非守护线程的所有线程都已停止运行,无论是通过从对run()方法的调用中返回,还是通过抛出一个传播到run()方法之外的异常。

介绍多线程的停止方法

* <p>
 * There are two ways to create a new thread of execution. One is to
 * declare a class to be a subclass of <code>Thread</code>. This
 * subclass should override the <code>run</code> method of class
 * <code>Thread</code>. An instance of the subclass can then be
 * allocated and started. For example, a thread that computes primes
 * larger than a stated value could be written as follows:
 * <hr><blockquote><pre>
 *     class PrimeThread extends Thread {
 *         long minPrime;
 *         PrimeThread(long minPrime) {
 *             this.minPrime = minPrime;
 *         }
 *
 *         public void run() {
 *             // compute primes larger than minPrime
 *             &nbsp;.&nbsp;.&nbsp;.
 *         }
 *     }
 * </pre></blockquote><hr>
 * <p>
 * The following code would then create a thread and start it running:
 * <blockquote><pre>
 *     PrimeThread p = new PrimeThread(143);
 *     p.start();
 * </pre></blockquote>
 * <p>
 * The other way to create a thread is to declare a class that
 * implements the <code>Runnable</code> interface. That class then
 * implements the <code>run</code> method. An instance of the class can
 * then be allocated, passed as an argument when creating
 * <code>Thread</code>, and started. The same example in this other
 * style looks like the following:
 * <hr><blockquote><pre>
 *     class PrimeRun implements Runnable {
 *         long minPrime;
 *         PrimeRun(long minPrime) {
 *             this.minPrime = minPrime;
 *         }
 *
 *         public void run() {
 *             // compute primes larger than minPrime
 *             &nbsp;.&nbsp;.&nbsp;.
 *         }
 *     }
 * </pre></blockquote><hr>
 * <p>
 * The following code would then create a thread and start it running:
 * <blockquote><pre>
 *     PrimeRun p = new PrimeRun(143);
 *     new Thread(p).start();
 * </pre></blockquote>
 * <p>

创建新执行线程有两种方法。

  • 将类声明为 Thread 的子类。该子类应重写 Thread 类的 run() 方法。
  • 创建线程的另一种方法是声明实现 Runnable 接口的类。

说明创建线程的几种方法,以及开启方法

 *
 * Every thread has a name for identification purposes. More than
 * one thread may have the same name. If a name is not specified when
 * a thread is created, a new name is generated for it.
 * <p>
 * Unless otherwise noted, passing a {@code null} argument to a constructor
 * or method in this class will cause a {@link NullPointerException} to be
 * thrown.

每个线程都有一个标识名,多个线程可以同名。如果线程创建时没有指定标识名,就会为其生成一个新名称。

说明:线程是有名字,如果没有去指定,那么,JAVA虚拟机会自动生成一个姓名。


这里我们以及介绍完了`Thread`类的类注释文档。
我们来看一下它的方法 > 这里不介绍构造方法,主要注重基本方法。

方法名说明
static int activeCount()返回当前线程线程组中活动线程的数目
void checkAccress()判定当前运行的线程是否有权修改该线程
(不理解)
static currentThread返回对当前正在执行的线程对象的引用。
static void dumpStack()将当前线程的线程组的堆栈跟踪打印至标准错误流。
static enumerate()将当前线程的线程组及其子组中的每一个活动线程复制到指定的数组。
long getId()返回当前线程的标识符
String getName()返回该线程的姓名
int getPriority()返回该线程的优先级
ThreadGroup getStart返回该线程的状态
ThreadGroup getThreadGroup()返回该线程所属的线程组
static boolean holdsLock(Object obj)当且仅当当前线程在指定的对象上保持监视器锁时,才返回 true
void interrupt()中断这个线程
boolean isAlive()测试这个线程是否活着
static boolean interrupted()测试当前线程是否已经中断
boolean isAlive()测试当前线程是否属于活动状态
boolean isDaemon()测试该线程是否为守护线程
boolean isInterrupted()测试线程是否中断
void join()测试线程是否中断
void setDaemon(boolean on)将该线程标记为守护线程或用户线程
void name(String name)改变线程的名称
void setPriority()更改线程优先级
void sleep(long millis)在指定毫秒内让当前正在执行的线程睡觉
void start()
使该线程开始执行,
JVM开启线程的方法
static void yield()暂停当前正在执行的线程

以上我们讲完Thread类的方法后,在进行讲解它的一些常量值。

常量值说明默认值
public static final int MIN_PRIORITY线程可以具有的最低优先级1
public static final int NORM_PRIORITY分配给线程的默认优先级5
MAX_PRIORITY线程可以具有的最高优先级10

讲解完这里的方法和一些常量值,这个Thread类也就差不多了。接下来我们去看看最后一个方法。

  • Callable<V>接口
  • Callable接口 这是我们需要了解多线程创建的最后一个接口,之前我们说过了Callable接口和其他两个方法不同的是,它是有返回值。而且它不在java.lang包下,它在java.util.concurrent包。
    现在让我来给它进行详细介绍 首先它是一个函数式接口,可以使用lombok表达式,进行编码。它的接口使用了泛型。这里的泛型我们会在接下来的方法里介绍
 * A task that returns a result and may throw an exception.
 * Implementors define a single method with no arguments called
 * {@code call}.

返回结果并可能引发异常的任务。 实现者定义一个没有参数的单一方法,称为call。

它用来返回数据的方法抛出了一个异常,并且该方法是一个无参方法,该方法称为call

 * <p>The {@code Callable} interface is similar to {@link
 * java.lang.Runnable}, in that both are designed for classes whose
 * instances are potentially executed by another thread.  A
 * {@code Runnable}, however, does not return a result and cannot
 * throw a checked exception.
 *

Callable接口类似于Runnable ,因为它们都是为其实例可能由另一个线程执行的类设计的。 然而,A Runnable不返回结果,也不能抛出被检查的异常。

Callable接口时Runnable接口的增加版。有异常抛出和返回值

 * <p>The {@link Executors} class contains utility methods to
 * convert from other common forms to {@code Callable} classes.

该Executors类包含的实用方法,从其他普通形式转换为Callable类。

这句话,不理解

我们继续看看它的方法

    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;

计算一个结果,如果不能这样做,就会抛出一个异常。

然后我们来看一下FutureTask<V>
它实现了RunnableFuture接口,而RunnableFuture接口则分别继承了Runnable接口和Future<V>接口。

 * A cancellable asynchronous computation.  This class provides a base
 * implementation of {@link Future}, with methods to start and cancel
 * a computation, query to see if the computation is complete, and
 * retrieve the result of the computation.  The result can only be
 * retrieved when the computation has completed; the {@code get}
 * methods will block if the computation has not yet completed.  Once
 * the computation has completed, the computation cannot be restarted
 * or cancelled (unless the computation is invoked using
 * {@link #runAndReset}).

可取消的异步计算。 该类提供了一个Future的基本实现 ,具有启动和取消计算的方法,查询计算是否完整,并检索计算结果。 结果只能在计算完成后才能检索; 如果计算尚未完成,则get方法将阻止。 一旦计算完成,则无法重新启动或取消计算(除非使用runAndReset()调用计算 )。

 * <p>A {@code FutureTask} can be used to wrap a {@link Callable} or
 * {@link Runnable} object.  Because {@code FutureTask} implements
 * {@code Runnable}, a {@code FutureTask} can be submitted to an
 * {@link Executor} for execution.

A FutureTask可用于包装Callable或Runnable对象。 因为FutureTask实现Runnable ,一个FutureTask可以提交到一个Executor执行。

 * <p>In addition to serving as a standalone class, this class provides
 * {@code protected} functionality that may be useful when creating
 * customized task classes.

除了作为独立类之外,此类还提供了protected功能,在创建自定义任务类时可能很有用。

方法说明
boolean cancel(boolean mayInterruptIfRunning)尝试取消执行任务
V get()等待计算完成,然后检索其结果
boolean isDone()返回true表示任务完成
boolean isCancelled()如果此任务在正常完成之前取消,则返回true
void run()将此未来设置为其计算结果,除非已被取消

说实话FutureTask类的注释和其中的方法,我并不是特别了解,我将在之后重新把这个部分重新讲解一次。

里面所有的方法我基本上是参考JDK在线文档