基础
线程的创建与运行
- 继承Thread类,重写run方法。调用类的start()方法启用。
- 实现Runnable接口,重写run方法。new Thread(Runnable的实现类).start()方法启动。
- 实现Callable接口,从写call方法。
通知等待系列函数
wait()函数
当一个线程调用一个共享变量的wait()方法的时候,该调用线程会被阻塞挂起。
(1)等到其他线程调用共享变量的notify() 或者 notifyAll() 方法,调用阻塞线程才会返回。
(2)其他线程调用了该线程的interrupt()方法,调用线程抛出InterruptedException
异常返回。
需要注意的是,调用共享变量wait()方法的线程,要首先获取到该对象上的监视器锁。如果没有获取该对象上的监视器锁,直接调用共享对象的wait()方法,会抛出ILLegalMonitorStateException
异常。
获取对象上的监视器锁,我们只需要通过synchronized
关键字实现。
//1使用共享变量作为参数
synchronized(共享变量) {
}
//2方法上使用synchronized修饰
synchronized void add() {
//doSomething
}
虚假唤醒
:没有中断,没有调用notify,notifyAll,没有超时的情况下,被唤醒。虽然虚假唤醒在生产环境很少出现,但是为了防患于未然,需要下一个死循环不停的去测试该线程被唤醒的条件是否满足。不满足继续等待。
场景:通过一个queue对象进行加锁synchronized。当queue队列里面为空,线程阻塞。当条件满足队列不为空,退出while循环。
synchronized (obj) {
while (条件不满足) {
obj.wait();
}
}
wait(long timeout)函数
该方法相比较wait()方法多了一个超时时间参数。它的不同之处在于,如果一个线程调用共享变量挂起后,没有在指定的时间内被其他线程调用notify()或者notifyAll()方法唤醒。那么该函数还是会因为超时而返回。wait(0)和wait()方法效果一样。
notify()与notifyAll()函数
一个线程调用共享对象
上的notify()方法后,会唤醒一个在共享对象
上通过wait系列函数被挂起的线程,具体唤醒哪一个等待的线程是随机的。
一个线程调用共享对象
上的notifyAll()方法后,会唤醒共享对象
上所有调用wait系列函数挂起的线程。
线程被唤醒后,不能立马从wait()方法处继续执行,它必须再获取了共享对象
上的监视器锁后,才可以继续执行。
notify(),notifyAll()函数也是必须先获取到共享对象
上的监视器锁后,才可以调用共享对象的notify(),notifyAll()方法。
join方法
项目中有这么一种场景,就是需要等某几件事情都完成后才继续往下执行。Thread类中有一个join
方法,就可以做这个事情。主线程调用Thread类的join()方法会被阻塞,等线程执完返回后,主线程才会继续执行。在JUC并发包中有一个CountDownLatch
,功能更加强大。
sleep方法
Thread类中有一个静态的sleep方法
,当一个执行中的线程调用了Thread的sleep方法
后,调用线程会暂时让出指定时间的执行权,也就是这期间不参与cpu调度
。sleep睡眠时间内不释放获取的锁
。
yield方法
yield方法也是Thread类中的一个静态方法。当一个线程调用yield方法时,实际就是在暗示线程调度器当前线程请求让出自己的cpu使用,让线程调度器进行下一轮的线程调度。
当一个线程调用yield方法时,当前线程会让出cpu使用权,然后处于就绪状态,线程调度器会从线程就绪队列里面获取一个线程优先级最高的线程,当然也有可能获取到的就是刚刚调用yield方法让出时间片的线程。
线程中断
- void interrupt()方法
中断线程。比如:当线程A运行时,线程B可以调用线程A的interrupt()方法来设置线程A的中断标志为true并立即返回。设置标志仅仅是设置标志,线程A实际并没有被中断,它会继续往下执行。如果线程A调用了wait系列函数,join方法,sleep方法被阻塞挂起。这时候若线程B调用线程A的interrupt()方法,线程A会再调用这些方法的地方抛出InterruptedException异常而返回。 - boolean isinterrupted()方法
检测当前线程是否被中断,如果是返回true,否则返回false。 - boolean interrupted()方法
该方法是静态方法。检测当前线程是否被中断,如果是返回true,否则返回false。与isInterrupted()不同的是。该方法如果发现当前线程被中断,则会清除中断标志。
线程死锁
死锁是这两个或两个以上线程在执行过程中
,因争夺资源而造成的互相等待
的现象。死锁产生必须具备的四个条件:
- 互斥条件
资源具有排他性,同一时刻只有一个线程占用。如果此时还有其他线程想要获取资源只能等待,直至占有资源的线程释放该资源。 - 持有并等待
指一个线程已经持有了至少一个资源,但又提出了新的资源请求,而新的资源已被其他线程占有。 - 不可剥夺
线程拿到资源后,在自己释放前,不能被其他线程抢占。 - 环路等待
发生死锁时,必然存在两个或两个以上线上相互等待的情况。
如何避免死锁:
避免死锁,只需要破坏产生死锁其中一个条件就行了。但是学过操作系统的知道,目前只有持有并等待
和环路等待
是可以破坏的。
所以。我们可以指定获取锁的顺序,使用资源申请的有序性
原则就可以避免死锁。
守护线程和用户线程
Java中的线程分为两类,分别为daemon线程(守护线程) 和 user线程(用户线程)。在JVM启动时,会调用main函数,main函数所在的线程就是一个用户线程。JVM内部同时还启动了好多守护线程,比如垃圾回收线程。
守护线程和用户线程的区别。
(1)当最后一个非守护线程结束时,JVM退出。
(2)只要有一个用户线程没有结束,JVM就不会退出。
可以通过Thread类的setDaemon方法设置当前线程为守护线程。
ThreadLocal
多线程访问同一个共享变量特别容易出现并发问题。特别是多线程对同一个变量进行并发写入的时候。为了保证线程安全,一般使用者在访问共享变量的时候需要进行适当的同步。ThreadLocal相当于一个工具类,里面的方法操作都是对应当前线程的操作。
ThreadLocal可以设置线程对象中的本地变量进行赋值。这样每个线程操作的都是自己的变量,就不存在共享变量的线程安全问题了。
创建一个ThreadLocal变量后,每个线程都会复制一个变量到自己的本地内存。
ThreadLocal的使用
本例开启了两个线程,在每个线程内部都设置了本地变的值,然后调用print 函数打印当前本地变量的值。如果打印后调用了本变量的remove 方法, 删除地内中的该变量。
package com.example.demo.entryone;
public class ThreadLocalTest {
static ThreadLocal<String> localVariable = new ThreadLocal<>() ;
static void print(String str) {
//1.1打印当前线程本地内存中 localVariable变量的值
System.out.println(str + ":" +localVariable .get());
//1.2清除当前线程本地内存中的localVariable变量
//localVariable.remove() ;
}
public static void main(String[] args) {
//创建线程ThreadOne
Thread threadOne = new Thread(new Runnable() {
@Override
public void run() {
//设置线程One中的本地变量
localVariable.set("threadOne local variable");
print("threadOne");
System.out.println("threadOne remove after" + ":" + localVariable.get());
}
});
//创建线程ThreadTwo
Thread threadTwo = new Thread(new Runnable() {
@Override
public void run() {
//设置线程Two中的本地变量
localVariable.set("threadTwo local variable");
print("threadTwo");
System.out.println("threadTwo remove after" + ":" + localVariable.get());
}
});
threadOne.start();
threadTwo.start();
}
}
ThreadLocal的实现原理
由该图可知,Thread
类中有一个threadLocals
和一个inheritableThreadLocals
。它们都是ThreadLocalMap
类型的变量,ThreadLocalMap
是一个定制化的Hashmap
。
每个线程中的这两个变量初始化默认都为null。当线程第一次调用ThreadLocal的set或者get方法时才会创建它们。线程的本地变量就是存储在threadLocals里面。
ThreadLocal
就是一个工具壳,它通过set
方法把value
值放入调用线程
的threadLocals
里面并存放起来。如果调用线程一直不终止,那么这个本地变量会一直存放在调用线程的threadLocals变量里面,所以当不需要使用本地变量时可以通过调用TreadLocal的remove方法,从当前线程的threadlocals里面删除该本地变量。
ThreadLocal的set,get及remove方法的实现逻辑如下:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//将当前线程作为key,去查询对应的线程变量,找到则设置
ThreadLocalMap map = getMap(t);
if (map != null)
//将Threadlocal作为key传入ThreadLocalMap中
map.set(this, value);
else
//第一次调用就创建当前线程对应的HashMap
createMap(t, value);
}
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的threadlocals变量
ThreadLocalMap map = getMap(t);
if (map != null) {
//根据key:threadlocal对象,获取value值
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
//移出当前key为:threadlocal对象的entry
m.remove(this);
}
InheritableThreadLocal类
InheritableThreadLocal继承ThreadLocal
,提供了一个特性,就是让子线程可以访问在父线程中设置的本地变量
。
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
/**
* Computes the child's initial value for this inheritable thread-local
* variable as a function of the parent's value at the time the child
* thread is created. This method is called from within the parent
* thread before the child is started.
* <p>
* This method merely returns its input argument, and should be overridden
* if a different behavior is desired.
*
* @param parentValue the parent thread's value
* @return the child thread's initial value
*/
protected T childValue(T parentValue) {
return parentValue;
}
/**
* Get the map associated with a ThreadLocal.
*
* @param t the current thread
*/
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
/**
* Create the map associated with a ThreadLocal.
*
* @param t t