JAVA多线程demo

基于上一篇文章介绍了一些关于JAVA多线程基础方面的理论知识,这一篇开始实际动手操作一番看看具体效果。

1、通过集成java.lang.Thread线程类来创建一个线程

package com.feizi.java.concurrency.thread;

/**
 * Created by feizi on 2018/5/17.
 */
public class TestThread {
    public static void main(String[] args) {
        /**
         * 控制台输出结果:
         * 主线程ID是: 1
         * 名称线程2的线程ID是:1
         * 名称线程1的线程ID是:11
         * 结论:
         * 1、主线程和线程2的线程ID相同,说明直接调用run()方法不会创建新的线程,而是在主线程中直接调用run()方法,普通的方法调用
         * 2、线程1先调用start()方法,而后线程2调用run()方法,最终却线程2先于线程1输出,说明新建的线程并不会影响主线程的执行顺序
         */

        System.out.println("主线程ID是: " + Thread.currentThread().getId());

        Thread t1 = new MyThread("线程1");
        t1.start();

        Thread t2 = new MyThread("线程2");
        /*直接调用run()方法*/
        t2.run();
    }
}

/**
 * 自定义线程
 */
class MyThread extends Thread{
    /*线程名称*/
    private String name;

    public MyThread(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        System.out.println("名称" + name + "的线程ID是:" + Thread.currentThread().getId());
    }
}

控制台输出结果:

主线程ID是: 1
名称线程2的线程ID是:1
名称线程1的线程ID是:11

Process finished with exit code 0

由上面输出结果我们总结一下结论:

  1. 主线程和线程2的线程ID相同,说明直接调用run()方法不会创建新的线程,而是在主线程中直接调用run()方法,普通的方法调用
  2. 线程1先调用start()方法,而后线程2调用run()方法,最终却线程2先于线程1输出,说明新建的线程并不会影响主线程的执行顺序

2、通过实现java.lang.Runnable接口来创建一个线程

package com.feizi.java.concurrency.thread;

/**
 * Created by feizi on 2018/5/17.
 */
public class TestRunnable {
    public static void main(String[] args) {
        System.out.println("主线程的ID是: " + Thread.currentThread().getId());
        MyRunnable r1 = new MyRunnable("线程1");
        Thread t1 = new Thread(r1);
        t1.start();

        MyRunnable r2 = new MyRunnable("线程2");
        Thread t2 = new Thread(r2);
        /*直接调用run()方法,并不会创建新线程*/
        t2.run();
    }
}

class MyRunnable implements Runnable{

    private String name;

    public MyRunnable(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        System.out.println("名字" + name + "的线程ID是: " + Thread.currentThread().getId());
    }
}

控制台输出结果:

主线程的ID是: 1
名字线程2的线程ID是: 1
名字线程1的线程ID是: 11

Process finished with exit code 0

由以上输出结果我们可以看出:

其实不管是通过继承Thread类,还是实现Runnable接口的方式,都可以创建一个线程,其结果都是一样的。区别在于:

  1. 实现Runnable的方式需要将实现Runnable接口的类作为参数传递给Thread,然后通过Thread类调用Start()方法来创建线程
  2. 直接继承Thread类的话代码更简洁,也更容易理解,但是由于JAVA被设计为只支持单继承,所以如果要继承其他类的同时需要实现线程那就只能实现Runnable接口了,这里更推荐实现Runnable接口,实现Runnable的方式更为灵活一些。

3、sleep()线程休眠

package com.feizi.java.concurrency.thread;

/**
 * Created by feizi on 2018/5/17.
 */
public class TestSleep {
    private int i = 10;
    private Object object = new Object();

    public static void main(String[] args) {
        /**
         * 线程: Thread-0开始执行,i的值为:11
         * 线程: Thread-0准备进入休眠状态...
         * 线程: Thread-0休眠结束...
         * 线程: Thread-0继续执行,i的值为===========>:12
         * 线程: Thread-1开始执行,i的值为:13
         * 线程: Thread-1准备进入休眠状态...
         * 线程: Thread-1休眠结束...
         * 线程: Thread-1继续执行,i的值为===========>:14
         * 结论:
         * 1、当Thread-0进入休眠状态,Thread-1并没有马上执行,而是等待Thread-0休眠结束释放了对象锁才继续执行
         * 2、当调用sleep()方法时,必须捕获异常或者向上抛出异常,当线程休眠结束并不会马上执行,而是进入就绪状态,等待CPU的再次调度,调用Sleep()方法相当于是进入了阻塞状态
         */

        TestSleep testSleep = new TestSleep();
        Thread t1 = testSleep.new MyTestThread();
        t1.start();

        Thread t2 = testSleep.new MyTestThread();
        t2.start();
    }

    class MyTestThread extends Thread{
        @Override
        public void run() {
            synchronized (object){
                i++;
                System.out.println("线程: " + Thread.currentThread().getName() + "开始执行,i的值为:" + i);
                System.out.println("线程: " + Thread.currentThread().getName() + "准备进入休眠状态...");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println("线程: " + Thread.currentThread().getName() + "休眠结束...");
                i++;
                System.out.println("线程: " + Thread.currentThread().getName() + "继续执行,i的值为===========>:" + i);
            }
        }
    }
}

控制台输出结果:

线程: Thread-0开始执行,i的值为:11
线程: Thread-0准备进入休眠状态...
线程: Thread-0休眠结束...
线程: Thread-0继续执行,i的值为===========>:12
线程: Thread-1开始执行,i的值为:13
线程: Thread-1准备进入休眠状态...
线程: Thread-1休眠结束...
线程: Thread-1继续执行,i的值为===========>:14

Process finished with exit code 0

由上面输出结果我们总结一下结论:

1、当Thread-0进入休眠状态,Thread-1并没有马上执行,而是等待Thread-0休眠结束释放了对象锁才继续执行
2、当调用sleep()方法时,必须捕获异常或者向上抛出异常,当线程休眠结束并不会马上执行,而是进入就绪状态,等待CPU的再次调度,调用Sleep()方法相当于是进入了阻塞状态

4、join()加入线程

package com.feizi.java.concurrency.thread;

/**
 * Created by feizi on 2018/5/17.
 */
public class TestJoin {
    public static void main(String[] args) throws InterruptedException {
        /**
         * 执行结果:
         * main主线程: main ====> 0
         * main主线程: main ====> 1
         * 当前线程: 线程1 ===> 0
         * main主线程: main ====> 2
         * 当前线程: 线程1 ===> 1
         * 当前线程: 线程1 ===> 2
         * 当前线程: 线程1 ===> 3
         * 当前线程: 线程1 ===> 4
         * main主线程: main ====> 3
         * main主线程: main ====> 4
         * 当前线程: 线程2 ===> 0
         * 当前线程: 线程2 ===> 1
         * 当前线程: 线程2 ===> 2
         * 当前线程: 线程2 ===> 3
         * 当前线程: 线程2 ===> 4
         * main主线程: main ====> 5
         * main主线程: main ====> 6
         * main主线程: main ====> 7
         * main主线程: main ====> 8
         * main主线程: main ====> 9
         * 结论:
         * 1、使用了join()方法之后,主线程会等待子线程结束之后才会结束
         */
        Thread t1 = new MyJoinThread("线程1");
        t1.start();
//        t1.join();

        for (int i = 0; i < 10; i++){
            if(i == 5){
                Thread t2 = new MyJoinThread("线程2");
                t2.start();
                t2.join();
            }
            System.out.println("main主线程: " + Thread.currentThread().getName() + " ====> " + i);
        }
    }
}

class MyJoinThread extends Thread{
    public MyJoinThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; i++){
            System.out.println("当前线程: " + Thread.currentThread().getName() + " ===> " + i);
        }
    }
}

控制台输出结果:

main主线程: main ====> 0
当前线程: 线程1 ===> 0
main主线程: main ====> 1
当前线程: 线程1 ===> 1
main主线程: main ====> 2
当前线程: 线程1 ===> 2
main主线程: main ====> 3
当前线程: 线程1 ===> 3
main主线程: main ====> 4
当前线程: 线程1 ===> 4
当前线程: 线程2 ===> 0
当前线程: 线程2 ===> 1
当前线程: 线程2 ===> 2
当前线程: 线程2 ===> 3
当前线程: 线程2 ===> 4
main主线程: main ====> 5
main主线程: main ====> 6
main主线程: main ====> 7
main主线程: main ====> 8
main主线程: main ====> 9

Process finished with exit code 0

由上面输出结果我们总结一下结论:

使用了join()方法之后,主线程会等待子线程结束之后才会结束

5、yeild()让出CPU执行权限

package com.feizi.java.concurrency.thread;

/**
 * Created by feizi on 2018/5/17.
 */
public class TestYield {
    public static void main(String[] args) {
        /**
         * 执行结果:
         * 名字: 线程1执行
         * 名字: 线程2执行
         * 线程2结果num: 1065788928计算耗时: 8毫秒
         * 线程1结果count: 1784293664计算耗时: 138毫秒
         * 结论:
         * 1、调用yield()方法是为了让当前线程让出CPU执行权限,从而可以让CPU去执行其他线程,它和sleep()方法类似同样是不会释放对象锁,
         * 但是yield()不会控制具体的交出CPU权限的时间,同时也只能让具有相同优先级的线程获得CPU执行时间的机会
         * 2、调用yield()方法并不会让当前线程进入阻塞状态,而只是进入就绪状态,只需要等待重新获取CPU的时间片,而Sleep()则会进入阻塞状态
         */
        MyYieldThread t = new MyYieldThread("线程1");
        t.start();

        MyYieldThread2 t2 = new MyYieldThread2("线程2");
        t2.start();
    }
}

class MyYieldThread extends Thread{
    private String name;

    public MyYieldThread(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        System.out.println("名字: " + name + "执行");
        long start = System.currentTimeMillis();
        int count = 0;
        for (int i = 0; i < 1000000; i++){
            count = count + (i + 1);
            Thread.yield();
        }

        long end = System.currentTimeMillis();
        System.out.println(name + "结果count: " + count + "计算耗时: " + (end - start) + "毫秒");
    }
}

class MyYieldThread2 extends Thread{
    private String name;

    public MyYieldThread2(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        System.out.println("名字: " + name + "执行");
        long start = System.currentTimeMillis();
        int num = 0;
        for (int i = 0; i < 10000000; i++){
            num += i * 8;
        }
        long end = System.currentTimeMillis();
        System.out.println(name + "结果num: " + num + "计算耗时: " + (end - start) + "毫秒");
    }
}

控制台输出结果:

名字: 线程1执行
名字: 线程2执行
线程2结果num: 1065788928计算耗时: 6毫秒
线程1结果count: 1784293664计算耗时: 133毫秒

Process finished with exit code 0

由上面输出结果我们总结一下结论:

1、调用yield()方法是为了让当前线程让出CPU执行权限,从而可以让CPU去执行其他线程,它和sleep()方法类似同样是不会释放对象锁,但是yield()不会控制具体的交出CPU权限的时间,同时也只能让具有相同优先级的线程获得CPU执行时间的机会
2、调用yield()方法并不会让当前线程进入阻塞状态,而只是进入就绪状态,只需要等待重新获取CPU的时间片,而Sleep()则会进入阻塞状态

6、带返回值的线程接口Callable

package com.feizi.java.concurrency.thread;

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;

/**
 * Created by feizi on 2018/5/17.
 */
public class TestCallable {
    public static void main(String[] args) {
        //创建一个线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(3);

        //创建三个有返回值的任务
        MyCallable c1 = new MyCallable("线程1");
        MyCallable c2 = new MyCallable("线程2");
        MyCallable c3 = new MyCallable("线程3");

        Future f1 = threadPool.submit(c1);
        Future f2 = threadPool.submit(c2);
        Future f3 = threadPool.submit(c3);

        try {
            System.out.println(f1.get().toString());
            System.out.println(f2.get().toString());
            System.out.println(f3.get().toString());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }finally {
            threadPool.shutdown();
        }
    }
}

class MyCallable implements Callable{
    private String name;

    public MyCallable(String name) {
        this.name = name;
    }

    @Override
    public Object call() throws Exception {
        return name + "返回了东西...";
    }
}

控制台输出结果:

线程1返回了东西...
线程2返回了东西...
线程3返回了东西...

Process finished with exit code 0

由上面输出结果我们总结一下结论:

  1. Callable接口类似于Runnable,从名字就可以看出来了,但是Runnable不会返回结果,并且无法抛出返回结果的异常,而Callable功能更强大一些,被线程执行后,可以返回值,这个返回值可以被Future拿到,也就是说,Future可以拿到异步执行任务的返回值。
  2. 通常在使用Callable的时候,也会涉及到Future,一般配合一起使用,一个产生结果,一个拿到结果。
  3. 假设有一个很耗时的返回值需要计算,并且这个返回值不是立刻需要的话,那么就可以使用这个组合,用另一个线程去计算返回值,而当前线程在使用这个返回值之前可以做其它的操作,等到需要这个返回值时,再通过Future得到最终计算结果。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容

  • 进程和线程 进程 所有运行中的任务通常对应一个进程,当一个程序进入内存运行时,即变成一个进程.进程是处于运行过程中...
    小徐andorid阅读 2,781评论 3 53
  • 本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。 首先讲...
    李欣阳阅读 2,377评论 1 15
  • 单任务 单任务的特点是排队执行,也就是同步,就像再cmd输入一条命令后,必须等待这条命令执行完才可以执行下一条命令...
    Steven1997阅读 1,126评论 0 6
  • 本文参加#乡忆·乡思·乡情·乡恋#活动,本人承诺,文章内容为原创,且未在其他平台发表过。 搭上最晚的航班 我离开了...
    冯海宇阅读 270评论 0 0
  • (前言) 我就是拼了命的要把我失去的尊严给夺回来! 我要的是这个世界在我面前低头! 随着...
    哀囍阅读 309评论 0 0