Java之多线程

Thread(耦合,不推荐)

Runnable(解耦,推荐)

Executors

ExecutorService

Callable


1. Thread概述

Thread类在使用的方法在JDK手册中有介绍,它实现了Runnable接口中的唯一方法run(), run方法是线程执行的主体,因此在使用的时候需要重写run()方法去定义线程需要执行的功能,并且自定义Thread类的子类。

线程的执行开始点是调用了Thread类中的start()方法,start方法中实际上是去调用重写的run方法,这样一个线程就开始执行了

JDK手册中对两种创建和启动线程的例子


Thread构造方法:

    Thread()

    Thread(Runnable target)

    Thread(Runnable target,String name)

    Thread(String name)

    Thread(ThreadGroup group,Runnable target)

    Thread(ThreadGroup group,Runnable target,String name)

    Thread(ThreadGroup group,Runnable target,String name, long stackSize)

    Thread(ThreadGroup group,String name)


2. 继承Thread类创建运行线程

main线程的退出不影响自定义线程的运行

自定义线程类MyThread,使用默认线程名的构造方法:

public class MyThread extends Thread{

    long minPrime;

    public MyThread() {}

    public void run() {

        int cnt = 0;

        while(cnt != 5) {

            try {

                Thread.sleep(1000);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            out.printf(" ThreadName[%s] myThread cnt:%d\n", super.getName(), cnt);

            cnt++;

        }

        out.println("myThread exit@@@@@@@@");

    }

}


2.1. 自定义线程先退出


2.2. main线程先退出


3. 关于线程的命名

3.1. 获取线程名

String getName();

在线程中使用方法:super.getName(); 因为Mythread是继承了父类Thread,supper也可以不写,但是是调用的Thread中的方法

在2中的线程没有对线程命名,默认是Thread-N(0,1,2,……,N),主线程的名字是Thread-main


3.2. 获取当前线程的引用来获取线程名

Thread类中的静态方法:static Thread currentThread();

这个静态方法返回当前线程的Thread对象引用,在线程中拿到了Thread对象就能调用getName()方法了

例:在main线程中不能直接按照3.1的方式来获取线程名,使用静态方法currentThread()来获取线程名。


3.2. 自定义线程名

我们也可以对每个线程自定义名字

3.2.1. 通过构造方法设置线程名

原理是Thread的构造方法除了空参构造方法,还可以加入线程名的构造方法

Thread(String name)

Thread(String name)构造方法


3.2.2. 在启动线程之前设置线程名

void setName(String name)


4. 线程休眠sleep

static void sleep(long millis)    毫秒级休眠 1000 = 1s

static void sleep(long millis, int nanos)    纳秒级休眠 1/1000000000s = 1纳秒

静态方法直接调用,休眠1s
休眠1微秒




5. 实现Runnable接口创建启动线程(推荐使用)

该方式是使用Thread的构造函数:Thread(Runnable target)

参数是实现了Runnable接口抽象方法的一个子类

public class RunnableThread implements Runnable{

    public RunnableThread() {}

    public void run() {

        int cnt = 0;

        Thread thd = Thread.currentThread();

        while(cnt != 5) {

            try {

                Thread.sleep(1000);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            out.printf(" ThreadName[%s] myThread cnt:%d\n", thd.getName(), cnt);

            cnt++;

        }

        out.println("myThread exit@@@@@@@@");

    }

}



6. 继承Thread方式和实现Runnable接口 方式对比


7. 使用匿名内部类实现多线程

复习一下内部类

前提:

    要有继承或者接口实现


语法:

    new 父类或者接口(){

        重写抽象方法abstractMethd(){

        }

    }


7.1. 方式一:子类继承Thread,匿名子类

其实就是子类继承Thread,子类匿名了


7.2. 方式二:实现Runnable接口


7.3. 方式三:前2种方法的结合,匿名第二种方式的接口实现类


8. 线程的状态


9. 线程池

JDK5以前要使用线程池,开发人员需要用集合维护线程池:

常用ArrayList:

旧的线程池做法

从JDK5开始,提供了内置线程池

Executors类提供了创建线程池功能,是一个工具类都是静态方法


线程池使用场景:

假如你要做一个服务器端,一直在后台运行不会退出重启等操作,那么线程池是一个很好的选择,它避免了频繁创建销毁线程的系统开销,而且当线程池中的线程运行结束,线程池会回收该线程资源让其阻塞等待下一次被激活

如果是非一直需要运行的后台服务器,则没有必要使用线程池,因为如果没有强行终止,线程池中的线程是会一直存在的,即便main线程退出也会存在


9.1. 使用Executors工具类创建线程池

Executors工具类中的方法创建线程池:static ExecutorService newFixedThreadPool(int nThreads)

    参数nThreads:是要固定创建的线程数量

    返回值ExecutorService:本身是一个接口,返回的是该接口实现类的对象


9.1.1 Future submit(Runnable task)

submit方法就是用来提交线程执行任务的,参数是Runnable接口的实现类

定义一个类SubThread实现Runnable接口中的run方法:

public class SubThread implements Runnable {

    private String thdName = null;

    private int intParam = 0;

    public SubThread(String thdName, int param){

        this.thdName = thdName;

        this.intParam = param;

    }

    public void run() {

        Thread thd = Thread.currentThread();

        int cnt = 0;

        thd.setName(thdName);

        while(cnt != 5) {

            try {

                Thread.sleep(1000);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            out.printf("  ThreadName[%s] thdN[%s] base[%d] myThread cnt:%d\n", thdName, thd.getName(), intParam, cnt);

            cnt++;

        }

        out.println("myThread exit@@@@@@@@");

    }

}



9.1.2. 使用Callable创建线程池

Callable接口是一个跟Runnable功能类似的接口,但是功能比Runnable强大

call方法

Callable接口中也只有一个方法来描述线程任务,类似于Runnable中的run方法,但是call方法有一个泛型返回值,且可以抛出异常,这是run方法做不到的


定义一个SubThreadCallable类:

import java.util.concurrent.Callable;public class SubThreadCallable implements Callable {

    private String thdName = null;

    private int intParam = 0;

    public SubThreadCallable(String thdName, int param){

        this.thdName = thdName;

        this.intParam = param;

    }

    public Integer call() {

        Thread thd = Thread.currentThread();

        int cnt = 0;

        thd.setName(thdName);

        while(cnt != 5) {

            try {

                Thread.sleep(1000);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            out.printf("  ThreadName[%s] thdN[%s] base[%d] myThread cnt:%d\n", thdName, thd.getName(), intParam, cnt);

            cnt++;

        }

        out.println("subThread exit@@@@@@@@");

        return cnt;

    }

}


从运行结果可以看出2行submit代码瞬间运行打印出main1,之后在Integer i1 = ft.get(); 这一行阻塞,使用Callable的实现类作为submit方法的参数,会等待所有线程获取到了返回值才会向下继续执行,但是线程是异步执行的,结果确是同步返回。

所以这种方式在main线程的最后一个返回值是阻塞的,跟python的线程很类似,改成异步锁效率更高,当释放一个线程就又使释放的线程又运行起来。

再不要求返回值的情况下,应该尽量采用Runnable这种方式。


改进版:

让结果返回到最后去获取


9.1.3.  void shutdown()

启动有序关闭,其中先前提交的任务将被执行,但不会接受任何新任务。


9.2. 非线程池 和 线程池 性能测试

计算1+2+3+……+1000的总和

思路:

    要做线程池和非线程池的对比,就分两个程序测试,并计算得出结果的时间差

    由于要想线程返回结果,目前只能用Callable方式来做(下一节可以用锁来做同步)


9.2.1. 使用非线程池计算

多次运算,diffTime是1600ms左右  


9.2.2. 使用Callable线程池

将1加到1000,分成2部分相加同时运行

Callable
可见,运行速度提升了一倍



10. Daemon Thread守护线程

先看一段非守护线程的代码:

子线程是非守护线程

由于子线程是非守护线程,主线程退出了,子线程依然在运行。


将子线程改为守护线程:

子线程设置为守护线

将子线程设置为了Daemon true,当主线程退出,子线程也自动销毁了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,277评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,689评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,624评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,356评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,402评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,292评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,135评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,992评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,429评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,636评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,785评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,492评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,092评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,723评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,858评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,891评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,713评论 2 354

推荐阅读更多精彩内容

  • 进程和线程 进程 所有运行中的任务通常对应一个进程,当一个程序进入内存运行时,即变成一个进程.进程是处于运行过程中...
    胜浩_ae28阅读 5,106评论 0 23
  • 单任务 单任务的特点是排队执行,也就是同步,就像再cmd输入一条命令后,必须等待这条命令执行完才可以执行下一条命令...
    Steven1997阅读 1,178评论 0 6
  • 原创申明:本文参加“423简书故事节”,本人承诺文章内容为原创。 一、求婚 “喂,请问...
    于辛阅读 370评论 0 7
  • 天气在逐渐转暖了,每天的阳光,温暖的从窗户照射进来,这个时候的阳光温度刚好,不冷不热,很舒服,清明小长假开启了,这...
    清空妙有阅读 162评论 2 1
  • 这一幕让花金叶也很困惑,武娇现在的这种状况,她也是闻所未闻,看着武娇害怕的神情,花金叶也显得有些慌张。。。。正在这...
    MissGirls组合Anne阅读 211评论 0 5