JSON简介

JSON简介

JSON 是一种与开发语言无关的、轻量级的数据格式 - JavaScript Object Notation

JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。 易于人阅读和编写。同时也易于机器解析和生成。 它基于JavaScript Programming Language, Standard ECMA-262 3rd Edition - December 1999的一个子集。 JSON采用完全独立于语言的文本格式,但是也使用了类似于C语言家族的习惯(包括C, C++, C#, Java, JavaScript, Perl, Python等)。 这些特性使JSON成为理想的数据交换语言。

优点:易于人的阅读和编写,易于程序的解析和生产
基本格式:{key:value}

标准的json数据表示
数据结构:Object、Array
基本类型:String、number、true、false、null

因此如果需要表示时间类型的时候需要使用约定格式的字符串如"YYYY-MM-DD"  
然后在后台对数据进行解析

数据结构——object
使用花括号{}包含的键值对结构,key必须是String类型,value为任何基本类型或数据结构
数据结构——Array
使用中括号[]来起始,并用逗号来分割元素

下面这个网站可以帮助你解析json字符串,并生成文件:
JSON Editor Online

{
  "array": [
    1,
    2,
    3
  ],
  "boolean": true,
  "null": null,
  "number": 123,
  "object": {
    "a": "b",
    "c": "d",
    "e": "f"
  },
  "string": "Hello World"
}
注意,JSON文件里不允许注释

具体资料可以查阅-->JSON中文官方网站
这里还有所有语言的示例代码以及相关工具等等内容,十分详尽,如

Java:
    JSON-java.
    JSONUtil.
    jsonp.
    Json-lib.
    Stringtree.
    SOJO.
    json-taglib.
    Flexjson.
    JON tools.
    Argo.
    jsonij.
    fastjson.
    mjson.
    jjson.
    json-simple.
    json-io.
    JsonMarshaller.
    google-gson.
    Json-smart.
    FOSS Nova JSON.
    Corn CONVERTER.
    Apache johnzon.
    Genson.
    JSONUtil.
    cookjson.

JavaScript:
    JSON.
    json2.js.
    clarinet.
    Oboe.js.

利用org.Json构建json数据

利用org.Json在Java中使用JSONObject对象

Json是Android SDK的官方库
适合移动端开发

例子 JSON和对象的转换,在pom.xml中添加依赖包

<!--JAVA ASON--> 
<dependency>  <groupId>org.json</groupId>
  <artifactId>json</artifactId>
  <version>20090211</version>
 </dependency>

测试类

    @Test
    public void testJSONObject(){
        BasicConfigurator.configure();
        JSONObject jsonObject = new JSONObject();
        Object nullObject= null;//使编译器跳过对null类型对检查
        try {
            jsonObject.put("name","张全蛋");
            jsonObject.put("age","25.2");
            jsonObject.put("birthday","1990-01-01");
            jsonObject.put("school","富土康");
            jsonObject.put("major", new String[]{"理发", "挖掘机"});
            jsonObject.put("hasGirlFriend",false);
            jsonObject.put("hascar",nullObject);
            jsonObject.put("comment","这是一个注释,因为json格式里是不能使用常规注释手段的");

            System.out.println(jsonObject.toString());
        } catch (JSONException e) {
            e.printStackTrace();
        }
        //output
        //{"birthday":"1990-01-01","major":["理发","挖掘机"],"school":"富土康","name":"张全蛋","comment":"这是一个注释,因为json格式里是不能使用常规注释手段的","hasGirlFriend":false,"age":"25.2"}
    }
}

输出

  {
    "birthday": "1990-01-01",
    "major": [
      "理发",
      "挖掘机"
    ],
    "school": "富土康",
    "name": "张全蛋",
    "comment": "这是一个注释,因为json格式里是不能使用常规注释手段的",
    "hasGirlFriend": false,
    "age": "25.2"
  }

Json在Java中使用字符串生成JSONObject对象

@Test
public void createJSONObjectByString(){
    JSONObject jsonObject = null;
    String jsonStr ="{\"errno\":0,\"data\":true,\"errmsg\":\"success\"}";
    try {
        jsonObject = new JSONObject(jsonStr);
    } catch (JSONException e) {
        e.printStackTrace();
    }
    if(jsonObject != null){
        System.out.println(jsonObject.toString());
        //out:
        // {"errno":0,"data":true,"errmsg":"success"}
    }
}

Json在Java中使用MAP生成JSONObject对象


    @Test
    public void createJSONObjectByMap() {
        Object nullObject = null;//使编译器跳过对null类型对检查

        Map<String, Object> jsonObject = new HashMap<String, Object>();
        jsonObject.put("name", "张全蛋");
        jsonObject.put("age", "25.2");
        jsonObject.put("birthday", "1990-01-01");
        jsonObject.put("school", "富土康");
        jsonObject.put("major", new String[]{"理发", "挖掘机"});
        jsonObject.put("hasGirlFriend", false);
        jsonObject.put("hascar", nullObject);
        jsonObject.put("comment", "这是一个注释,因为json格式里是不能使用常规注释手段的");

        System.out.println(new JSONObject(jsonObject).toString());

        //out:{"birthday":"1990-01-01","major":["理发","挖掘机"],"school":"富土康","name":"张全蛋","comment":"这是一个注释,因为json格式里是不能使用常规注释手段的","hasGirlFriend":false,"age":"25.2","hascar":null}
    }

Json在Java中使用JavaBean对象来生成JSONObject对象

    @Test
    public void createJSONObjectbyJavaBean() {
        //常用
        Message msg = new Message();
        msg.errno = 0;
        msg.errmsg = "success";
        msg.data = true;
        System.out.println(new JSONObject(msg).toString());

        //out:
        //{"errno":0,"data":true,"errmsg":"success"}
    }

从文件读取JSON数据

依赖

    <!--org.apache.commons.io.FileUtils-->
    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.4</version>
    </dependency>

测试文件的内容 与测试类文件同级 名为success_message.json

    {
        "errno": 0,
        "errmsg":["智商余额不足","需发红包给刘老师@95820608进行充值!"],
        "data":true
    }

测试类中的测试方法


    @Test
    public void readJSONFile() throws IOException, JSONException {
        //从当前类的同源文件中查找特定路径的文件
        File file = new File(ReadJSONFileSample.class.getResource("/success_message.json").getFile());
        String content = FileUtils.readFileToString(file);
        JSONObject jsonObject = new JSONObject(content);
        if(!jsonObject.isNull("errno")){
            System.out.println("错误代码 --> " + jsonObject.getInt("errno"));
        }
        if(!jsonObject.isNull("errmsg")){
            System.out.println("错误信息 --> " + jsonObject.getString("errmsg").toString());
        }
        if(!jsonObject.isNull("data")){
            System.out.println("错误内容 --> " + jsonObject.getBoolean("data"));
        }
        //out
        //错误代码 --> 0
        //错误信息 --> ["智商余额不足","需发红包给刘老师@95820608进行充值!"]
        //错误内容 --> true

        //将JSONObject中的属性值为数组的值读取出来遍历
        //如果值不是数组则会导致报错!
        if(!jsonObject.isNull("errmsg")){
            JSONArray jsonArray = jsonObject.getJSONArray("errmsg");
            for (int i = 0; i < jsonArray.length(); i++) {
                String m = (String)jsonArray.get(i);
                System.out.println(m);
            }
        }
        //out
        //智商余额不足
        //需发红包给刘老师@95820608进行充值!
    }
}

使用Gson 来更加灵活的构建json数据

gson是google提供的开源api
Gson功能更强大可以在json格式和javaObject之间通过反射灵活转换
更适合服务端后台的开发

  • 优点:
  • 1.轻量, 支持将json字符串反向生成指定类的对象
  • 2.支持日期格式

使用Gson生成json

pom.xml 添加依赖

<!--GOOGLE GSON-->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.4</version>
</dependency>

测试类和测试方法


    //使用GSON注解更改序列化后JSON的属性名称
    protected class Message{
        @SerializedName("ERRNO")
        int errno;
        String errmsg;
        boolean data;
    }

    @Test
    public void gsonCreate(){
        Message msg = new Message();
        msg.errno = 0;
        msg.errmsg = "success";msg.data = true;

        Gson gson = new Gson();
        String jsonStr = gson.toJson(msg);
        System.out.println(jsonStr);
        //out:{"ERRNO":0,"errmsg":"success","data":true}
    }

使用gsonBuilder定制json的转换规则

    protected class FailMessage{
        private int errno;
        private String errmsg;
        private boolean data;
        private transient String  ignor;//transient 声明的属性不会被序列化
    }

    @Test
    public void gsonCreateByGsonBuilder(){
        FailMessage msg = new FailMessage();
        msg.errno = -1;
        msg.errmsg = "fail";msg.data = false;

        GsonBuilder gsonBuilder = new GsonBuilder();
        gsonBuilder.setPrettyPrinting();//美化格式后输出!方便调试
        gsonBuilder.setFieldNamingStrategy(new FieldNamingStrategy() {
        //通过回调函数修改反射得到的属性值
            @Override
            public String translateName(Field field) {
                if(field.getName().equals("errno")){
                    return ("error-code");
                }else{
                    return field.getName();
                }
            }
        });
        Gson gsonNew = gsonBuilder.create();
        System.out.println(gsonNew.toJson(msg));////美化格式后输出!方便调试
        //out:
        //        {
        //              "error-code": -1, //errno被修改为error-code
        //              "errmsg": "fail",
        //              "data": false
        //        }
    } 

使用gson反向将json字串转换成指定类

message.json文件内容如下:

    {
        "errno": 0,
        "errmsg":"智商余额不足,需发红包给刘老师@95820608进行充值!",
        "data":true
    }

测试类

public class Message {
  public int errno;
  public String errmsg;
  public boolean data;

    public int getErrno() {
        return errno;
    }

    public void setErrno(int errno) {
        this.errno = errno;
    }

    public String getErrmsg() {
        return errmsg;
    }

    public void setErrmsg(String errmsg) {
        this.errmsg = errmsg;
    }

    public boolean getData() {
        return data;
    }

    public void setData(boolean data) {
        this.data = data;
    }

    @Override
    public String toString() {
        return "Message{" +
                "errno=" + errno +
                ", errmsg='" + errmsg + '\'' +
                ", data=" + data +
                '}';
    }
}

测试方法

    @Test
    public void GsonReadTest() throws IOException {
        File file = new File(GsonSample.class.getResource("/message.json").getFile());
        String content = FileUtils.readFileToString(file);

        Gson gson = new Gson();
        //将json文件转换成指定类
        Message msg = gson.fromJson(content, com.niit.mvcdemo.model.Message.class);
        System.out.println(msg.toString());
        //out:
        //Message{errno=0, errmsg='智商余额不足,需发红包给刘老师@95820608进行充值!', data=true}
    }

反向将json字串转换成指定带有时间属性的类

    class Logger{
        private String content;
        private Date createtime;
        @Override
        public String toString() {
            return "Logger{content='" + content + '\'' + ", createtime=" + createtime.toString() +'}';
        }
    }

    @Test
    public void GsonFromJsonToObject() throws IOException {

        Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create();

        //使用gsonBuilder指定时间类型格式将json文件转换成指定类
        Logger logger = gson.fromJson("{\"content\":\"Application started...\",\"createtime\":\"1970-01-01 00:00:00\"}", json.GsonSample.Logger.class);
        System.out.println(logger.toLocalString());
        //out:Logger{content='Application started...', createtime=1970-1-1 0:00:00}
    }
小知识
yyyy-MM-dd HH:mm:ss
年-月-日 时:分:秒
大写是为了区分“月”与“分”
顺便说下HH为什么大写,是为了区分12小时制与24小时制。
小写的h是12小时制,大写的H是24小时制。
书写格式和语言规定有关,上述写法是Windows系统中的我们常见的写法,包括日期设置于办公软件在内。在其他语言中有类似的但使用符号或格式不同的写法。
有的时候我们会看到这样的格式:yyyy-M-d H:m:s
mm与m等,它们的区别为是否有前导零:H,m,s表示非零开始,HH,mm,ss表示从零开始。
比如凌晨1点2分,HH:mm显示为01:02,H:m显示为1:2。
以2014年1月1日凌晨1点1分1秒(当天是星期三)为例子介绍一下其他的:
yyyy/yyy/yy/y 显示为 2014/2014/14/4
        (3个y与4个y是一样的,为了便于理解多写成4个y)
MMMM/MMM/MM/M 显示为 一月/一月/01/1
        (4个M显示全称,3个M显示缩写,不过中文显示是一样的,英文就是January和J)
dddd/ddd/dd/d 显示为 星期三/周三(有的语言显示为“三”)/01/1
        (在英文中同M一样,4个d是全称,3个是简称;
dddd/ddd表示星期几,dd/d表示几号)
HH/H/hh/h 显示为 01/1/01 AM/1 AM
剩下的mm/m/ss/s只是前导零的问题了。
yyyy/M/d/dddd H:mm:ss 就是 2014年1月1日星期三 1:01:01

gson转换会自动将数组值映射成实体类对应的集合属性

注意Map会被映射成google自定义的Map类型..

class Product{
    private String title;
    private String[] array;
    private List list;
    private Set set;
    private Map map;
}

@Test
public void GsonMappingForCollection(){
    String jsonStr= "" +
            "{\n" +
            "  \"title\": \"product_id_1\",\n" +
            "  \"array\": [\n" +
            "    \"array1\",\n" +
            "    \"array2\"\n" +
            "  ],\n" +
            "  \"list\": [\n" +
            "    \"list1\",\n" +
            "    \"list2\"\n" +
            "  ],\n" +
            "  \"set\": [\n" +
            "    \"set2\",\n" +
            "    \"set1\"\n" +
            "  ],\n" +
            "  \"map\": {\n" +
            "    \"1\": \"map1\",\n" +
            "    \"2\": \"map2\"\n" +
            "  }\n" +
            "}"
            ;
    //通过gson将制定类的实例转化成json字符串
    Product product = new Gson().fromJson(jsonStr,GsonSample.Product.class);
    System.out.println(product.title.toString() + "\t\t" + product.title.getClass());
    //out:        product_id_1          class java.lang.String
    System.out.println(Arrays.toString(product.array) + "\t\t" + product.array.getClass());
    //out:        [array1, array2]      class [Ljava.lang.String;
    System.out.println(product.set.toString() + "\t\t" + product.set.getClass());
    //out:        [set2, set1]          class java.util.LinkedHashSet
    System.out.println(product.list.toString() + "\t\t" + product.list.getClass());
    //out:        [list1, list2]        class java.util.ArrayList
    System.out.println(product.map.toString() + "\t\t" + product.map.getClass());
    //out:        {1=map1, 2=map2}      class com.google.gson.internal.LinkedTreeMap
}

jackson实现Json序列化和反序列化

利用fasterxml.jackson实现JSON序列化和反序列化

Gradle依赖
fasterxml.jackson依赖jackson-core, jackson-databind和jackson-annotations.
示例如下:

compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.5'
compile group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.9.5'
compile group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: '2.9.5'

序列化和反序列化实现

JSON工具类
我们构造一个JSON工具类,实现JSON序列化和反序列化, 主要用到的类为com.fasterxml.jackson.databind.ObjectMapper, 代码示例如下:

package com.notepad.util;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.util.HashMap;
import java.util.Map;

public class JsonSerializer {

    /**
     * JSON序列化
     *
     * @param object 对象
     * @return JSON字符串
     */
    public static String serialize(Object object) {
        ObjectMapper mapper = new ObjectMapper();
        try {
            return mapper.writeValueAsString(object);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            return "";
        }
    }

    /**
     * JSON字符串反序列化
     *
     * @param jsonStr JSON字符串
     * @return a Map
     */
    public static Map deserialize(String jsonStr) {
        try {
            return deserialize(jsonStr, Map.class);
        } catch (Exception e) {
            e.printStackTrace();
            return new HashMap();
        }
    }

    public static <T> T deserialize(String jsonStr, Class<T> classType) throws Exception {
        return new ObjectMapper().readValue(jsonStr, classType);
    }
}

Entity
为测试我们编写的JSON工具类, 定义一个Entity对象。
为了构建的Entity对象被JSON工具类操作, 关于Entity有几点说明:

  • Entity对象必须有默认构造函数
  • 成员变量必须有对应的Setter方法
  • 可选: 可通过@JsonProperty自定义序列化和反序列化对应的字符的名称,如@JsonProperty(“UID”),则序列化时uid字段显示为UID,同理反序列化时找到字符串中UID对应的值,复制给uid。
  • 可选:可通过@JsonIgnore注解,过滤掉不需要进行序列化的成员变量。

示例如下:

package com.notepad.thinkingnote.domain;

import com.fasterxml.jackson.annotation.JsonProperty;

public class Entity {

    public Entity() {}

    public Entity(String uid, String name) {
        this.uid = uid;
        this.name = name;
    }

    /** 实体标识符 */
    @JsonProperty("UID")
    private String uid;

    @JsonProperty("name")
    private String name;

    public void setUid(String uid) {
        this.uid = uid;
    }

    public String getUid() {
        return uid;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

单元测试

编写单元测试,测试Entity的序列化和反序列化, 示例如下:

package com.notepad.thinkingnote.domain;

import com.notepad.util.JsonSerializer;
import org.junit.Test;

import static org.junit.Assert.*;

public class EntityTest {

    @Test
    public void testSerialize() throws Exception {
        Entity entity = new Entity("James", "James");
        System.out.println(JsonSerializer.serialize(entity));
    }

    @Test
    public void testDeserialize() throws Exception {
        String test = "{\"UID\":\"James\",\"name\":\"James\"}";
        Entity entity = JsonSerializer.deserialize(test, Entity.class);
        System.out.println(entity.getUid() + ":" + entity.getName());
    }
}

输出结果如下:
entity的序列化结果, 显示字段UID
{"UID":"James","name":"James"}

entity的序列化结果
James:James

UnrecognizedPropertyException异常解决

通过上面的介绍, 我们基本了解了JSON的序列化和反序列化,不过有时我们会遇到一种问题。
考虑这样一种情况,我们针对Http接口返回的JSON字符串,构建了一个具体的Entity对象,但是当Http接口中突然增加了一个字段type,如果还是按照原来方式解析会如何呢?

@Test
    public void testDeserialize() throws Exception {
        String test = "{\"UID\":\"James\",\"name\":\"James\", \"type\":\"entity\"}";
        Entity entity = JsonSerializer.deserialize(test, Entity.class);
        System.out.println(entity.getUid() + ":" + entity.getName());
    }

出现UnrecognizedPropertyException异常:

com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "type" ....

即对于新添加的type字符无法识别。如何解决这个问题呢?我们这里提供2种方法。

  • 方法1 JsonIgnoreProperties注解
    利用fasterxml.jackson提供的@JsonIgnoreProperties注解,针对无法识别的属性进行过滤。这里主要是修改需要进行反序列化的对象Entity,示例如下:
    针对无法识别的属性进行过滤

    @JsonIgnoreProperties(ignoreUnknown = true)
    public class Entity {...}
  • 方法2 DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
    方法1的修改需要对每一个需要进行反序列化的类进行修改, 不太方便。
    方法2通过修改JSON工具类的反序列化方法,设置DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES的值为false, 可以仅一次修改就适用全部对象。
    示例如下:

    public static <T> T deserialize(String jsonStr, Class<T> classType) throws Exception {
        // 添加configure, 设置DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES为false
        // 则对于无法识别的属性直接过滤
        return new ObjectMapper()
                .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
                .readValue(jsonStr, classType);
    }

Views: 34

Java多线程及Future用法

Java多线程及Future用法

同步和异步 - 比要发射10枚导弹,同步的方式就是上一枚导弹炸毁后才发射下一枚,而异步就是全部挨个发射出去,而不在乎它们是否击中目标,这种异步方式也被称为Fire and Forget。Kafka为了提高吞吐性能默认是异步发送消息的。为了更好的了解Kafka的Producer发送,我们先补充一些关于Java多线程的知识。

Java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。

  • 继承Thread创建线程(不推荐)
  • 实现Runnable接口创建线程
  • 实现Callable接口实现线程
  • 使用线程池Executor创建线程(推荐)

1.继承Thread实现线程

  我们先来看一下Thread的源码,它是一个类,同样也实现了Runnable接口

public Thread implements Runnable {
    /* Make sure registerNatives is the first thing <clinit> does. */
    private static native void registerNatives();
    static {
        registerNatives();
    }

    private volatile String name;
    private int            priority;
    private Thread         threadQ;
    private long           eetop;

    /* Whether or not to single_step this thread. */
    private boolean     single_step;

    /* Whether or not the thread is a daemon thread. */
    private boolean     daemon = false;

    /* JVM state */
    private boolean     stillborn = false;

    /* What will be run. */
    private Runnable target;

    /* The group of this thread */
    private ThreadGroup group;

    /* The context ClassLoader for this thread */
    private ClassLoader contextClassLoader;

    /* The inherited AccessControlContext of this thread */
    private AccessControlContext inheritedAccessControlContext;

    /* For autonumbering anonymous threads. */
    private static int threadInitNumber;
    private static synchronized int nextThreadNum() {
        return threadInitNumber++;
    }

    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

    /*
     * The requested stack size for this thread, or 0 if the creator did
     * not specify a stack size.  It is up to the VM to do whatever it
     * likes with this number; some VMs will ignore it.
     */
    private long stackSize;

    /*
     * JVM-private state that persists after native thread termination.
     */
    private long nativeParkEventPointer;

    /*
     * Thread ID
     */
    private long tid;

    /* For generating thread ID */
    private static long threadSeqNumber;

    /* Java thread status for tools,
     * initialized to indicate thread 'not yet started'
     */

    private volatile int threadStatus = 0;

    //......

}

通过继承Thread类来创建并启动多线程的一般步骤如下

  1. 定义Thread类的子类,并重写该类的run()方法,该方法的方法体就是线程需要完成的任务,run()方法也称为线程执行体。
  2. 创建Thread子类的实例,也就是创建了线程对象
  3. 启动线程,即调用线程的start()方法

代码示例:

public class  ThreadTest {

    public static void main(String[] args) {
        new MyThread().start();
    }

   static class MyThread extends Thread {//继承Thread
        public void run() {
            System.out.println("我是继承Thread类!! ");
        }
    }

}

2.实现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();
}

由于run()方法返回值为void类型,所以在执行完任务之后无法返回任何结果。

通过实现Runnable接口创建并启动线程一般步骤如下:

  1. 定义Runnable接口的实现类,一样要重写run()方法,这个run()方法和Thread中的run()方法一样是线程的执行体
  2. 创建Runnable实现类的实例,并用这个实例作为Thread的target来创建Thread对象,这个Thread对象才是真正的线程对象
  3. 第三部依然是通过调用线程对象的start()方法来启动线程

代码示例:

public class RunnableTest {

    public static void main(String[] args) {
        MyThread2 myThread=new MyThread2();
        Thread thread = new Thread(myThread);
        thread.start();
    }

    static class MyThread2 implements Runnable {
        @Override
        public void run() {
            System.out.println("我是实现Runnable接口!! ");
        }
    }
}

3.实现callable接口实现线程

  我们来看一下callable源码,它是一个接口:

@FunctionalInterface
public interface Callable<V> {
    /**
     * 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;
}

它和Runnable接口不一样的是,call()方法提供了2个额外功能:

  • call()方法可以有返回值
  • call()方法可以声明抛出异常

java5提供了Future接口来代表Callable接口里call()方法的返回值,并且为Future接口提供了一个实现类FutureTask,这个实现类既实现了Future接口,还实现了Runnable接口,因此可以作为Thread类的target。在Future接口里定义了几个公共方法来控制它关联的Callable任务。

那么怎么使用Callable呢?一般情况下是配合ExecutorService来使用的,在ExecutorService接口中声明了若干个submit方法的重载版本:

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

第一个submit方法里面的参数类型就是Callable。

暂时只需要知道Callable一般是和ExecutorService配合来使用的,具体的使用方法讲在后面讲述。

一般情况下我们使用第一个submit方法和第三个submit方法,第二个submit方法很少使用。

3.1 Future

我们来看一下Future的源码,它是一个接口,用来返回子线程的计算结果:

public interface Future<V> {

    /**
     * Attempts to cancel execution of this task.  This attempt will
     * fail if the task has already completed, has already been cancelled,
     * or could not be cancelled for some other reason. If successful,
     * and this task has not started when {@code cancel} is called,
     * this task should never run.  If the task has already started,
     * then the {@code mayInterruptIfRunning} parameter determines
     * whether the thread executing this task should be interrupted in
     * an attempt to stop the task.
     *
     * <p>After this method returns, subsequent calls to {@link #isDone} will
     * always return {@code true}.  Subsequent calls to {@link #isCancelled}
     * will always return {@code true} if this method returned {@code true}.
     *
     * @param mayInterruptIfRunning {@code true} if the thread executing this
     * task should be interrupted; otherwise, in-progress tasks are allowed
     * to complete
     * @return {@code false} if the task could not be cancelled,
     * typically because it has already completed normally;
     * {@code true} otherwise
     */
    boolean cancel(boolean mayInterruptIfRunning);

    /**
     * Returns {@code true} if this task was cancelled before it completed
     * normally.
     *
     * @return {@code true} if this task was cancelled before it completed
     */
    boolean isCancelled();

    /**
     * Returns {@code true} if this task completed.
     *
     * Completion may be due to normal termination, an exception, or
     * cancellation -- in all of these cases, this method will return
     * {@code true}.
     *
     * @return {@code true} if this task completed
     */
    boolean isDone();

    /**
     * Waits if necessary for the computation to complete, and then
     * retrieves its result.
     *
     * @return the computed result
     * @throws CancellationException if the computation was cancelled
     * @throws ExecutionException if the computation threw an
     * exception
     * @throws InterruptedException if the current thread was interrupted
     * while waiting
     */
    V get() throws InterruptedException, ExecutionException;

    /**
     * Waits if necessary for at most the given time for the computation
     * to complete, and then retrieves its result, if available.
     *
     * @param timeout the maximum time to wait
     * @param unit the time unit of the timeout argument
     * @return the computed result
     * @throws CancellationException if the computation was cancelled
     * @throws ExecutionException if the computation threw an
     * exception
     * @throws InterruptedException if the current thread was interrupted
     * while waiting
     * @throws TimeoutException if the wait timed out
     */
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

我们来看一下它的各个方法:

  • boolean cancel(boolean mayInterruptIfRunning):用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false,即如果取消已经完成的任务会返回false;如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若mayInterruptIfRunning设置为false,则返回false;如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true。
  • boolean isCancelled():如果在Callable任务正常完成前被取消,返回True
  • boolean isDone():若Callable任务完成,返回True
  • V get() throws InterruptedException, ExecutionException:返回Callable里call()方法的返回值,调用这个方法会导致程序阻塞,必须等到子线程结束后才会得到返回值
  • V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException:用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null

因为Future只是一个接口,所以是无法直接用来创建对象使用的,因此就有了下面的FutureTask

3.2 FutureTask

我们先来看一下FutureTask的实现:

public class FutureTask<V> implements RunnableFuture<V> {}

FutureTask类实现了RunnableFuture接口,我们看一下RunnableFuture接口的实现:

public interface RunnableFuture<V> extends Runnable, Future<V> {
    /**
     * Sets this Future to the result of its computation
     * unless it has been cancelled.
     */
    void run();
}

可以看出RunnableFuture继承了Runnable接口和Future接口,而FutureTask实现了RunnableFuture接口。所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。

FutureTask提供了2个构造器:

public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
}
public FutureTask(Runnable runnable, V result) {
        this.callable = Executors.callable(runnable, result);
        this.state = NEW;       // ensure visibility of callable
}

事实上,FutureTask是Future接口的一个唯一实现类。

3.3 使用FutureTask对象作为Thread对象的target创建并启动线程

接下来我们看如何创建并启动有返回值的线程:

  1. 创建Callable接口的实现类,并实现call()方法,然后创建该实现类的实例(从java8开始可以直接使用Lambda表达式创建Callable对象)。
  2. 使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值
  3. 使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口)
  4. 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

代码示例:

public class CallableAndFuture {
    public static void main(String[] args) {

        Callable<Integer> call = new Callable<Integer>() {
            public Integer call() throws Exception {
                System.out.println("计算线程正在计算结果...");
                Thread.sleep(3000);
                return 1;
            }
        };
        FutureTask<Integer> future = new FutureTask<>(call);

        new Thread(future,"有返回值的线程").start();//实质上还是以Callable对象来创建并启动线程

        try {
            System.out.println("子线程的返回值:" + future.get());//get()方法会阻塞,直到子线程执行结束才返回
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

    }
}

3.4 使用executor创建线程

3.4.1.使用Callable+Future获取执行结果

代码示例:

public class CallableAndFuture {

    public static void main(String[] args) {
        /** Executors提供了一系列工厂方法用于创先线程池,返回的线程池都实现了ExecutorService接口。   */
        ExecutorService executor = Executors.newCachedThreadPool();
        Task task = new Task();
        Future<Integer> result = executor.submit(task);

        executor.shutdown();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }

        System.out.println("主线程在执行任务");

        try {
            System.out.println("task运行结果"+result.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        System.out.println("所有任务执行完毕");
    }

    static class Task implements Callable<Integer> {
        @Override
        public Integer call() throws Exception {
            System.out.println("子线程在进行计算");
            Thread.sleep(3000);
            int sum = 0;
            for(int i=0;i<100;i++)
                sum += i;
            return sum;
        }
    }
}
3.4.2.使用Callable+FutureTask获取执行结果
public class CallableAndFuture2 {
    public static void main(String[] args) {
        //第一种方式
        ExecutorService executor = Executors.newCachedThreadPool();
        Task task = new Task();
        FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
        executor.submit(futureTask);
        executor.shutdown();

        //第二种方式,注意这种方式和第一种方式效果是类似的,只不过一个使用的是ExecutorService,一个使用的是Thread
        /*Task task = new Task();
        FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
        Thread thread = new Thread(futureTask);
        thread.start();*/

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }

        System.out.println("主线程在执行任务");

        try {
            System.out.println("task运行结果"+futureTask.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        System.out.println("所有任务执行完毕");
    }

    static class Task implements Callable<Integer> {
        @Override
        public Integer call() throws Exception {
            System.out.println("子线程在进行计算");
            Thread.sleep(3000);
            int sum = 0;
            for(int i=0;i<100;i++)
                sum += i;
            return sum;
        }
    }
}

4.使用线程池Executor创建线程

4.1 Executor执行Runnable

public class ExecutorRunnable {

    /**
     * 从结果中可以看出,pool-1-thread-1和pool-1-thread-2均被调用了两次,这是随机的,execute会首先在线程池中选择
     * 一个已有空闲线程来执行任务,如果线程池中没有空闲线程,它便会创建一个新的线程来执行任务。
     */
    public static void main(String[] args){
        ExecutorService executorService = Executors.newCachedThreadPool();
//      ExecutorService executorService = Executors.newFixedThreadPool(5);
//      ExecutorService executorService = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 5; i++){
            executorService.execute(new TestRunnable());
            System.out.println("************* a" + i + " *************");
        }
        executorService.shutdown();
    }
}

     class TestRunnable implements Runnable {
        public void run() {
            System.out.println(Thread.currentThread().getName() + "线程被调用了。");
        }

}

执行结果:

************* a1 *************
************* a2 *************
pool-1-thread-2线程被调用了。
************* a3 *************
pool-1-thread-1线程被调用了。
pool-1-thread-2线程被调用了。
************* a4 *************
pool-1-thread-3线程被调用了。

复制代码

4.2Executor执行Callable

public class Executor执行Callable {
    /**
     *  从结果中可以同样可以看出,submit也是首先选择空闲线程来执行任务,如果没有,才会创建新的线程来执行任务。
     *  另外,需要注意:如果Future的返回尚未完成,则get()方法会阻塞等待,直到Future完成返回,可以通过
     *  调用isDone()方法判断Future是否完成了返回。
     */
    public static void main(String[] args){
        ExecutorService executorService = Executors.newCachedThreadPool();
        List<Future<String>> resultList = new ArrayList<Future<String>>();

        //创建10个任务并执行
        for (int i = 0; i < 10; i++){
            //使用ExecutorService执行Callable类型的任务,并将结果保存在future变量中
            Future<String> future = executorService.submit(new TaskWithResult(i));
            //将任务执行结果存储到List中
            resultList.add(future);
        }

        //遍历任务的结果
        for (Future<String> fs : resultList){
            try{
                while(!fs.isDone());//Future返回如果没有完成,则一直循环等待,直到Future返回完成
                System.out.println(fs.get());     //打印各个线程(任务)执行的结果
            }catch(InterruptedException e){
                e.printStackTrace();
            }catch(ExecutionException e){
                e.printStackTrace();
            }finally{
                //启动一次顺序关闭,执行以前提交的任务,但不接受新任务
                executorService.shutdown();
            }
        }
    }
}

class TaskWithResult implements Callable<String> {
    private int id;

    public TaskWithResult(int id){
        this.id = id;
    }

    /**
     * 任务的具体过程,一旦任务传给ExecutorService的submit方法,
     * 则该方法自动在一个线程上执行
     */
    public String call() throws Exception {
        System.out.println("call()方法被自动调用!!!    " + Thread.currentThread().getName());
        //该返回结果将被Future的get方法得到
        return "call()方法被自动调用,任务返回的结果是:" + id + "    " + Thread.currentThread().getName();
    }
}

执行结果:

call()方法被自动调用!!!    pool-1-thread-1
call()方法被自动调用,任务返回的结果是:0    pool-1-thread-1
call()方法被自动调用!!!    pool-1-thread-2
call()方法被自动调用,任务返回的结果是:1    pool-1-thread-2
call()方法被自动调用!!!    pool-1-thread-3
call()方法被自动调用,任务返回的结果是:2    pool-1-thread-3
call()方法被自动调用!!!    pool-1-thread-5
call()方法被自动调用!!!    pool-1-thread-6
call()方法被自动调用!!!    pool-1-thread-7
call()方法被自动调用!!!    pool-1-thread-9
call()方法被自动调用!!!    pool-1-thread-4
call()方法被自动调用,任务返回的结果是:3    pool-1-thread-4
call()方法被自动调用,任务返回的结果是:4    pool-1-thread-5
call()方法被自动调用,任务返回的结果是:5    pool-1-thread-6
call()方法被自动调用,任务返回的结果是:6    pool-1-thread-7
call()方法被自动调用!!!    pool-1-thread-8
call()方法被自动调用,任务返回的结果是:7    pool-1-thread-8
call()方法被自动调用,任务返回的结果是:8    pool-1-thread-9
call()方法被自动调用!!!    pool-1-thread-10
call()方法被自动调用,任务返回的结果是:9    pool-1-thread-10

Views: 60