线程基础
线程
使用多线程的好处
- 可以更好的利用cpu资源
- 能有更快的响应时间(使用多线程合理的情况下)
- 更好的编程模型
线程的状态
如上图详细的描述了线程的生命周期
,不同事件触发,而导致的状态转换。
启动和终止线程
线程的启动
thread.start();
线程启动的代码形式,即 当前线程同步告知jvm,如果线程规划器空间的话,则会启动线程。
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 */
}
}
}
private native void start0();
源码中可以看出这是一个同步方法,且启动的时候,检查threadStatus,并将自己添加进group中去,然后调用start0() 本地方法。
线程的中断
thread.interrupt(); 实际就是设置中断的标识。
如果在阻塞的状态下,例如,sleep()、wait、join,则会抛出InterruptedException。
实际的用途(根据状态标志判断是否退出线程)还是看一段代码方便:
public void run() {
try {
while (true){
Thread.sleep(1000l);//阻塞状态,线程被调用了interrupte()方法,清除中断标志,抛出InterruptedException
//dosomething
boolean isIn = this.isInterrupted();
//运行状态,线程被调用了interrupte()方法,中断标志被设置为true
//非阻塞状态中进行中断线程操作
if(isIn) break;//退出循环,中断进程
}
}catch (InterruptedException e){//阻塞状态中进行中断线程操作
boolean isIn = this.isInterrupted();//退出阻塞状态,且中断标志被清除,重新设置为false,所以此处的isIn为false
return;//退出run方法,中断进程
}
}
抛出异常后,线程的中断标志会重新设置为false。
线程间的通讯
线程之间的通讯其实很简单直观,对共享变量的多线程修改就是一种通讯方式,但是为了保证这种方式不出问题,就需要进行额外的同步,其代价也是相当的大的。
使用volatile和synchronized关键字
volatile 保证了可见性,在jvm 的时候已经学习过了可见性。
synchronized关键字则是对方法或者代码块进行同步,里面应该是包括对变量的访问。
使用等待/通知机制
我们可以看到Java的大父类,Object,里面有许多默认的方法其中就包括了wait() 和 notify() notifyAll() 方法,可以看出Java当初设计的时候就是考虑这多线程方面去的。
假设:线程A 调用了对象T的wait方法,就可以理解为 线程A需要一个T对象 notify或者notifyAll的方法才能进行下面的步骤。现有个线程B调用了T的notify方法,线程A则继续开始运行。
等待/通知有个经典公式
等待:
1)获取对象的锁
2)如果条件不满足,那么调用对象的wait方法,被通知后再检查条件
3)条件满足则执行对应的逻辑
通知:
1)获取对象的锁
2)改变条件
3)通知所有等待在对象上的线程。
public class Service {
static boolean flag = true;
public void testMethod(Object lock) {
try {
synchronized (lock) {
System.out.println("begin wait() ThreadName="
+ Thread.currentThread().getName());
// 循环判断,如果flag 为true则会进行等待队列
// 进入等待队列后,线程则会释放锁,而不会占用着资源
while(flag){
lock.wait();
}
System.out.println(" end wait() ThreadName="
+ Thread.currentThread().getName());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void synNotifyMethod(Object lock) {
try {
synchronized (lock) {
System.out.println("begin notify() ThreadName="
+ Thread.currentThread().getName() + " time="
+ System.currentTimeMillis());
flag = false;
lock.notify();
Thread.sleep(5000);
System.out.println(" end notify() ThreadName="
+ Thread.currentThread().getName() + " time="
+ System.currentTimeMillis());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
thread.join()
先看下join的源码,可以简化成下面的这个样子
public final synchronized void join()
throws InterruptedException {
while (isAlive()) {
wait(0);
}
}
他调用的就是线程对象的wait方法。
假设我们在threadA上调用threadB的join方法。那threadA则会等待threadB线程结束才会从join()返回。
ThreadLocal
TreadLocal,本地线程变量,可见是单线程里面的变量,所以可以认为是线程安全的。
上面这图看气力好像有点复杂,我们还是结合图对着源码来分析。
public class ThreadLocal<T> {
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
}
以上是对ThreadLocal 源码的部分截取。
- 首先要明白的是,Thread类中有个包可见访问字段 threadLocals,他是一个map, key值为threadLocal对象,value则是用户设置的对象(A、B、C。。。)
- Thread 里面的 treadLocals对我们是透明的,所以我们在使用的时候,只需要创建ThreadLocal的对象就好了。
就像这样:
ThreadLocal<Integer> threadLocal1 = new ThreadLocal<>();
ThreadLocal<String> threadLocal2 = new ThreadLocal<>();
在多线程的情况下,多条线程执行这两段代码的的时候,如果有设置值的话。
那他们的线程对象 thread 里面的threadLocals 就都存在这两个对象。
所以他们设置的时候会先去 线程中获取 线程里面的map。
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
更通俗点就是:我有100个线程,每个线程都保存引用为“java.lang.ThreadLocal@610455d6”的threadLocal对象,但是我100个线程中保存的值都是不相同的。 并且我这100个线程里面有的是ThreadLocalMap 可以保存各种各样的 ThreadLocal对象,以及对应的值
。