join方法
调用了join方法会先执行另外一个线程,在等待的过程中释放对象锁,底层是基于wait()方法封装的。
三个线程 T1,T2,T3,怎么确保它们按顺序执行?
创建Thread01类,包名:com.wdp.thread02.test
package com.wdp.thread02.test;
public class Thread01 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> System.out.println(Thread.currentThread().getName() + "线程执行"), "t1");
Thread t2 = new Thread(() -> System.out.println(Thread.currentThread().getName() + "线程执行"), "t2");
Thread t3 = new Thread(() -> System.out.println(Thread.currentThread().getName() + "线程执行"), "t3");
t1.start();
t2.start();
t3.start();
}
}
运行结果:
并不是从上往下的顺序,而是cpu调用那个线程,那个线程就先执行,没有顺序可言,使用join就能够让他们一次执行
创建Thread02类,包名:com.wdp.thread02.test
package com.wdp.thread02.test;
public class Thread02 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ",线程执行");
}, "t1");
Thread t2 = new Thread(() -> {
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ",线程执行");
}, "t2");
Thread t3 = new Thread(() -> {
try {
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ",线程执行");
}, "t3");
t1.start();
t2.start();
t3.start();
}
}
运行结果:
这次就是依次执行的
为什么使用join方法就可以依次执行,下面我们来看看join底层设计原理
join方法底层的设计原理
join(0)默认就是无限制等待
public final void join() throws InterruptedException {
join(0);
}
再点join可以看见源码如下,join的底层就是synchronized和wait()方法封装的,synchronized加载实例方法是this锁
现在我们再来看下图这段代码的含义,当t2线程调用了t1.join,底层就是调用了同步锁synchronized,在锁里面还调用了wait方法,wait方法会主动释放this锁,线程变为阻塞等待状态,t2就无法下往下执行,只有当t1线程执行完毕以后,再来唤醒t2线程,t1线程执行完毕是怎么唤醒t2的,这里源码没有,唤醒的代码在jvm Hotspot源码中,当jvm在关闭线程之前会检测阻塞在t1线程对象上的线程,然后执行notfyAll(),这样t2就被唤醒了。
Thread t1 = new Thread(() -> {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ",线程执行");
}, "t1");
Thread t2 = new Thread(() -> {
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ",线程执行");
}, "t2");
多线程七种执行的状态
初始化状态(也叫新建状态)
就绪状态
运行状态
死亡状态
阻塞状态
超时状态
等待状态
初始化状态:new一个线程的时候就是创建一个线程,就是初始化(新建)状态
就绪状态:当调用start方法的时候,就会进入就绪状态
运行状态:当被cpu调度执行该线程的时候,也就是进入线程的run方法,就是运行状态
死亡状态:线程运行完以后,就会变成死亡状态
阻塞状态:比如同步锁,没有抢到锁就会进入阻塞状态,然后等待拿到锁的线程执行完毕才会重新被唤醒进入就绪状态,等待cpu调用。
超时等待:线程调用了wait() join() slepp()方法的时候,并且设置了时间,这就叫超时状态,时间一到就会重新进入就绪状态,等待cpu调用
等待状态:比如线程调用了wait()方法,没有设置超时时间,线程就会一直等待。
守护线程与用户线程
java中线程分为两种类型:用户线程和守护线程。通过Thread.setDaemon(false)设置为用户线程;通过Thread.setDaemon(true)设置为守护线程。如果不设置次属性,默认为用户线程。
1.守护线程是依赖于用户线程,用户线程退出了,守护线程也就会退出,典型的守护线程如垃圾回收线程。
2.用户线程是独立存在的,不会因为其他用户线程退出而退出。
创建Thread03类,包名:com.wdp.thread02.test
package com.wdp.thread02.test;
public class Thread03 {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + ",我是子线程");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread.setDaemon(false);
thread.start();
System.out.println("我是主线程,代码执行结束");
}
}
多线程yield
主动释放cpu执行权
1.多线程yield 会让线程从运行状态进入到就绪状态,让后调度执行其他线程。
2.具体的实现依赖于底层操作系统的任务调度器
创建Thread04类,包名:com.wdp.thread02.test
package com.wdp.thread02.test;
public class Thread04 extends Thread {
public Thread04(String name) {
super(name);
}
@Override
public void run() {
for (int i=0;i<50;i++){
if(i==30){
System.out.println(Thread.currentThread().getName()+",释放cpu资源");
this.yield();
}
System.out.println(Thread.currentThread().getName() + "," + i);
}
}
public static void main(String[] args) {
new Thread04("wdp01").start();
new Thread04("wdp02").start();
}
}
多线程优先级
1.在java语言中,每个线程都有一个优先级,当线程调控器有机会选择新的线程时,线程的优先级越高越有可能先被选择执行,线程的优先级可以设置1-10,数字越大代表优先级越高
注意:Oracle为Linux提供的java虚拟机中,线程的优先级将被忽略,即所有线程具有相同的优先级。
所以,不要过度依赖优先级。
2.线程的优先级用数字来表示,默认范围是1到10,即Thread.MIN_PRIORITY到Thread.MAX_PRIORTY.一个线程的默认优先级是5,即Thread.NORM_PRIORTY
3.如果cpu非常繁忙时,优先级越高的线程获得更多的时间片,但是cpu空闲时,设置优先级几乎没有任何作用。
创建Thread05类,包名:com.wdp.thread02.test
package com.wdp.thread02.test;
public class Thread05 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
int count = 0;
for (; ; ) {
System.out.println(Thread.currentThread().getName() + "," + count++);
}
}, "t1线程");
Thread t2 = new Thread(() -> {
int count = 0;
for (; ; ) {
System.out.println(Thread.currentThread().getName() + "," + count++);
}
}, "t2线程");
t1.setPriority(Thread.MIN_PRIORITY);
t1.setPriority(Thread.MAX_PRIORITY);
t1.start();
t2.start();
}
}
sleep防止CPU占用100%
sleep(long millis) 线程睡眠 millis 毫秒
sleep(long millis, int nanos) 线程睡眠 millis 毫秒 + nanos 纳秒
比方一个线程一直调用一个死循环,cpu占内存就会非常高,使用了sleep方法后线程会有休眠时间,而不是一直执行
创建SleepThread类,包名:com.wdp.thread02.test
package com.wdp.thread02.test;
public class SleepThread {
public static void main(String[] args) {
new Thread(() -> {
while (true) {
try {
System.out.println("开始执行了");
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
}
死循环没有加上sleep方法,看cpu的内存占比达到67.9%
SleepThread类里加入sleep方法
package com.wdp.thread02.test;
public class SleepThread {
public static void main(String[] args) {
new Thread(() -> {
while (true) {
try {
System.out.println("开始执行了");
Thread.sleep(30);
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
}
再看cpu只有0.4%
如何安全的停止一个线程
调用stop方法
Stop:中止线程,并且清除监控器锁的信息,但是可能导致 线程安全问题,JDK不建议用。 Destroy: JDK未实现该方法。
Interrupt(线程中止)
Interrupt 打断正在运行或者正在阻塞的线程
如果目标线程调用了wait()、wait(long)、wait(long, int)、join()、join(long, int)、sleep(long, int)这些方法,线程就是等待阻塞的状态,现在中止线程也会成功,但是会抛异常报错。
创建Thread06类,包名:com.wdp.thread02.test
package com.wdp.thread02.test;
public class Thread06 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1");
t1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t1.interrupt();
System.out.println("是否等待");
System.out.println("获取打断标记:" +t1.isInterrupted());
}
}
运行结果:
这种方法不推荐使用,代码都没执行完成,强制把线程运行状态变为了死亡状态
interrupt()方法是中断线程的方法,调用这个方法,底层会有一个boolean变量isInterrupted,等于true就可以中断了,就可以自己代码进行逻辑判断了,而不是强制停止了
创建Thread07类,包名:com.wdp.thread02.test
package com.wdp.thread02.test;
public class Thread07 extends Thread {
@Override
public void run() {
while (true){
if(this.isInterrupted()){
System.out.println("中断成功");
break;
}
}
}
public static void main(String[] args) {
Thread07 thread07 = new Thread07();
thread07.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread07.interrupt();
}
}
运行结果:
正确的线程中止-标志位
在上方代码逻辑中,增加一个判断,用来控制线程执行的中止。
创建Thread08,包名:com.wdp.thread02.test
package com.wdp.thread02.test;
import java.util.concurrent.ConcurrentHashMap;
public class Thread08 extends Thread {
private volatile boolean isFlag = true;
@Override
public void run() {
while (isFlag){ //true就一直循环
}
}
public static void main(String[] args) {
Thread08 thread08 = new Thread08();
thread08.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("中断线程");
//设置为false就终止死循环
thread08.isFlag = false;
}
}
运行结果:正常终止,没有报错
Lock锁的基本使用
在jdk1.5后新增的ReentrantLock类同样可达到此效果,且在使用上比synchronized更加灵活
相关API:
使用ReentrantLock实现同步
lock()方法:上锁
unlock()方法:释放锁
使用Condition实现等待/通知 类似于 wait()和notify()及notifyAll()
Lock锁底层基于AQS实现,需要自己封装实现自旋锁。
Synchronized —属于JDK 关键字 底层属于 C++虚拟机底层实现
Lock锁底层基于AQS实现-- 变为重量级
Synchronized 底层原理—锁的升级过程
Lock 过程中 注意 获取锁 释放锁
ReentrantLock用法
创建Thread09,包名:com.wdp.thread02.test
package com.wdp.thread02.test;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Thread09 implements Runnable {
private int count = 100;
private Lock lock = new ReentrantLock();
public static void main(String[] args) {
Thread09 thread09 = new Thread09();
Thread t1 = new Thread(thread09);
Thread t2 = new Thread(thread09);
t1.start();
t2.start();
}
@Override
public void run() {
while (true){
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
lock.lock();
if (count > 1) {
count--;
System.out.println(Thread.currentThread().getName() + "," + count);
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
}
Condition用法
创建Thread10,包名:com.wdp.thread02.test
package com.wdp.thread02.test;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class Thread10 {
private ReentrantLock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public static void main(String[] args) {
Thread10 thread10 = new Thread10();
thread10.print();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread10.signal();
}
private void signal() {
try {
//加锁
lock.lock();
//唤醒线程 相当于notify()
condition.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
private void print() {
new Thread(() -> {
try {
//加锁
lock.lock();
System.out.println(Thread.currentThread().getName() + ",1");
//相当于wait()方法,释放锁并且让当前线程阻塞等待其他线程唤醒
condition.await();
System.out.println(Thread.currentThread().getName() + ",2");
} catch (Exception e) {
e.printStackTrace();
} finally {
//解锁
lock.unlock();
}
}).start();
}
}