线程
线程是程序执行流的最小单位,在传统的开发中一个程序仅仅拥有一个线程,通俗的讲,单线程程序就是一根肠子通屁眼,按照顺序执行,后面的必须要等前面的执行完之后再执行,比如一个全栈工程师,他要完成美术,ui,前端,后端等工作,不管这个工程师多厉害同一时刻他只能干一件事,也就是说这些事是同步执行的,效率相对较慢
多线程
进程是线程的容器,如果一个进程中有多个线程那么这个进程的效率就会快上许多,比如我们有四个工程师,美术,ui,前端,后端,他们可以同时进行工作,互相之间产生的影响较小,异步工作肯定是比同步工作快的。可能有点读者要说,从cpu的角度来看,异步工作也有切换线程带来的消耗,速度不一定比同步快吧?在以前单核cpu的时候确实单线程比较快,现在多核cpu,每一个核可以单独处理一个线程,自然异步就比同步快上许多
多线程也并不是没有坏处,我们可以想象一下,如果有四个工程师,但是只有二台电脑可用,那么这四个工程师就会抢着用这两台电脑,这里电脑就是多线程中的临界资源,需要对临界资源进行加锁处理,锁相关的问题,我们后面再来讨论
并不是线程数越多越好,看服务器cpu的核数,一般几核的cpu开几个线程性能可以最大化
创建线程的三种方式
- 继承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);
}
}
- 实现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);
}
}
- 实现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创建线程经过了下列几个步骤
- 新建一个类,实现Callable接口,覆写call方法
- 使用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
- 使用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上面扩展的。