多线程学习(二)---线程的创建

参考文章:

多线程三分钟就可以入个门了!
多线程全面详解总结
【多线程】Java创建多线程的4种方法

首先看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.

JVM允许多个线程并发执行

 * <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>
 * 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
 * Machine continues to execute threads until either of the following
 * occurs:

JVM启动时,通常会有一个叫做main的线程启动起来,这个线程没有守护线程

 * <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>

线程结束有两种情况:
1) 执行退出exit方法
2) 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>

第一种:继承Thread类,重写run方法

 * 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>

第二种:实现Runnable接口,重写run方法

 * 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.
 *
 * @author  unascribed
 * @see     Runnable
 * @see     Runtime#exit(int)
 * @see     #run()
 * @see     #stop()
 * @since   JDK1.0
 */

不能将参数设置为null,否则会引起空指针异常

注释提供了两种方式创建多线程:

  1. 继承Thread,重写run方法
  2. 实现Runnable接口,重写run方法

一. 继承Thread类

  1. 定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体
  2. 创建Thread子类的实例,即创建了线程对象
  3. 调用线程对象的start()方法来启动该线程
public class ExtendThreadTest extends Thread {

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(getName() + " : run " + i);
        }
    }

    public static void main(String[] args) {
        Thread thread1 = new ExtendThreadTest();
        Thread thread2 = new ExtendThreadTest();
        
        thread1.start();
        thread2.start();
        
        for (int i = 0; i < 3; i++) {
            System.out.println("main : run " + i);
        }
    }
}

运行结果:

Thread-0 : run 0
main : run 0
Thread-1 : run 0
main : run 1
Thread-0 : run 1
main : run 2
Thread-1 : run 1
Thread-1 : run 2
Thread-1 : run 3
Thread-1 : run 4
Thread-0 : run 2
Thread-0 : run 3
Thread-0 : run 4

二. 实现Runnable接口

  1. 定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体
  2. 创建 Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象
  3. 调用线程对象的start()方法来启动该线程
public class ImplementsRunnableTest implements Runnable {

    private int tick = 10;
    
    public void run() {
        while (true) {
            if(tick > 0){
                System.out.println(Thread.currentThread().getName() + "..." + tick--);
            }
        }
    }

    public static void main(String[] args) {
        ImplementsRunnableTest t = new ImplementsRunnableTest();
        new Thread(t).start();
        new Thread(t).start();
    }
}

运行结果:

Thread-0...10
Thread-1...9
Thread-1...7
Thread-1...6
Thread-1...5
Thread-1...4
Thread-0...8
Thread-1...3
Thread-0...2
Thread-1...1

三. 通过Callable和Future创建

  1. 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值
  2. 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。(FutureTask是一个包装器,它通过接受Callable来创建,它实现了RunnableFuture接口,而RunnableFuture又同时实现了Future和Runnable接口,所以间接的实现了Runnable接口)
  3. 使用FutureTask对象作为Thread对象的target创建并启动新线程
  4. 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
public class CallableTest implements Callable<String> {

    public String call() throws Exception {
        return Thread.currentThread().getName();
    }

    public static void main(String[] args) {
        // 创建callable实现类实例
        CallableTest t = new CallableTest();
        // 使用FutureTask类来包装Callable对象
        FutureTask<String> ft1 = new FutureTask<String>(t);
        FutureTask<String> ft2 = new FutureTask<String>(t);
        new Thread(ft1).start();
        new Thread(ft2).start();
        try {
            System.out.println(ft1.get());
            System.out.println(ft2.get());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行结果:

Thread-0
Thread-1

四. 通过线程池

  1. 通过以上方法创建一个线程
  2. 使用 Executors对象创建线程池
  3. 调用 ExecutorService 对象的submit() 或execute() 方法执行线程
  4. 当使用submit() 方法时,可以调用 Future对象的get() 方法来获得子线程执行结束后的返回值
public class ThreadPoolTest implements Callable<String> {

    public static void main(String[] args) {
        // 创建线程池
        ExecutorService e = Executors.newFixedThreadPool(5);
        ThreadPoolTest t = new ThreadPoolTest();
        for (int i = 0; i < 3; i++) {
            Future<String> submit = e.submit(t);
            try {
                System.out.println(submit.get());
            } catch (InterruptedException | ExecutionException exception) {
                exception.printStackTrace();
            }
        }
        e.shutdown();
    }

    public String call() throws Exception {
        return Thread.currentThread().getName();
    }

}

运行结果:

pool-1-thread-1
pool-1-thread-2
pool-1-thread-3

注:
创建线程可以使用submit或execute(),两者区别为:

  • submit() 有返回值,execute() 无返回值void execute(Runnable command);,利用Future .get()去的线程执行的结果,如果等于null,表示执行成功
  • submit()方法有3种参数类型,execute只有1种参数
void execute(Runnable command);

Future<?> submit(Runnable task);
<T> Future<T> submit(Runnable task, T result);
<T> Future<T> submit(Callable<T> task);

五. 四种创建方法的区别

  1. 使用创建Thread子类的方法,只能继承Thread类,所以不能再继承其它父类了,而实现了Runnable接口或Callable接口,还可继承其它的类,更加面向对象
  2. Runnable接口或Callable接口时,要访问当前线程必须使用Thread.currentThread() 方法,而继承Thread直接使用this即可获得当前线程
  3. Callable任务执行后有返回值,而Runnable没有返回值
  4. call方法可抛出异常,而run方法不可以

六. Java实现多线程需要注意的细节

不要将run()和start()搞混了

run()和start()方法区别:

  • run():仅仅是封装被线程执行的代码,直接调用是普通方法
  • start():首先启动了线程,然后再由jvm去调用该线程的run()方法。

jvm虚拟机的启动是单线程的还是多线程的?

  • 是多线程的。不仅仅是启动main线程,还至少会启动垃圾回收线程的,不然谁帮你回收不用的内存

总结

其实归根到底,所有的实现方法都是实现Runnable接口创建线程
只有在启动线程的start方法时,才有执行线程要完成的任务,即run方法

使用实现类的好处

  • 可以避免java中的单继承的限制
  • 将并发运行任务和运行机制解耦

猜你喜欢

转载自blog.csdn.net/a770794164/article/details/91989846