简介
在介绍线程前先了解下进程与线程的概念
进程
进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。进程是一种抽象的概念,从来没有统一的标准定义。
进程一般由程序,数据集合和进程控制块三部分组成。程序用于描述进程要完成的功能,是控制进程执行的指令集;数据集合是程序在执行时所需要的数据和工作区;程序控制块包含进程的描述信息和控制信息是进程存在的唯一标志
进程具有的特征:
-
动态性:进程是程序的一次执行过程,是临时的,有生命期的,是动态产生,动态消亡的;
-
并发性:任何进程都可以同其他进行一起并发执行;
-
独立性:进程是系统进行资源分配和调度的一个独立单位;
-
结构性:进程由程序,数据和进程控制块三部分组成
线程
在早期的操作系统中并没有线程的概念,进程是拥有资源和独立运行的最小单位,也是程序执行的最小单位。任务调度采用的是时间片轮转的抢占式调度方式,而进程是任务调度的最小单位,每个进程有各自独立的一块内存,使得各个进程之间内存地址相互隔离。
后来,随着计算机的发展,对CPU的要求越来越高,进程之间的切换开销较大,已经无法满足越来越复杂的程序的要求了。于是就发明了线程,线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。
一个进程可以有一个或多个线程,各个线程之间共享程序的内存空间(也就是所在进程的内存空间)。一个标准的线程由线程ID,当前指令指针PC,寄存器和堆栈组成。而进程由内存空间(代码,数据,进程空间,打开的文件)和一个或多个线程组成。
进程与线程的区别
-
线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位;
-
一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线
-
进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段,数据集,堆等)及一些进程级的资源(如打开文件和信号等),某进程内的线程在其他进程不可见;
-
调度和切换:线程上下文切换比进程上下文切换要快得多
Java中的线程
在Java中如果想单独启动一个线程可以通过两种方式一种是通过声明一个类继承Thread并实现run方法new出对象后调用start方法就可以将线程交给jvm调度了
class PrimeThread extends Thread {
long minPrime;
PrimeThread(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
}
}
PrimeThread p = new PrimeThread(143);
p.start();
第二种方法则是通过继承Runable接口并实现run方法后通构造Thread对象后调用start方法将线程交给jvm调度
class PrimeRun implements Runnable {
long minPrime;
PrimeRun(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
}
PrimeRun p = new PrimeRun(143);
new Thread(p).start();
这里说下面试经常会被问到的问题:**start()方法和run()**方法的区别
首先看下start方法在api中注释
Causes this thread to begin execution; the Java Virtual Machine
calls the <code>run</code> method of this thread.
The result is that two threads are running concurrently: the
current thread (which returns from the call to the
<code>start</code> method) and the other thread (which executes its
<code>run</code> method).
It is never legal to start a thread more than once.
In particular, a thread may not be restarted once it has completed
execution.
这段话翻译过来就是start方法以线程的方式执行而真正执行是通过java虚拟机调用该线程的run方法,调用start方法的线程在start方法结束后直接返回另一个线程开始执行run方法,并且start方法最多只允许调用一次
多说一句关于new Thread() 如果不调用start()方法线程只是处于新建状态,在调用了方法后线程才变为就绪状态,等到cpu的时间片到来线程才会进入执行状态
而run方法的执行只是在当前线程中执行并且可以被重复调用
Thread源码解析
首先Thread 实现了Runnable接口,该接口中只有一个抽象方法供子类复写,接下来看下Thread类的成员变量
/**线程名对其他线程可见**/
private volatile String name;
/**线程优先级**/
private int priority;
private Thread threadQ;
private long eetop;
private boolean single_step;
/**是否是守护线程,默认非守护线程,如果守护线程未终止程序将一直运行**/
private boolean daemon = false;
private boolean stillborn = false;
/**实际线程任务**/
private Runnable target;
/**线程分组**/
private ThreadGroup group;
/**上下文类加载器**/
private ClassLoader contextClassLoader;
private AccessControlContext inheritedAccessControlContext;
/**线程编号,如果不指定线程名默认用此编号作为线程名**/
private static int threadInitNumber;
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
/**线程操作时所需堆栈大小 此参数与JVM中Xss参数作用相同**/
private long stackSize;
/**本地阻塞事件指针**/
private long nativeParkEventPointer;
/**线程ID**/
private long tid;
/**线程编号 用于产生tid**/
private static long threadSeqNumber;
/**线程状态 默认状态为0表示线程处于新建且未运行状态**/
private volatile int threadStatus = 0;
下面通过两个构造函数分析线程在初始化阶段主要做了哪些事情,先看无参构造函数通过init方法初始化线程,线程任务target为null此时如果调用start方法线程将不做任何工作,带有参数target的构造函数,target为Runnable的实现类,调用start方法后等待执行run方法
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
而线程的初始化均由init方法完成,重点看下init方法的实现,主要参数为线程组,实际运行线程的Runable实现,线程名,线程栈大小等参数,主要工作就是初始化线程组,初始化target,初始化运行模式,优先级,线程id等操作
private void init(ThreadGroup g, Runnable 111, String name,long stackSize, AccessControlContext acc,boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
/* Determine if it's an applet or not */
/* If there is a security manager, ask the security manager what to do. */
if (security != null) {
g = security.getThreadGroup();
}
/* If the security doesn't have a strong opinion of the matter use the parent thread group. */
if (g == null) {
g = parent.getThreadGroup();
}
}
/* checkAccess regardless of whether or not threadgroup is explicitly passed in. */
g.checkAccess();
/*
* Do we have the required permissions?
*/
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
g.addUnstarted();
this.group = g;
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext = acc != null ? acc : AccessController.getContext();
this.target = target;
setPriority(priority);
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}
start方法分析
start方法调用首先会判断线程是否处于新建状态,如果不是直接抛出异常这也是很多面试会问的“thread 方法是否能调用多次”的答案,因为start方法一旦调用线程的状态就由新建转换为(就绪状态或者执行状态)
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
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 */
}
}
}
currentThread 方法
该方法为静态native方法,返回指向当前线程的引用
Thread.currentThread();
yield 方法
该方法为静态native方法,yield方法会临时暂停当前正在执行的线程,来让有同样优先级的正在等待的线程有机会执行。如果没有正在等待的线程,或者所有正在等待的线程的优先级都比较低,那么该线程会继续运行。执行了yield方法的线程什么时候会继续运行由线程调度器来决定,不同的厂商可能有不同的行为。yield方法不保证当前的线程会暂停或者停止,但是可以保证当前线程在调用yield方法时会放弃CPU
Thread.yield()
sleep 方法
该方法为静态native方法,暂停当前线程执行并不会释放锁
Java中的中断
java中的中断其实是一种协作的机制,让你可以更好的构造可取消任务。如果正在执行的任务强制结束,那么很可能会造成数据不一致的情况,而中断不要求立即返回,对于阻塞方法响应中断的方式是抛出InterruptedException,其他的方法响应中断的方式仅仅是标记这个任务被中断了,并不会立即退出运行,但是通过这个中断标记可以让上层调用方法感知到这个方法被中断过,可以增加业务上的响应中断的方法。
interrupt 方法
interrupt方法是用于中断线程的,调用该方法的线程的状态将被置为"中断"状态。注意:线程中断仅仅是设置线程的中断状态位,不会停止线程。需要用户自己去监视线程的状态为并做处理。支持线程中断的方法(也就是线程中断后会抛出InterruptedException的方法,比如这里的sleep,以及Object.wait等方法)就是在监视线程的中断状态,一旦线程的中断状态被置为“中断状态”,就会抛出中断异常。