参考文章:
多线程三分钟就可以入个门了!
多线程全面详解总结
【多线程】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
* . . .
* }
* }
* </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
* . . .
* }
* }
* </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,否则会引起空指针异常
注释提供了两种方式创建多线程:
- 继承Thread,重写run方法
- 实现Runnable接口,重写run方法
一. 继承Thread类
- 定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体
- 创建Thread子类的实例,即创建了线程对象
- 调用线程对象的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接口
- 定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体
- 创建 Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象
- 调用线程对象的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创建
- 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值
- 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。(FutureTask是一个包装器,它通过接受Callable来创建,它实现了RunnableFuture接口,而RunnableFuture又同时实现了Future和Runnable接口,所以间接的实现了Runnable接口)
- 使用FutureTask对象作为Thread对象的target创建并启动新线程
- 调用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
四. 通过线程池
- 通过以上方法创建一个线程
- 使用 Executors对象创建线程池
- 调用 ExecutorService 对象的submit() 或execute() 方法执行线程
- 当使用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);
五. 四种创建方法的区别
- 使用创建Thread子类的方法,只能继承Thread类,所以不能再继承其它父类了,而实现了Runnable接口或Callable接口,还可继承其它的类,更加面向对象
- Runnable接口或Callable接口时,要访问当前线程必须使用Thread.currentThread() 方法,而继承Thread直接使用this即可获得当前线程
- Callable任务执行后有返回值,而Runnable没有返回值
- call方法可抛出异常,而run方法不可以
六. Java实现多线程需要注意的细节
不要将run()和start()搞混了
run()和start()方法区别:
- run():仅仅是封装被线程执行的代码,直接调用是普通方法
- start():首先启动了线程,然后再由jvm去调用该线程的run()方法。
jvm虚拟机的启动是单线程的还是多线程的?
- 是多线程的。不仅仅是启动main线程,还至少会启动垃圾回收线程的,不然谁帮你回收不用的内存
总结
其实归根到底,所有的实现方法都是实现Runnable接口创建线程
只有在启动线程的start方法时,才有执行线程要完成的任务,即run方法
使用实现类的好处
- 可以避免java中的单继承的限制
- 将并发运行任务和运行机制解耦