Thread
构造函数
//无参
Thread()
//传入Runnable接口实现
Thread(Runnable target)
//传入Runnable接口实现,传入线程名
Thread(Runnable target, String name)
//设置当前线程用户组
Thread(ThreadGroup group, Runnable target)
//设置用户组,传入线程名
Thread(ThreadGroup group, Runnable target, String name)
//设置用户组,传入线程名,设置当前线程栈大小
Thread(ThreadGroup group, Runnable target, String name, long stackSize)
init方法初始化线程
private void init(ThreadGroup g, Runnable target, 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) {
if (security != null) {
g = security.getThreadGroup();
}
if (g == null) {
g = parent.getThreadGroup();
}
}
//检查访问权限
g.checkAccess();
/*
* Do we have the required permissions?
*/
//这里应该是设置访问权限
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
//增加线程组中未启动线程的数量,待start执行后移除
g.addUnstarted();
this.group = g;
/* 设置当前线程是否为守护线程,默认是和当前类的ThreadGroup设置相
* 同。如果是守护线程的话,当前线程结束会随着主线程的退出而退出。
*jvm退出的标识是,当前系统没有活跃的非守护线程。
*/
this.daemon = parent.isDaemon();
/*设置的线程的访问权限默认为当前ThreadGroup权限*/
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 */
/*设置指定的栈大小,如果未指定大小,将在jvm 初始化参数中声明:Xss参数进行指定*/
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}
总结
- 获得执行线程的的引用,即用以创建新线程的上级线程引用
- 设置线程访问权限,线程组及线程的访问权限
- 设置线程栈大小,默认0
- 线程序列号+1
start()方法
//启动该线程,由JVM调用执行run方法
//结果是两个线程同时运行:当前线程(从调用返回到start方法)和另一个线程(执行其run方法)。
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".
*/
//此判断当前线程只能被启动一次,不能被重复启动,对应线程状态为NEW(thread类内部有stat枚举类)
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()
线程状态更新至就绪状态。由JVM执行该线程的run()
run()
线程所执行的方法,处于运行状态。
yield()本地方法
使当前执行线程暂停一会,让其它线程得以执行。只是临时让出时间片,不会释放拥有的锁。
sleep()
使当前执行线程休眠指定的时间,不释放持有的锁。 定时等待状态。
join()
A线程调用B线程的join()方法,将会使A等待B执行,直到B线程终止。如果传入time参数,将会使A等待B执行time的时间,如果time时间到达,将会切换进A线程,继续执行A线程。
不太懂,很少用,可以达到类似排队执行的效果
interrupt()
中断当前线程的执行,允许当前线程对自身进行中断,否则将会校验调用方线程是否有对该线程的权限。
interrupted(),返回true或者false:
查看当前线程是否处于中断状态,这个方法比较特殊之处在于,如果调用成功,会将当前线程的interrupt status清除。所以如果连续2次调用该方法,第二次将返回false。
isInterrupted(),返回true或者false:
上面方法相同的地方在于,该方法返回当前线程的中断状态。不同的地方在于,它不会清除当前线程的interrupt status状态。
Thread内部枚举类State
public enum State {
/**
* 新建状态;尚未启动的线程的线程状态
*/
NEW,
/**
* 可运行状态;
*/
RUNNABLE,
/**
* 阻塞状态;需等待唤醒
* Object#wait() Object.wait
*/
BLOCKED,
/**
* 等待状态(无限等待需主动唤醒);
* Objec.wait()无限等待,
* LockSupport.park()juc禁用当前线程,
* Thread.join()无限等待
*/
WAITING,
/**
* 定时等待状态;
* sleep(),Objec.wait(long)非无限等待
* LockSupport.parkNanos
* LockSupport.parkUntil
*/
TIMED_WAITING,
/**
* 终止状态;线程已完成执行。
*/
TERMINATED;
}
- 线程状态转换图:
Thread类与object类方法区别
- sleep 与 wait 区别:
sleep()方法,Thread类
wait():Object
sleep()方法暂停执行指定的时间,自动恢复运行状态。线程不会释放对象锁
调用 wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,需notify();
线程的生命周期
新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)
内部存在一个静态内部类ThreadLocalMap,ThreadLocalMap内有一个静态内部类Entry(键值对)。
k=threadlocal对象,V=存入变量。
Thread内部变量副本(线程隔离)-ThreadLocal
线程Thread内部有两个个ThreadLocal.ThreadLocalMap类型的成员变量;
/*
* 线程的本地变量
*/
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* 父线程的threadlocal变量,可以利用该变量进行父线程变量传递至子线程中进行使用。
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
如何使用这俩变量呢?
- ThreadLocal.ThreadLocalMap threadLocals
- 创建new ThreadLocal();
- set()存入value即可
public void set(T value) {
//获得当前线程引用
Thread t = Thread.currentThread();
//获得线程内的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//为线程threadLocals 赋值,即创建一个ThreadLocalMap
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
//
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
- get()获得存入的value
ThreadLocal是懒加载的,在set或者get才会创建threadlocalMap
- ThreadLocal.ThreadLocalMap inheritableThreadLocals
- new InheritableThreadLocal()
- set() 会在thread对象内的inheritableThreadLocals赋值
InheritableThreadLocal 为ThreadLocal的子类 重写了getMap(),createMap()会对thread的inheritableThreadLocals赋值
//关键代码
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
/**
* Create the map associated with a ThreadLocal.
*/
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
总结:流程
- 创建ThreadLocal后使用set、get方法会创建初始化一个ThreadLocalMap为当前线程的threadLocals赋值
- 多次创建ThreadLocal并set(),会存入ThreadLoaclMap 的Entry数组中(Key=创建的threadLocal对象,value=存入变量),不过你只能get当前ThreadLocal对象所对应的value。
- 是通过遍历索引的方式解决哈希冲突
- 什么时候扩容:threshold = len * 2 / 3;默认扩容两倍
自我理解:
①第一次set 会创建ThreadLocallMap并为线程的threadLocals(就是一个Map)赋值
②重新newThreadLocal在set会存入线程里的map里,key就是执行set方法的ThreadLocal对象
关键代码
public void set(T value) {
Thread t = Thread.currentThread();
//获得线程里的map
ThreadLocalMap map = getMap(t);
if (map != null)
//往线程的map put
map.set(this, value);
else
createMap(t, value);
}
//创建map
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
如何避免内存泄漏?
最简单手动remove即可。
ThreadLocalMap的问题
由于ThreadLocalMap的key是弱引用,而Value是强引用。ThreadLocal在没有外部对象强引用时,发生GC时弱引用Key会被回收,而Value不会回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。
ThreadLocal内部的ThreadLocalMap键为弱引用,会有内存泄漏的风险。
如何避免泄漏
调用ThreadLocal的get()、set()方法时完成后再调用remove方法,将Entry节点和Map的引用关系移除。
比如使用线程池的时候,线程结束是不会销毁的,会再次使用的。就可能出现内存泄露。key被回收,value确没被回收。
ThreadLocal 的作用?
通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。
应用场景
- 数据库连接
- Session管理
- 分页
参考博文:
https://www.cnblogs.com/aspirant/p/8991010.html
https://www.jianshu.com/p/6fc3bba12f38?tdsourcetag=s_pctim_aiomsg
https://www.jianshu.com/p/3a09dd40f8a4?tdsourcetag=s_pctim_aiomsg