java多线程(1)-概述

线程

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

多线程

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

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

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

创建线程的三种方式

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


/**
 * @author rockjh 【[email protected]】
 * @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 【[email protected]】
 * @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 【[email protected]】
 * @Description: 实现Callable的方式新建一个线程
 * @Date 2017/11/17 14:15
 **/
public class ImplCallableWay implements Callable{

    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 implRunnableWay=new ImplCallableWay<>();
        FutureTask futureTask1=new FutureTask<>(implRunnableWay);
        FutureTask 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 【[email protected]】
 * @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 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 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上面扩展的。

你可能感兴趣的:(java多线程(1)-概述)