1.线程的创建方式
1、继承Thread类
public class ThreadDemo2 extends Thread {
public static void main(String[] args) {
ThreadDemo2 threadDemo2 = new ThreadDemo2();
threadDemo2.start();
}
//重写run方法
@Override
public void run() {
System.out.println("hello i am a thread" + this.getName());
}
}
在调用了start()方法后,才真正启动了线程,但是调用start()方法后线程并不是马上执行,而是处于就绪状态,这个就绪状态是指该线程已经获取了除CPU资源外的其他资源,等待获取到CPU资源后才会真正的处于运行状态,一旦run方法执行完毕,该线程就处于终止状态。
继承Thread方法的好处是可以直接使用this获取当前线程的信息,而不需要在用Thread.currentThread(),限制条件是java只支持单继承,如果创建线程的时候使用继承,则没法继承别的其他类。
//可以看到Thread类其实也是实现了Runnable 接口,实际上创建线程都是走的Runnable接口
public class Thread implements Runnable {}
2、实现Runnable接口
public class ThreadDemo2 implements Runnable {
public static void main(String[] args) {
ThreadDemo2 threadDemo2 = new ThreadDemo2();
new Thread(threadDemo2).start();
new Thread(threadDemo2).start();
}
//重写run方法
@Override
public void run() {
System.out.println("hello i am a thread" + Thread.currentThread().getName());
}
}
3、使用FutureTask方式可以获取线程的返回值
public class ThreadDemo2 implements Callable<String> {
@Override
public String call() throws Exception {
return "call 线程执行完毕";
}
public static void main(String[] args) {
// 创建异步任务,
FutureTask<String> futureTask = new FutureTask(new ThreadDemo2());
// 启动线程
new Thread(futureTask).start();
try {
String result = futureTask.get();
System.out.println(result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
小结: 使用继承方式的好处是方便传参,你可以在子类里面添加成员变量,通过set方法设置参数或者通过构造函数进行传递,而如果使用Runnable 方式,则只能使用主线程里面被声明为final 的变量。不好的地方是Java 不支持多继承,如果继承了Thread 类,那么子类不能再继承其他类,而Runable 则没有这个限制。前两种方式都没办法拿到任务的返回结果,但是Futuretask 方式可以。
2.线程通知与等待
1、wait()方法
public static void main(String[] args) throws InterruptedException {
//单独用wait()方法执行会报异常 java.lang.IllegalMonitorStateException
Object o = new Object();
o.wait();
}
1.1 IllegalMonitorStateException异常
调用wait()方法的对象需要先获取该对象的监视器锁,否则会报IllegalMonitorStateException异常。
1.2 使用方式
获取监视器锁的方法是需要和synchronized关键字进行组合使用,如下:
(1)执行synchronized同步代码块时,使用该共享变量作为参数;
(2)调用该共享变量的且带有synchronized关键字的方法。
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
synchronized (o){
try {
System.out.println("线程被阻塞");
o.wait();
System.out.println("jjjj");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
Thread.sleep(1000);
synchronized (o){
o.notifyAll();
System.out.println("唤醒");
}
System.out.println(11);
}
1.3 wait的唤醒方式
当一个线程调用另外一个线程的wait方法时,该调用线程会被阻塞挂起,唤醒的方式:
(1)别的线程调用该共享对象的notify/notifyAll方法;
(2)其他线程调用该线程的interrupt()方法。
1.4 虚假唤醒
因阻塞的线程未经别的线程调用notify/notifyAll方法进行通知时导致的由挂起状态转为运行状态,可能是由于中断或者等待超时原因引起的现象。
解决办法:需要不停的测试该线程被唤醒的条件是否满足,即循环判断
synchronized(obj){
while(条件不满足){
obj.wait();
}
}
1.5生产者消费者案例
private static int MAX_SIZE =1;
static LinkedBlockingQueue<String> linkedBlockingQueue = new LinkedBlockingQueue(MAX_SIZE);
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 5; i++) {
//消费者
new Thread(()->{
synchronized (linkedBlockingQueue){
while (linkedBlockingQueue.isEmpty()){//空队列
try {
System.out.println("消费者准备阻塞");
//挂起当前线程,并释放通过同步块获取的 linkedBlockingQueue 上的锁,
//让生产者线程可以获取该锁,将生产元素放入队
linkedBlockingQueue.wait();
System.out.println("消费者被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("消费者:"+Thread.currentThread().getName()+":::"+linkedBlockingQueue.toString());
//消费元素,并通知唤醒生产者线程
linkedBlockingQueue.poll();
linkedBlockingQueue.notifyAll();
}
},"B"+i).start();
}
Thread.sleep(3000);
//生产者
for (int i = 0; i < 5; i++) {
new Thread(()->{
synchronized (linkedBlockingQueue){
while (linkedBlockingQueue.size()==MAX_SIZE){
try {
System.out.println("生产者准备阻塞");
// 挂起当前线程, 并释放通过同步块获取的linkedBlockingQueue上的锁,
//让消费者线程可以获取该锁,然后获取队列里面的元素
linkedBlockingQueue.wait();
System.out.println("生产者被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 空闲则生成元素, 并通知消费者线程
try {
linkedBlockingQueue.put("a");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("生产者:"+Thread.currentThread().getName()+":::"+linkedBlockingQueue.toString());
linkedBlockingQueue.notifyAll();
}
},"A"+i).start();
}
}
2、wait(long timeout)方法
wait方法带参数,表示在参数所设置的时间内没有接受到notify/notifyAll方法将其唤醒,则会因超时而接着往下执行,默认被唤醒的意思。传参若为负值,则报异常IllegalArgumentException,若参数为0,则和wait()等价,默认wait()方法底层就是调用的wait(0);
public static void main(String[] args) {
Object o = new Object();
new Thread(()->{
synchronized (o){
try {
System.out.println("开始等待");
o.wait(1000);
System.out.println("超时了");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
System.out.println("主线程执行");
}
3、wait(long timeout, int nanos)方法
如下代码,即该方法对应的底层逻辑,默认还是调用了wait(long timeout)方法,
timeout- 要等待的最长时间(以毫秒为单位)。
nanos - 额外时间(以毫微秒为单位,范围是 0-999999)。
// o.wait(1000,2); 这样传入的参数,在最后的运行结果中为wait(1001)
public final void wait(long timeout, int nanos) throws InterruptedException {
if (timeout < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos > 0) {
timeout++;
}
wait(timeout);
}
4、notify()方法
一个线程调用共享对象的notify()方法后,会唤醒一个在该共享变量上调用wait 系列方法后被挂起的线程。一个共享变量上可能会有多个线程在等待,具体唤醒哪个等待的线程是随机的。(下面的例子输出的是放在前面的线程)
public static void main(String[] args) throws InterruptedException {
Object o = new Object();
new Thread(()->{
synchronized (o){
try {
System.out.println("开始等待"+Thread.currentThread().getName());
o.wait();
System.out.println("被唤醒 "+Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
synchronized (o){
try {
System.out.println("开始等待"+Thread.currentThread().getName());
o.wait();
System.out.println("被唤醒 "+Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
Thread.sleep(3000);
new Thread(()->{
synchronized (o){
System.out.println("准备唤醒"+Thread.currentThread().getName());
o.notify();
try {
System.out.println("唤醒后睡三秒");
Thread.sleep(3000);
//沉睡三秒,此时wait方法后的线程还不会继续执行,因此刻还未释放监视器锁,
//等三秒过去后才能继续执行之前等待的线程
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
System.out.println("主线程执行");
}
输出结果如下,此时程序仍然处于等待状态,因A线程还未被释放
开始等待B
开始等待A
主线程执行
准备唤醒C
唤醒后睡三秒
被唤醒 B
5、notifyAll()方法
不同于在共享变量上调用notify()函数会唤醒被阻塞到该共享变量上的一个线程,notifyAll() 方法则会唤醒所有在该共享变量上由于调用wait 系列方法而被挂起的线程。
如上述的例子中将notify方法换为notifyAll(),则输出结果为(程序是正常执行完毕的,不会阻塞):
开始等待A
开始等待B
主线程执行
准备唤醒C
唤醒后睡三秒
被唤醒 B
被唤醒 A
6、join方法
join 的重载方法有:
join():等待该线程终止。
join(long millis):等待该线程终止的时间最长为 millis
毫秒。超时为 0
意味着要一直等下去。
join(long millis, int nanos):等待该线程终止的时间最长为 millis
毫秒 + nanos
纳秒。
Thread AThread = new Thread(()->{
System.out.println("A Thread start");
},"A");
Thread BThread = new Thread(()->{
System.out.println("B Thread start");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
},"B");
System.out.println("主线程执行");
AThread.start();
BThread.start();
AThread.join();
BThread.join();
System.out.println("over A and B");//此时的输出会是在A和B线程执行完毕之后才会执行
下面的例子中,线程A死循环,主线程调用线程A的join方法,等待线程A执行完毕,待B线程休眠3秒后调用主线程的interrupt()方法,设置主线程的中断标志,从结果看到是在AThread.join();处抛出InterruptedException异常。
Thread AThread = new Thread(()->{
System.out.println("A Thread start");
while (true){
}
},"A");
Thread mainThread = Thread.currentThread();
Thread BThread = new Thread(()->{
System.out.println("B Thread start");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
mainThread.interrupt();
},"B");
System.out.println("主线程执行");
AThread.start();
BThread.start();
AThread.join();
System.out.println("over A and B");
结果:
主线程执行
B Thread start
A Thread start
Exception in thread "main" java.lang.InterruptedException
at java.lang.Object.wait(Native Method)
at java.lang.Thread.join(Thread.java:1252)
at java.lang.Thread.join(Thread.java:1326)
at com.yuancheng.boot.thread.ThreadDemo4.main(ThreadDemo4.java:42)
7、sleep方法
休眠,需要注意的一点是,如果A线程处于sleep状态,此时别的线程调用A线程的interrupt()方法,则会抛出InterruptedException异常。还有参数不可以传入负值,会报IllegalArgumentException。
8、yield方法
暂停当前正在执行的线程对象,并执行其他线程。(让出CPU 执行权)
当一个线程调用y ield 方法时, 当前线程会让出CPU 使用权,然后处于就绪状态,线程调度器会从线程就绪队列里面获取一个线程优先级最高的线程,当然也有可能会调度到刚刚让出CPU 的那个线程来获取CPU 执行权。
public class ThreadDemo5 implements Runnable {
public ThreadDemo5() {
Thread thread = new Thread(this);
thread.start();
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
if(i % 5 ==0){//即首次的时候进入
System.out.println(Thread.currentThread()+"yield cpu...");
Thread.yield();//让出cpu的执行权
}
}
System.out.println(Thread.currentThread()+"over...");
}
public static void main(String[] args) throws InterruptedException {
new ThreadDemo5();
new ThreadDemo5();
new ThreadDemo5();
}
}
输出结果:
Thread[Thread-1,5,main]yield cpu...
Thread[Thread-2,5,main]yield cpu...
Thread[Thread-0,5,main]yield cpu...
Thread[Thread-0,5,main]over...
Thread[Thread-1,5,main]over...
Thread[Thread-2,5,main]over...
若将上面的那个Thread.yield(); 注释掉,则情况为下面:
Thread[Thread-0,5,main]yield cpu...
Thread[Thread-0,5,main]over...
Thread[Thread-2,5,main]yield cpu...
Thread[Thread-2,5,main]over...
Thread[Thread-1,5,main]yield cpu...
Thread[Thread-1,5,main]over...
总结: sleep与yield 方法的区别在于,当线程调用sleep 方法时调用线程会被阻塞挂起指定的时间,在这期间线程调度器不会去调度该线程。而调用yield方法时,线程只是让出自己剩余的时间片,并没有被阻塞挂起,而是处于就绪状态,线程调度器下一次调度时就有可能调度到当前线程执行。
9、线程中断方法
9.1 void interrupt()方法
中断线程。
如果当前线程没有中断它自己(这在任何情况下都是允许的),则该线程的 checkAccess
) 方法就会被调用,这可能抛出 SecurityException
。
如果线程在调用 Object
类的 wait()
)、wait(long)
) 或 wait(long, int)
) 方法,或者该类的join()
)、join(long)
)、join(long, int)
)、sleep(long)
) 或 sleep(long, int)
) 方法过程中受阻,则其中断状态将被清除,它还将收到一个 InterruptedException
。
当线程A 运行时,线程B 可以调用钱程A的interrupt() 方法来设置线程A 的中断标志为true 并立即返回。设置标志仅仅是设置标志, 线程A 实际并没有被中断, 它会继续往下执行。
9.2 boolean isinterrupted() 方法
测试线程是否已经中断。线程的中断状态 不受该方法的影响,如果该线程已经中断,则返回 true
;否则返回 false
。
9.3 boolean interrupted() 方法
测试当前线程是否已经中断。线程的中断状态 由该方法清除。换句话说,如果连续两次调用该方法,则第二次调用将返回 false(在第一次调用已清除了其中断状态之后,且第二次调用检验完中断状态前,当前线程再次中断的情况除外)。
与isInterrupted不同的是,该方法如果发现当前线程被中断, 则会清除中断标志,并且该方法是static 方法, 可以通过Thread 类直接调用。另外从下面的代码可以知道, 在interrupted()内部是获取当前调用线程的中断标志而不是调interrupted()方法的实例对象的中断标志。
例子:
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{
while(true){
}
});
thread.start();
thread.interrupt();//设置中断标志
// 获取中断标志 true
System.out.println("-isInterrupted-------"+thread.isInterrupted());
// 获取中断标志并重置
System.out.println("-interrupted-------"+thread.interrupted());
// 获取中断标志并重置
System.out.println("-interrupted-------"+Thread.interrupted());
// 获取中断标志
System.out.println("-isInterrupted-------"+thread.isInterrupted());
thread.join();
System.out.println("main is over");
}
public static boolean interrupted() {
return currentThread().isInterrupted(true);//获取的是当前的线程,上面的例子都是取得主线程
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{
while(!Thread.currentThread().interrupted()){//清除了中断标志
}
System.out.println("Thread is "+Thread.currentThread()+"===::::"+Thread.currentThread().isInterrupted());
});
thread.start();
thread.interrupt();//设置中断标志
//
thread.join();
System.out.println("main is over");
}
结果为(由输出结果可知,调用interrupted() 方法后中断标志被清除了。):
Thread is Thread[Thread-0,5,main]===::::false
main is over
10、死锁
10.1 死锁发生的条件
互斥条件: 指线程对己经获取到的资源进行排它性使用, 即该资源同时只由一个线程占用。如果此时还有其他线程请求获取该资源,则请求者只能等待,直至占有资源的线程释放该资源。
请求并持有条件: 指一个线程己经持有了至少一个资源, 但又提出了新的资源请求,而新资源己被其他线程占有,所以当前线程会被阻塞,但阻塞的同时并不释放自己己经获取的资源。
不可剥夺条件: 指线程获取到的资源在自己使用完之前不能被其他线程抢占, 只有在自己使用完毕后才由自己释放该资源。
环路等待条件: 指在发生死锁时, 必然存在一个线程→资源的环形链, 即线程集合{T0 , T1 T2 ,…, Tn }中的T0 正在等待一个T1 占用的资源, Tl 正在等待T2 占用的资源,……Tn 正在等待己被T0 占用的资源。
10.2 如何避免线程死锁
要想避免死锁,只需要破坏掉至少一个构造死锁的必要条件即可, 但是学过操作系统的读者应该都知道,目前只有请求并持有和环路等待条件是可以被破坏的。
更新资源的有序性即可,访问资源的顺序都保持一致,不允许随意访问资源就可保证环路等待的条件不会发生。
11、用户线程和守护线程
用户线程:main线程就属于用户线程。
守护线程:thread . setDaemo(true) ;在启动线程之前调用该方法则会将线程设置为守护线程,在主线程或者所有的用户线程全部结束后,JVM会直接销毁,不会管守护线程是否执行等的状态
如果你希望在主线程结束后JVM进程马上结束,那么在创建线程时可以将其设置为守护线程,如果你希望在主线程结束后子线程继续工作,等子线程结束后再让JVM 进程结束,那么就将子线程设置为用户线程。
12、Threadlocal
如果你创建了一个ThreadLocal 变量,那么访问这个变量的每个线程都会有这个变量的一个本地副本。当多
个线程操作这个变量时,实际操作的是自己本地内存里面的变量,从而避免了线程安全问题。创建一个ThreadLocal 变量后,每个线程都会复制一个变量到自己的本地内存。
在每个线程内部都有一个名为threadLocals 的成员变量, 该变量的类型为Hash Map , 其中key 为我们定义的ThreadLocal 变量的this 引用, value 则为我们使用set 方法设置的值。每个线程的本地变量存放在线程自己的内存变量threadLocals 中,如果当前线程一直不消亡, 那么这些本地变量会一直存在, 所以可能会造成内存溢出, 因
此使用完毕后要记得调用ThreadLocal 的remove 方法删除对应线程的threadLocals 中的本地变量。
12.1 代码示例
public class ThreadDemo9 {
static void print(String str){
System.out.println(str+":"+threadLocal.get());
// threadLocal.remove();//删掉本地线程的存储的数据
}
static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
threadLocal.set("Thread A set");
print("Thread A ");
System.out.println("thread A remove after "+threadLocal.get());
},"A").start();
new Thread(()->{
threadLocal.set("Thread B set");
print("Thread B ");
System.out.println("thread B remove after "+threadLocal.get());
},"B").start();
}
}
输出结果:
Thread A :Thread A set
thread A remove after Thread A set
Thread B :Thread B set
thread B remove after Thread B set
将注释放开:
Thread A :Thread A set
thread A remove after null
Thread B :Thread B set
thread B remove after null
12.2 InheritableThreadLocal类
InheritableThreadLocal继承自ThreadLocal , 其提供了一个特性,就是让子线程可以访问在父线程中设置的本地变量,适用场景:子线程需要使用存放在threadlocal 变量中的用户登录信息,再比如一些中间件需要把统一的id 追踪的整个调用链路记录下来,会用到该类。
public class ThreadDemo10 {
static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
threadLocal.set("Thread A set");
new Thread(()->{
System.out.println("A Thread :"+ threadLocal.get());
},"A").start();
System.out.println("main Thread :"+threadLocal.get());
}
}
输出结果为:
main Thread :Thread A set
A Thread :null
更新ThreadLocal:**
static ThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
结果为(子线程可以获取到父线程的数据):
main Thread :Thread A set
A Thread :Thread A set
12.3 源码分析
1.每个线程都有一个ThreadlocalMap对象(threadLocals),key为threadlocal对象的弱引用,value为对应的副本值;
2.ThreadLocalMap的数据结构:类似于HashMap的key-value键值对,底层实现是数组,没有链表结构,适用开放定址法解决hash冲突。
3.set()方法
public void set(T value) {
Thread t = Thread.currentThread();//获取当前线程
ThreadLocalMap map = getMap(t);//获取当前线程的map对象,即线程类的属性threadLocals。
if (map != null)
//底层new Entry(key, value)给数组赋值,且继承自WeakReference,创建弱引用的对象指向key,有效 //防止内存泄露。
map.set(this, value);
else
createMap(t, value);
}
4.get()方法
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
5.remove()方法
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}