Java—多线程创建详解

关注:CodingTechWork,一起学习进步。

多线程介绍

线程和进程

进程

  • 定义:进程是一块包含了某些资源的内存区域,操作系统利用进程把它的工作划分为一些功能单元。(应用程序是由一个或多个相互协作的进程组成)
  • 从资源看:进程是资源分配的最小单位;
  • 从基本单位看:进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位;
  • 从操作系统资源管理方式看:进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其他进程产生影响,所以多进程的程序要比多线程的程序健壮;在进程切换时,耗费资源较大,效率要差一些;
  • 独立性:进程是系统中独立存在的实体,拥有独立的资源,每个进程私有地址空间,一个进程不可直接访问另一个进程的地址空间。隔离性较强。
  • 动态性:进程和程序的区别在于,程序只是一个静态的指令集合,进程是一个正在系统中活动的指令集合。进程拥有自己的生命周期和不同的状态。
  • 并发性:单个处理器上可以并发执行多个进程,互不干扰。对于一个CPU而言,在某个时间点上只能执行一个程序,即只能运行一个进程,CPU不断地交织执行这些进程,由于CPU的执行速度相对比较快,多个进程轮换执行给用户感觉是多个进程在同时执行。

线程

  • 定义:进程中所包含的一个或多个执行单元就称为线程(thread)。
  • 从调度看:线程是CPU调度的最小单位;
  • 从基本单位看:线程是进程的一个实体,只能归属于一个进程且只能访问该进程所拥有的资源。线程是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位;
  • 从资源看:线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源且访问该进程所包含的地址空间;
  • 从操作系统资源管理方式看:线程只是一个进程的不同执行路径,线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮;对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程;
  • 性能:使用大量的线程会引起过多的上下文切换,影响性能;
  • 轻量级进程:线程是进程的执行单元,在一个程序(进程)中,线程是独立的、并发的执行流。进程初始化后,主线程就会被创建(绝大部分引应用程序只需要一个主线程),当有场景需求多线程时,可以在该进程中创建多条顺序执行流,每个线程也是相互独立的。
  • 线程资源:线程是进程的组成部分,线程拥有自己的堆栈、程序计数器和局部变量,但不拥有系统资源,与父进程的其他线程共享了该进程的所有系统资源
  • 独立运行:线程是抢占式执行,是独立运行的,当前运行的线程在任何时刻都可能被挂起,便于另一个线程运行。

进程和线程的关系

  1. 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。
  2. 资源分配给进程,同一进程的所有线程共享该进程的所有资源。
  3. 处理机分给线程,即真正在处理机上运行的是线程。
  4. 线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。

多线程的优势

  1. 性能高:进程之间不能够共享内存,同一进程下的多线程之间可以共享内存,极大提高程序的运行效率。多线程共享同一个进程的虚拟空间,线程共享环境主要包括进程代码段和进程的公有数据,便于实现线程间的通信。
  2. 开销小:系统创建进程是需要为该进程重新分配系统资源,创建进程的开销比较大。相对而言,创建线程的代价就小很多,使用的多线程并发多任务比多进程效率高。
  3. 编程简易:Java内置多线程功能,编程简易。

多线程实现方式

多线程实现的三种方式

1)继承Thread类创建线程类;
2)实现Runnable接口创建线程类;
3)使用CallableFuture创建线程。

继承Thread类创建线程类

继承Thread类步骤

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

常用方法

  • Thread currentThread():Thread类的静态方法,返回当前正在执行的线程对象。
  • void setName():Thread类的实例方法,重命名线程名称。
  • String getName():Thread类的实例方法,返回调用该方法的线程名称。
  • void start():启动线程。
  • run():start()方法调用后,执行Thread的run()体。

示例

代码


public class ThreadTest extends Thread {
    private int i;

    @Override
    public void run() {
        for(; i < 2; i++) {
            System.out.println("继承Thread启动线程:" + getName() + " : " + i);
        }
        setName("Thread-new");
        for(; i < 4; i++) {
            System.out.println("重命名后的新线程名:" + Thread.currentThread().getName() + " : " + i);
        }
    }


    public static void main(String[] args) {
        System.out.println("main线程:" +Thread.currentThread().getName());
        new ThreadTest().start();
    }
}

运行结果

main线程:main
继承Thread启动线程:Thread-0 : 0
继承Thread启动线程:Thread-0 : 1
重命名后的新线程名:Thread-new : 2
重命名后的新线程名:Thread-new : 3

实现Runnable接口创建线程类

实现Runnable接口步骤

  1. 定义Runnable接口的实现类,并重写该接口的run()方法,该run()是线程执行体;
  2. 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象;
  3. 调用线程对象的start()方法来启动该线程。

注意
实现Runable接口和继承Thread的方式区别:继承Thread创建的线程是创建的Thread子类即可代表线程对象;而实现Runable接口的创建的Runnable对象只能作为线程对象的target。

示例

代码

public class RunnableTest implements Runnable {

    private int i;

    @Override
    public void run() {

        //不能直接调用getName()和setName()方法,Runnable只有run方法
        for(; i < 5; i++) {
            System.out.println("实现Runnable接口创建线程:" + Thread.currentThread().getName() + " : " + i);
        }
    }

    public static void main(String[] args) {
        System.out.println("main线程:" +Thread.currentThread().getName());
        RunnableTest runnableTest = new RunnableTest();
        new Thread(runnableTest).start();

        //指定线程名称
        RunnableTest runnableTestWithNewName = new RunnableTest();
        new Thread(runnableTestWithNewName, "Runnable-Thread-new").start();
    }
}

常用方法

  • public abstract void run();:Runnable接口中只包含一个抽象方法,Runnable接口是函数式接口,可使用Lambda表达式创建Runnable对象。

运行结果

main线程:main
实现Runnable接口创建线程:Thread-0 : 0
实现Runnable接口创建线程:Thread-0 : 1
实现Runnable接口创建线程:Thread-0 : 2
实现Runnable接口创建线程:Runnable-Thread-new : 0
实现Runnable接口创建线程:Thread-0 : 3
实现Runnable接口创建线程:Runnable-Thread-new : 1
实现Runnable接口创建线程:Thread-0 : 4
实现Runnable接口创建线程:Runnable-Thread-new : 2
实现Runnable接口创建线程:Runnable-Thread-new : 3
实现Runnable接口创建线程:Runnable-Thread-new : 4

使用Callable和Future创建线程

使用Callable和Future步骤

  1. 创建Callable接口的实现类,并实现call()方法,该call()方法即为线程执行体,且该call()方法有返回值,再创建Callable实现类的实例;
  2. 使用FutureTask类包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值;
  3. 使用FutureTask对象作为Thread对象的target创建并通过线程对象的start()方法启动线程;
  4. 调用FutureTask对象的get()方法获得子线程执行结束后的返回值。

示例

代码

public class CallableFutureTest implements Callable<Integer>{

    private int i;

    @Override
    public Integer call(){
        for (i = 0; i < 2; i++) {
            System.out.println("实现Callable接口创建线程: " + Thread.currentThread().getName() + " : " + i);
        }
        return i;
    }

    public static void main(String[] args) {
        System.out.println("main线程:" +Thread.currentThread().getName());

        long begin = System.currentTimeMillis();
        ExecutorService executorService = Executors.newCachedThreadPool();
        CallableFutureTest callableFutureTest1 = new CallableFutureTest();
        CallableFutureTest callableFutureTest2 = new CallableFutureTest();
        FutureTask<Integer> futureTask1 = new FutureTask<>(callableFutureTest1);
        FutureTask<Integer> futureTask2 = new FutureTask<>(callableFutureTest2);
        executorService.submit(futureTask1);
        executorService.submit(futureTask2);
        try {
            System.out.println("futureTask1: "+ futureTask1.get() + "-futureTask2: " + futureTask2.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }finally {
            executorService.shutdown();
        }
        System.out.println("executor pool: " + executorService.isShutdown());
        System.out.println("time: " + (System.currentTimeMillis() - begin));
        
    }

}

运行结果

main线程:main
实现Callable接口创建线程: pool-1-thread-1 : 0
实现Callable接口创建线程: pool-1-thread-2 : 0
实现Callable接口创建线程: pool-1-thread-1 : 1
实现Callable接口创建线程: pool-1-thread-2 : 1
futureTask1: 2-futureTask2: 2
executor pool: true
time: 5

Q&A

并行和并发的区别

并行:parallelism,物理上同时执行;多个处理器同时处理多条指令;(单线程永远无法达到并行状态)
并发:concurrency,逻辑上多个任务交织执行;多个进程指令交替执行,同一时刻只有一条指令执行。(宏观上给人一种错觉是多个进程同时执行)

进程与线程的区别

  1. 调度:线程作为调度的基本单位;进程是资源分配的基本单位。
  2. 并发性:不仅进程之间可以并发执行;同一个进程的多个线程之间也可并发执行。
  3. 拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源;
  4. 系统开销:在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤消线程时的开销。但是进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响。
    线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个进程死掉就等于所有的线程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。
  5. 作用:多进程作用是提高CPU的使用率,而不是提高执行速度;
    多线程作用是提高应用程序的使用率,而不是提高执行速率。

结论:

  1. 线程是进程的一部分。
  2. CPU调度的是线程。
  3. 系统为进程分配资源,不对线程分配资源。

三种实现线程方式的区别

  1. 返回值:继承Thread类,run()方法没有返回值;实现Runnable接口和Callable接口方式基本相同,但Callable接口定义的call()方法具有返回值,可以声明抛出异常。
  2. 继承:继承Thread类,不能再继承其他类;线程类实现Runnable和Callable接口可以继承其他类。
  3. 访问当前线程:继承Thread类,需要访问当前线程,无须使用Thread.currentThread()方法,直接使用this即可获得当前线程;线程类实现Runnable和Callable接口,若访问当前线程,必须使用Thread.currentThread()方法。多个线程共享同一个target对象,适合多个相同线程来处理同一份资源的情况,从而将CPU、代码和数据分开,形成清晰的模型,较好地体现面向对象思想。

综合:推荐使用线程类实现Runnable和Callable接口方式创建多线程。

参考书籍
《疯狂Java》
《并发编程实战》

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