这一系列的文章暂不涉及Java多线程开发中的底层原理以及JMM、JVM部分的解析(将另文总结),主要关注实际编码中Java并发编程的核心知识点和应知应会部分。

说在前面,Java并发编程的实质,是线程对象调用start方法启动多线程,而线程对象则必须是Thread类或其子类实现。Runnable和Callable的作用类似于Comparable、Serializable,是用于被并发的类实现的接口,从而使得Thread类可以在初始化时传入这个被并发的类。此是大前提。本文从多线程实现和启动出发,对这些类或接口予以说明。

Thread

通过Thread的子类创建多线程的步骤如下:

1. 创建Thread的子类,并重写run()方法,该方法即为线程执行体。

2. 创建Thread子类的对象,即为线程对象。

3. 调用线程对象的start()方法启动线程。

 public class TestThread extends Thread{

     public TestThread(String name) {
setName(name);
}
@Override
public void run() {
while(!interrupted())
System.out.println(getName() + "线程执行了");
}
public static void main(String[] args) { TestThread t1 = new TestThread("first");
TestThread t2 = new TestThread("second");
//setDaemon()设置线程为守护线程
// t1.setDaemon(true);
// t2.setDaemon(true);
t1.start();
t2.start();
t1.interrupt();
}
}

Runnable

需要并发执行的类,可以通过实现Runnable接口,作为Thread的Target来创建线程对象。

 public class TestRunnable implements Runnable{

     @Override
public void run() {
while(true) {
System.out.println("thread running...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} public static void main(String[] args) {
//传入TestRunnable对象作为Target, 开启线程
Thread t = new Thread(new TestRunnable());
t.start();
//采用匿名内部类的方式创建和启动线程
new Thread() {
@Override
public void run() {
System.out.println("Thread的匿名内部类");
}
}.start();
//父类采用匿名实现Runnable接口, 并由子类继承
new Thread(new Runnable() { @Override
public void run() {
System.out.println("父类的线程");
}
}) {
@Override
public void run() {
System.out.println("子类的线程");
}
}.start();
}
}

Callable和Future

Java5开始提供了Callable接口,用于现有多线程开发的强力补充。Callable接口提供一个call()方法来构造线程执行体。

1. call()方法可以有返回值

2. call()方法可以声明抛出异常

因此Callable接口没有继承Runnable接口,不能直接作为Thread类的Target来构造线程对象,所以Java5提供了Future接口来代表call方法的返回值。

Future提供了FutureTask实现类,该实现类实现了Future接口和Runnable接口,像桥梁一样把线程执行体和线程对象连接了起来。

Future接口提供了若干公共方法来操作Callable任务:

  • boolean cancel(boolean mayInterruptIfRunning): 试图取消Future里关联的Callable任务
  • V get():返回Callable任务里call方法的返回值。调用该方法会导致阻塞,必须等子线程完成后才得到返回值
  • V get(long timeout, TimeUnit unit):最多阻塞timeout和unit指定的时间,超时将抛出TimeoutException异常
  • boolean isCancelled():Callable任务正常完成前被取消,则返回true
  • boolean isDone():Callable任务已完成,则返回true

创建并启动有返回值的线程步骤如下:

1. 创建Callable接口的实现类,并实现call方法作为线程执行体,再创建类的实例。Java8中可通过Lambda表达式进行。

2. 使用FutureTask类来包装Callable实现类的对象

3. 使用FutureTask作为Thread对象的target

4. 使用FutureTask对象的get方法获取子线程执行后的返回值

Callable接口和FutureTask实现类的底层是基于接口回调技术实现,具体可参考:基于接口回调详解JUC中Callable和FutureTask实现原理

 public class TestCallable implements Callable<Integer>{
//实现Callable并重写call方法作为线程执行体, 并设置返回值1
@Override
public Integer call() throws Exception {
System.out.println("Thread is running...");
Thread.sleep(3000);
return 1;
} public static void main(String[] args) throws InterruptedException, ExecutionException {
//创建Callable实现类的对象
TestCallable tc = new TestCallable();
//创建FutureTask类的对象
FutureTask<Integer> task = new FutureTask<>(tc);
//把FutureTask实现类对象作为target,通过Thread类对象启动线程
new Thread(task).start();
System.out.println("do something else...");
//通过get方法获取返回值
Integer integer = task.get();
System.out.println("The thread running result is :" + integer);
}
}

总结一下,虽然继承Thread类的开发方式相对简单,但因为Java单继承的限制,一般建议通过实现Runnable或Callable接口来创建并启动多线程。

Java并发编程之线程创建和启动(Thread、Runnable、Callable和Future)的更多相关文章

  1. Java并发编程:如何创建线程?

    Java并发编程:如何创建线程? 在前面一篇文章中已经讲述了在进程和线程的由来,今天就来讲一下在Java中如何创建线程,让线程去执行一个子任务.下面先讲述一下Java中的应用程序和进程相关的概念知识, ...

  2. 【转】Java并发编程:如何创建线程?

    一.Java中关于应用程序和进程相关的概念 在Java中,一个应用程序对应着一个JVM实例(也有地方称为JVM进程),一般来说名字默认是java.exe或者javaw.exe(windows下可以通过 ...

  3. 2、Java并发编程:如何创建线程

    Java并发编程:如何创建线程? 在前面一篇文章中已经讲述了在进程和线程的由来,今天就来讲一下在Java中如何创建线程,让线程去执行一个子任务.下面先讲述一下Java中的应用程序和进程相关的概念知识, ...

  4. Java并发编程:线程的创建

    Java并发编程:线程的创建 */--> code {color: #FF0000} pre.src {background-color: #002b36; color: #839496;} J ...

  5. Java并发编程:线程池的使用

    Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...

  6. Java并发编程:线程池的使用(转)

    Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...

  7. Java并发编程:线程池的使用(转载)

    转载自:https://www.cnblogs.com/dolphin0520/p/3932921.html Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实 ...

  8. Java并发编程:线程池的使用(转载)

    文章出处:http://www.cnblogs.com/dolphin0520/p/3932921.html Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实 ...

  9. [转]Java并发编程:线程池的使用

    Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...

随机推荐

  1. 一目了然带你看lniux

    第一部分    熟悉Linux基本操作 一.初识Linux 1.Linux特点 ◊  开放性 ◊  多用户 ◊  多任务 ◊  良好的用户界面 ◊  设备独立性 ◊  丰富的网络功能 ◊  可靠的系统 ...

  2. Cocos2DX开发:记录遇到的一些问题和解决方法

    今天看了一下以前学习cocos2dx时记录的一些笔记,主要是在实际中遇到的一些问题,整理了一下,就成为了这篇文章,便于自己以后查找,也为一些新手提供点经验. 这篇文章会一直更新,将自己之后开发中遇到的 ...

  3. C#用Oracle.DataAccess中连接Oracle要注意版本问题!转)

    一般人,不包括全部平时在开发中使用的都是32位的PC机,所以安装的也是Oracle32位的客户端.但是一般服务器都是64位的,安装的也是 64位的Oracle客户端,如果要部署使用Oracle.Dat ...

  4. leetcode_11. Container With Most Water

    leetcode_11. Container With Most Water 一,问题: Given n non-negative integers a1, a2, ..., an, where ea ...

  5. AndroidStudio更改包名

    最近开发一个项目 和以前开发的某一个功能类似 不想再重新搭建界面 从零开始去写... 就想把原来的项目copy一份 但是这样的话安装在手机中会把原来的项目覆盖掉 这是因为它们的applicationI ...

  6. 人工智能AI芯片与Maker创意接轨 (中)

    在人工智能AI芯片与Maker创意接轨(上)这篇文章中,介绍人工智能与深度学习,以及深度学习技术的应用,了解内部真实的作业原理,让我们能够跟上这波AI新浪潮.系列文来到了中篇,将详细介绍目前市面上的各 ...

  7. selenium 基本常用操作

    from selenium import webdriverfrom selenium.webdriver.common.action_chains import ActionChains #鼠标操作 ...

  8. 《how tomcat works》阅读笔记 - 2 - 门面设计模式,避免强制转换

    在第二章 2.3节中 try { servlet = (Servlet) myClass.newInstance(); servlet.service((ServletRequest) request ...

  9. Array.Copy 数据是克隆吗?

    偶然看到 Array.Copy 方法的时候,想到,它是否是克隆,又是否是深克隆. 做了一个测试 public class abc { public string hello; } [TestMetho ...

  10. php 常用英语小汇

    bstract抽象的 -挨伯丝拽克特 access存取.访问 -挨克色丝 account账户 -厄靠恩特 action动作 -爱克身 activate激活 -爱克特维特 active活动的 -爱克得 ...