Java并发编程札记-(一)基础-02创建线程
来源:互联网 发布:ubuntu双系统磁盘分区 编辑:程序博客网 时间:2024/06/05 06:33
本文介绍Java中如何创建线程。
目录:
- Runnable-定义无返回值的任务
- Thread-线程构造器
- Callable-定义可以返回值的任务
- Executor-线程执行器
- 总结
在Java中,创建新执行线程有三种方法。
- 继承Thread。
- 实现Runnable。
- 实现Callable。
- 继承Executor。
我认为Runnable和Callable的作用只是定义任务,创建线程还是需要Thread构造器或者Executor执行器来完成。
Runnable
线程可以驱动任务,所以我们需要定义任务,Runnable可以来实现这个需求。
Runnable是一个接口,其中只声明了一个run方法。
public interface Runnable { public abstract void run();}
要想定义任务,只需要实现Runnable接口,并实现run方法。
下面通过实例看下如何通过实现Runnable创建和启动线程。
例1:
//定义任务class MyThread2 implements Runnable { // 重写run方法 public void run() { for (int i = 0; i < 3; i++) { System.out.println(Thread.currentThread().getName() + "在运行"); } }}public class MyRunnableTest { public static void main(String[] args) { // 创建任务 MyThread2 mt= new MyThread2(); //将任务附着在线程上 Thread t1 = new Thread(mt); Thread t2 = new Thread(mt); // 启动线程 t1.start(); t2.start(); }}
通过实现Runnable接口来定义的任务并无线程能力。要实现线程能力,必须显示的将一个任务附着到线程上。
运行结果为:
Thread-1在运行Thread-0在运行Thread-0在运行Thread-1在运行Thread-0在运行Thread-1在运行
Thread
Thread是一个类,它实现了Runnable,并额外提供了一些方法供用户使用。它的定义如下:
public class Thread implements Runnable {...}
在我看来,Thread就是一个线程构造器。
下面通过实例看下如何通过继承Thread创建和启动线程。
例2:
//定义线程类class MyThread extends Thread { //重写run方法 public void run() { for (int i = 0; i < 3; i++) { System.out.println(this.getName() + "在运行"); } }};public class MyThreadTest { public static void main(String[] args) { // 创建线程 MyThread t1 = new MyThread(); MyThread t2 = new MyThread(); //启动线程 t1.start(); t2.start(); for (int i = 0; i < 3; i++) { System.out.println(Thread.currentThread().getName() + "在运行"); } }}
运行结果为:
Thread-1在运行main在运行Thread-0在运行main在运行Thread-1在运行main在运行Thread-0在运行Thread-0在运行Thread-1在运行
Thread-0和Thread-1是我们创建的线程,因为我们没有给它们命名,所以JVM为它们分配了两个名字。名字为main的线程是main方法所在的线程,也是主线程。主线程的名字总是mian,非主线程的名字不确定。从执行结果中可以看到,几个线程不是顺序执行的,JVM和操作系统一起决定了线程的执行顺序。所以,你运行后可能看到的不是这样的打印顺序。
Callable
Callable接口类似于Runnable,两者作用都是定义任务。不同的是,被线程执行后,Callable可以返回结果或抛出异常。而Runnable不可以。Callable的返回值可以通过Future来获取。Callable的定义如下:
@FunctionalInterfacepublic interface Callable<V> { V call() throws Exception;}
下面通过实例看下如何通过实现Callable创建和启动线程并获取返回值。
import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.FutureTask;//定义任务class MyCallable implements Callable<String> { @Override public String call() throws Exception { return "This is returned string."; }}public class MyCallabeTest { public static void main(String[] args){ //创建有返回值的任务 Callable<String> callable = new MyCallable(); FutureTask<String> task = new FutureTask<>(callable); // 创建并启动线程 new Thread(task).start(); String result = null; try { // 调用get()阻塞主线程,并获取返回值 result = task.get(); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } System.out.println("return : " + result); }}
运行main方法后打印结果为return : This is returned string.
。
FutureTask实现了Runnable和Callable接口,所以它既可以作为任务附加到线程上(new Thread(task).start();
),又可以作为Future获取Callable的返回值(result = task.get();
)。
下面介绍另一种使用Callable和Future的方法。
import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Future;public class MyCallabeTest2 { public static void main(String[] args){ //创建有返回值的任务。Callable使用第一个例子中的MyCallable Callable<String> callable = new MyCallable(); //创建线程执行器 ExecutorService threadPool = Executors.newSingleThreadExecutor(); //通过线程执行器执行callable,并通过future获取返回结果 Future<String> future = threadPool.submit(callable); String result = null; try { // 调用get()阻塞主线程,并获取返回值 result = future.get(); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } finally{ threadPool.shutdown(); } System.out.println("return : " + result); }}
运行main方法后打印结果为return : This is returned string.
。
Java SE5的Java.util.concurrent包中的Executor(执行器)可以为我们管理线程,从而简化了并发编程。ExecutorService继承自Executor。下面会详细讲执行器。
下面介绍一种使用Callable和Future的获取多个返回值的方法。
import java.util.ArrayList;import java.util.Random;import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Future;//定义任务class MyCallable3 implements Callable<Integer> { @Override public Integer call() throws Exception { return new Random().nextInt(100); }}public class MyCallabeTest3 { public static void main(String[] args){ //创建线程执行器 ExecutorService threadPool = Executors.newSingleThreadExecutor(); //创建Future类型的集合 ArrayList<Future<Integer>> results = new ArrayList<Future<Integer>>(); //将Executor提交的任务的返回值添加到集合中 for(int i = 0;i<5;i++) results.add(threadPool.submit(new MyCallable3())); //遍历集合取出数据 try { // 调用get()阻塞主线程,并获取返回值 for(Future result:results) System.out.println(result.get()); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } finally{ threadPool.shutdown(); } }}
运行main方法后打印结果为
44946311
注释已经写得很清楚了,这里不再多说。
Executor
刚刚用到了ExecutorService,现在来详细讲下。Java SE5的Java.util.concurrent包中的Executor(执行器)可以为我们管理线程,从而简化了并发编程。Executor在客户端和任务执行之间提供了一个间接层,任务的执行由Executor而不是客户端来完成。Executor允许我们管理异步任务的执行,而无需显示地线程的生命周期。Executor是Java SE5中启动线程的优选方法。
执行器内容很多,以后会详细讲解。
总结
Thread和Runnable该如何选择?
- 因为Java不支持多继承,但支持多实现。所以从这个方面考虑Runnable比Thread有优势。
- Thread中提供了一些基本方法。而Runnable中只有run方法。如果只想重写run()方法,而不重写其他Thread方法,那么应使用Runnable接口。除非打算修改或增强Thread类的基本行为,否则应该选择Runnable。
从上面的分析可以看到,一般情况下Runnable更有优势。
run方法与start方法的区别
启动线程的方法是start方法。线程t启动后,t从新建状态转为就绪状态, 但并没有运行。 t获取CPU权限后才会运行,运行时执行的方法就是run方法。此时有t和主线程两个线程在运行,如果t阻塞,可以直接继续执行主线程中的代码。
直接运行run方法也是合法的,但此时并没有新启动一个线程,run方法是在主线程中执行的。此时只有主线程在运行,必须等到run方法中的代码执行完后才可以继续执行主线程中的代码。
可以将例2中代码t1.start();
改为t1.run()
,t2.start();
改为t2.run()
,你会发现打印结果会变为
Thread-0在运行Thread-0在运行Thread-0在运行Thread-1在运行Thread-1在运行Thread-1在运行main在运行main在运行main在运行
说明执行顺序为t1、t2、主线程(main)?不!this.getName()
并不能代表当前运行的线程名,只有Thread.currentThread().getName()
才能代表。可以将例2中代码this.getName()
改为Thread.currentThread().getName()
,会发现打印结果变为:
main在运行main在运行main在运行main在运行main在运行main在运行main在运行main在运行main在运行
这说明run方法根本就没有启动线程,从始至终运行的都只是主线程。
线程只能被启动一次
一个线程一旦被启动,它就不能再次被启动。在例2中在代码t1.start();
后再加一句t1.start();
,再运行会抛出java.lang.IllegalThreadStateException。
本文就讲到这里,想了解更多内容请参考
- Java并发编程札记-目录
END.
- Java并发编程札记-(一)基础-02创建线程
- Java并发编程札记-(一)基础-03线程的生命周期
- Java并发编程札记-(一)基础-05线程安全问题
- Java并发编程札记-(一)基础-01基本概念
- Java并发编程札记-(一)基础-04Thread详解
- Java并发编程札记-(一)基础-06synchronized详解
- Java并发编程札记-(一)基础-07volatile详解
- Java并发编程:线程创建
- java并发编程-创建线程
- Java并发编程(一)线程创建、生命周期、控制
- java并发编程之线程同步基础(一)
- java并发编程(一基本概念、线程基础)
- Java并发编程札记-目录
- Java 并发编程 基础 一
- 【Java并发编程】一.基础
- Java并发基础(一)-线程基础
- Java并发编程——线程池的使用(一) 简单创建线程池
- Java并发编程:如何创建线程
- Zookeeper介绍(三)——Zookeeper的安装
- 大型网站技术架构总结一二
- 读《大数据供应链》
- git错误:unable to auto-detect email address
- java语法与框架相遇
- Java并发编程札记-(一)基础-02创建线程
- Zookeeper介绍(四)——Zookeeper中的基本概念
- 3分钟了解入门「机器学习」该学习什么?(下)
- 3分钟了解入门「机器学习」该学习什么?(上)
- 【Python】【Java】【面试】【WordPress】【深度学习】【开源软件】| Chat · 预告
- 正则表达式从入门到实战
- java从包package中获取所有的Class
- WCF netTcpBinding:如何将net.tcp协议寄宿到IIS
- Visual Studio 2013 Tools for Unity