java多线程(1)-概述

线程

线程是程序执行流的最小单位,在传统的开发中一个程序仅仅拥有一个线程,通俗的讲,单线程程序就是一根肠子通屁眼,按照顺序执行,后面的必须要等前面的执行完之后再执行,比如一个全栈工程师,他要完成美术,ui,前端,后端等工作,不管这个工程师多厉害同一时刻他只能干一件事,也就是说这些事是同步执行的,效率相对较慢

多线程

进程是线程的容器,如果一个进程中有多个线程那么这个进程的效率就会快上许多,比如我们有四个工程师,美术,ui,前端,后端,他们可以同时进行工作,互相之间产生的影响较小,异步工作肯定是比同步工作快的。可能有点读者要说,从cpu的角度来看,异步工作也有切换线程带来的消耗,速度不一定比同步快吧?在以前单核cpu的时候确实单线程比较快,现在多核cpu,每一个核可以单独处理一个线程,自然异步就比同步快上许多

多线程也并不是没有坏处,我们可以想象一下,如果有四个工程师,但是只有二台电脑可用,那么这四个工程师就会抢着用这两台电脑,这里电脑就是多线程中的临界资源,需要对临界资源进行加锁处理,锁相关的问题,我们后面再来讨论

并不是线程数越多越好,看服务器cpu的核数,一般几核的cpu开几个线程性能可以最大化

创建线程的三种方式

  1. 继承Thread
package com.rockjh.jdk.thread;


/**
 * @author rockjh 【rockjh@aliyun.com】
 * @Description: 继承Thread的方式新建一个线程
 * @Date 2017/11/17 13:51
 **/
public class ExtendsThreadWay extends Thread{

    int i=3;

    public ExtendsThreadWay(String name){
        super(name);
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+":create new thread by extends Thread class");
        System.out.println(i--);
    }

    public static void main(String[] args) throws InterruptedException {
        ExtendsThreadWay thread1=new ExtendsThreadWay("thread1");
        ExtendsThreadWay thread2=new ExtendsThreadWay("thread2");
        ExtendsThreadWay thread3=new ExtendsThreadWay("thread3");
        thread1.start();
        thread2.start();
        thread3.start();
        Thread.sleep(1000);
    }
}

  1. 实现Runnable接口
package com.rockjh.jdk.thread;

/**
 * @author rockjh 【rockjh@aliyun.com】
 * @Description: 实现runnable的方式新建一个线程
 * @Date 2017/11/17 14:03
 **/
public class ImplRunnableWay implements Runnable{

    int i=3;

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+":create new thread by implement Runnable interface");
        System.out.println(i--);
    }

    public static void main(String[] args) throws InterruptedException {
        ImplRunnableWay implRunnableWay=new ImplRunnableWay();
        Thread thread1=new Thread(implRunnableWay,"thread1");
        Thread thread2=new Thread(implRunnableWay,"thread2");
        Thread thread3=new Thread(implRunnableWay,"thread3");
        thread1.start();
        thread2.start();
        thread3.start();
        Thread.sleep(1000);
    }
}
  1. 实现Callable接口
package com.rockjh.jdk.thread;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * @author rockjh 【rockjh@aliyun.com】
 * @Description: 实现Callable的方式新建一个线程
 * @Date 2017/11/17 14:15
 **/
public class ImplCallableWay<T> implements Callable<T>{

    int i=3;

    @Override
    public T call() throws Exception {
        System.out.println(Thread.currentThread().getName()+":create new thread by implement Callable interface");
        System.out.println(i--);
        return null;
    }

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ImplCallableWay<Integer> implRunnableWay=new ImplCallableWay<>();
        FutureTask<Integer> futureTask1=new FutureTask<>(implRunnableWay);
        FutureTask<Integer> futureTask2=new FutureTask<>(implRunnableWay);
        new Thread(futureTask1,"thread1").start();
        new Thread(futureTask2,"thread2").start();
        System.out.println(futureTask1.get());
        System.out.println(futureTask2.get());
    }

}

创建线程方式的异同

或许读者之前看到过某些关于继承Thread和实现Runnable这两种方式的区别,大部分说的都是通过继承Thread不能共享资源,实现Runnable的可以共享资源。其实这种说法不全面,通过实现Runnable创建的线程,为啥他可以共享资源呢,因为他在创建多个线程时使用的同一个target,也就是说多个线程运行的时同一个实例的run方法,自然资源就可以共享,Callable同理,然而继承Thread是否也可以通过这种方式达到共享资源呢,当然,代码如下:

package com.rockjh.jdk.thread;


/**
 * @author rockjh 【rockjh@aliyun.com】
 * @Description: 继承Thread的方式新建线程共享资源
 * @Date 2017/11/17 13:51
 **/
public class ExtendsThreadShareSource extends Thread{

    int i=3;

    public ExtendsThreadShareSource(String name){
        super(name);
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+":create new thread by extends Thread class");
        System.out.println(i--);
    }

    public static void main(String[] args) throws InterruptedException {
        ExtendsThreadShareSource thread=new ExtendsThreadShareSource("thread1");
        Thread thread1=new Thread(thread,"thread1");
        Thread thread2=new Thread(thread,"thread2");
        Thread thread3=new Thread(thread,"thread3");
        thread1.start();
        thread2.start();
        thread3.start();
        Thread.sleep(1000);
    }
}

Callable和Runnable类似,只是Callable有返回值,Runnable没有返回值

继承Thread和实现Runnable源码分析

我们可以看到这三种方式都使用了new Thread的方式来新建线程,那么Thread中到底是怎样的呢,一起来看看吧

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
 }

Thread的构造方法很多,不管他采用哪一个构造方法,都会经过这一步初始化,简单看看他的参数都是啥

  • ThreadGroup 线程组,把一些线程放在一组,可以设置一些共同的属性,可以一同销毁,方便管理线程用的,新建线程不传入该参数,Thread会通过currentThread().getThreadGroup()来初始化
  • Runnable 线程的主要执行体,他的run方法将被线程调用
  • name 线程名,如果不设置,Thread会通过"Thread-" + nextThreadNum()设置默认的线程名
  • stackSize 新线程所需堆栈大小,如果为0则会忽略
  • AccessControlContext 安全控制代码,如果不传入该参数会通过AccessController.getContext()来设置
  • inheritThreadLocals如果为true这继承创建该线程的ThreadLocal值

Thread初始化完成后,那么进入start方法,启动线程

public synchronized void start() {
       
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

从代码中我们可以看出start()方法中做了3件事:

  • 判断线程是否新创建的线程
  • 将线程加入到线程组中统一管理
  • 调用本地方法start0()来调用操作系统的API来创建线程之后,新的线程处于就绪状态,等待调度器调试执行run()方法

继承Thread和实现Runnable的方式创建的线程大致就使用了上述分析的部分代码,当时Thread里面还有很多的方法,内部类等等,后面逐一细说

Callable创建线程方式源码分析

通过Callable创建线程经过了下列几个步骤

  1. 新建一个类,实现Callable接口,覆写call方法
  2. 使用FutureTask包含Callable子类,FutureTask初始化如下
public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }

我们可以看到FutureTask实现RunnableFuture,RunnableFuture继承Runnable和Future

  1. 使用Thread新建一个线程,这里新建的一个线程里面的target中的run方法实际上运行的是FutureTask中覆写RunnableFuture的run方法
public void run() {
    if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                    null, Thread.currentThread()))
        return;
    try {
        Callable<V> c = callable;
        if (c != null && state == NEW) {
            V result;
            boolean ran;
            try {
                result = c.call();
                ran = true;
            } catch (Throwable ex) {
                result = null;
                ran = false;
                setException(ex);
            }
            if (ran)
                set(result);
        }
    } finally {
        // runner must be non-null until state is settled to
        // prevent concurrent calls to run()
        runner = null;
        // state must be re-read after nulling runner to prevent
        // leaked interrupts
        int s = state;
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
}

我们可以看到这里面的run方法主要是设置运行状态为new、执行Callable.call()方法并将执行结果值赋给本地变量outcome,后面的一些地方会用到这个运行状态,比如FutureTask.get()方法阻塞就根据了这个状态值。那么这个到底是怎样实现调用FutureTask.get()方法阻塞调用线程,直到返回值的呢,get()方法如下

   public V get() throws InterruptedException, ExecutionException {
        int s = state;
        if (s <= COMPLETING)
            s = awaitDone(false, 0L);
        return report(s);
    }

这里用到了运行状态,如果线程没有完成那么他会执行等待线程执行完成的方法awaitDone(false, 0L),这里的report(s)就是返回结果值。awaitDone方法的源码如下

private int awaitDone(boolean timed, long nanos)
        throws InterruptedException {
    final long deadline = timed ? System.nanoTime() + nanos : 0L;
    FutureTask.WaitNode q = null;
    boolean queued = false;
    for (;;) {
        if (Thread.interrupted()) {
            removeWaiter(q);
            throw new InterruptedException();
        }

        int s = state;
        if (s > COMPLETING) {
            if (q != null)
                q.thread = null;
            return s;
        }
        else if (s == COMPLETING) // cannot time out yet
            Thread.yield();
        else if (q == null)
            q = new FutureTask.WaitNode();
        else if (!queued)
            queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
                    q.next = waiters, q);
        else if (timed) {
            nanos = deadline - System.nanoTime();
            if (nanos <= 0L) {
                removeWaiter(q);
                return state;
            }
            LockSupport.parkNanos(this, nanos);
        }
        else
            LockSupport.park(this);
    }
}

这里有一个无线循环,前面的都是设置一些值,如果在设置值的时候,线程的state成为了正常,循环结束,返回结果值。new FutureTask.WaitNode()这个是一个内部类,就是设置正在等待的线程,最后执行这一句LockSupport.park(this);锁定这个线程,该线程禁止调度,除非当前线程禁止被解除。我们回过头看看run方法,里面有一个set返回值的方法

protected void set(V v) {
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        outcome = v;
        UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
        finishCompletion();
    }
}

通过Unsafe类对线程的状态进行改变,为线程执行结果返回值,通过finishCompletion()方法来处理后续收尾操作,finishCompletion代码如下

private void finishCompletion() {
    // assert state > COMPLETING;
    for (FutureTask.WaitNode q; (q = waiters) != null;) {
        if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
            for (;;) {
                Thread t = q.thread;
                if (t != null) {
                    q.thread = null;
                    LockSupport.unpark(t);
                }
                FutureTask.WaitNode next = q.next;
                if (next == null)
                    break;
                q.next = null; // unlink to help gc
                q = next;
            }
            break;
        }
    }

    done();

    callable = null;        // to reduce footprint
}

Thread t = q.thread这里取出之前存在静态内部类WaitNode中的等待线程,LockSupport.unpark(t)取消禁止线程调度的限制,前面的get方法中的awaitDone方法中的循环结束,返回值Callable执行的返回值。

总结

java多线程的实现主要是靠Runnable中的run方法,不管你怎么实现始终绕不过,包括Callable的方式都是在Runnable上面扩展的。

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

推荐阅读更多精彩内容

  • 线程概述 线程与进程 进程  每个运行中的任务(通常是程序)就是一个进程。当一个程序进入内存运行时,即变成了一个进...
    闽越布衣阅读 990评论 1 7
  • 下面是我自己收集整理的Java线程相关的面试题,可以用它来好好准备面试。 参考文档:-《Java核心技术 卷一》-...
    阿呆变Geek阅读 14,729评论 14 507
  • 先看几个概念:线程:进程中负责程序执行的执行单元。一个进程中至少有一个线程。多线程:解决多任务同时执行的需求,合理...
    yeying12321阅读 535评论 0 0
  • 在本教程中,您将了解一个叫作数据库视图的新数据库对象。我们将讨论使用数据库视图的优点和缺点。 数据库视图是一个虚拟...
    易百教程阅读 1,400评论 0 4
  • 1.模板方法模式 有个abstarct 类定义了整个函数的流程,继承,实现。 2.中介者模式为了不让 两个相关联的...
    IAmWhoAmI阅读 151评论 0 0