一、Java多线程编程并发编程
第一节 Java基础
线程状态
Java线程有6个状态定义:java.lang.Thread.State
- New:尚未启动的线程的线程状态;
- Runnable:可运行线程的线程状态,等待CPU调度;
- Blocked: 线程阻塞等待监视器锁定的线程状态,处于synchronized同步代码块或方法中被阻塞;
- Waiting:等待线程的线程状态,下列不带超时的方式:Object.wait、Thread.join、LockSupport.park;
- Timed Waiting:具有指定等待时间的等待线程的线程状态。带超时的方式:Thread.sleep、Object.wait、Thread.join、LockSupport.parkNanos、LockSupport.parkUntil;
- Terminated :终止线程的线程状态。线程正常完成执行或者出现异常。
状态流程图:
具体状态可以运行如下Demo进行简单的了解
/**
* 示例2 - 多线程运行状态切换示例 <br/>
*/
public class Demo1 {
public static Thread thread1;
public static Demo1 obj;
public static void main(String[] args) throws Exception {
// 第一种状态切换 - 新建 -> 运行 -> 终止
System.out.println("#######第一种状态切换 - 新建 -> 运行 -> 终止################################");
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread1当前状态:" + Thread.currentThread().getState().toString());
System.out.println("thread1 执行了");
}
});
System.out.println("没调用start方法,thread1当前状态:" + thread1.getState().toString());
thread1.start();
Thread.sleep(2000L); // 等待thread1执行结束,再看状态
System.out.println("等待两秒,再看thread1当前状态:" + thread1.getState().toString());
// thread1.start(); TODO 注意,线程终止之后,再进行调用,会抛出IllegalThreadStateException异常
System.out.println();
System.out.println("############第二种:新建 -> 运行 -> 等待 -> 运行 -> 终止(sleep方式)###########################");
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {// 将线程2移动到等待状态,1500后自动唤醒
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread2当前状态:" + Thread.currentThread().getState().toString());
System.out.println("thread2 执行了");
}
});
System.out.println("没调用start方法,thread2当前状态:" + thread2.getState().toString());
thread2.start();
System.out.println("调用start方法,thread2当前状态:" + thread2.getState().toString());
Thread.sleep(200L); // 等待200毫秒,再看状态
System.out.println("等待200毫秒,再看thread2当前状态:" + thread2.getState().toString());
Thread.sleep(3000L); // 再等待3秒,让thread2执行完毕,再看状态
System.out.println("等待3秒,再看thread2当前状态:" + thread2.getState().toString());
System.out.println();
System.out.println("############第三种:新建 -> 运行 -> 阻塞 -> 运行 -> 终止###########################");
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (Demo2.class) {
System.out.println("thread3当前状态:" + Thread.currentThread().getState().toString());
System.out.println("thread3 执行了");
}
}
});
synchronized (Demo2.class) {
System.out.println("没调用start方法,thread3当前状态:" + thread3.getState().toString());
thread3.start();
System.out.println("调用start方法,thread3当前状态:" + thread3.getState().toString());
Thread.sleep(200L); // 等待200毫秒,再看状态
System.out.println("等待200毫秒,再看thread3当前状态:" + thread3.getState().toString());
}
Thread.sleep(3000L); // 再等待3秒,让thread3执行完毕,再看状态
System.out.println("等待3秒,让thread3抢到锁,再看thread3当前状态:" + thread3.getState().toString());
}
}
运行结果:
#######第一种状态切换 - 新建 -> 运行 -> 终止################################
没调用start方法,thread1当前状态:NEW
thread1当前状态:RUNNABLE
thread1 执行了
等待两秒,再看thread1当前状态:TERMINATED
############第二种:新建 -> 运行 -> 等待 -> 运行 -> 终止(sleep方式)###########################
没调用start方法,thread2当前状态:NEW
调用start方法,thread2当前状态:RUNNABLE
等待200毫秒,再看thread2当前状态:TIMED_WAITING
thread2当前状态:RUNNABLE
thread2 执行了
等待3秒,再看thread2当前状态:TERMINATED
############第三种:新建 -> 运行 -> 阻塞 -> 运行 -> 终止###########################
没调用start方法,thread3当前状态:NEW
调用start方法,thread3当前状态:RUNNABLE
等待200毫秒,再看thread3当前状态:BLOCKED
thread3当前状态:RUNNABLE
thread3 执行了
等待3秒,让thread3抢到锁,再看thread3当前状态:TERMINATED
线程中止
如何中止一个线程:
1、不正确的线程中止-Stop
a、Stop:中止线程,并且清除监控器锁的信息,但是可能导致线程安全,JDK不建议用。
b、Destroy:JDK未实现该方法。
线程不安全演示:
public class StopThread extends Thread {
private int i = 0, j = 0;
@Override
public void run() {
synchronized (this) {
// 增加同步锁,确保线程安全
++i;
try {
// 休眠10秒,模拟耗时操作
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
++j;
}
}
/** * 打印i和j */
public void print() {
System.out.println("i=" + i + " j=" + j);
}
}
/**
* 示例3 - 线程stop强制性中止,破坏线程安全的示例
*/
public class Demo3 {
public static void main(String[] args) throws InterruptedException {
StopThread thread = new StopThread();
thread.start();
// 休眠1秒,确保i变量自增成功
Thread.sleep(1000);
// 暂停线程
thread.stop(); // 错误的终止
// thread.interrupt(); // 正确终止
while (thread.isAlive()) {
// 确保线程已经终止
} // 输出结果
thread.print();
}
}
运行结果:
thread.stop()方式中止线程,会有线程安全的问题,导致实际运行结果:i=1 j=0,线程安全的前提下:i=1 j=1。
thread.interrupt()中止线程,程序会以抛出java.lang.InterruptedException异常来通知线程中止,捕捉该异常,当前线程会执行完后续操作,保证线程的安全性。
正确中止线程的方法:interrupt
二、通过标志位来中止线程
代码逻辑中,增加一个判断,用来控制线程执行的中止。
/** 通过状态位来判断 */
public class Demo4 extends Thread {
public volatile static boolean flag = true;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
while (flag) { // 判断是否运行
System.out.println("运行中");
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}).start();
// 3秒之后,将状态标志改为False,代表不继续运行
Thread.sleep(3000L);
flag = false;
System.out.println("程序运行结束");
}
}
线程通信
1、文件共享
线程1将数据写入到文件系统,线程2可以通过读取该文件实现数据的共享
2、变量共享
利用内存的公共区域。如:定义共享变量,线程1对变量写入数据,线程2来读取数据。
3、线程协作–JDK API
JDK中对于需要多线程协作完成某一任务的场景,提供了对应API支持。
多线程协作的典型场景是:生产者 - 消费者模型。(线程阻塞
线程唤醒)
示例:线程1去买包子,没有包子,则不再执行。线程2生产出包子,通知线程1继续执行。
- suspend和resume
- wait和notify
- park和unpark
区别:
第一种容易产生死锁:
1、同步代码块中使用,由于suspend挂起后并不会释放锁,容易造成死锁;
2、执行顺序也可能造成死锁,suspend比resume后执行的情形;
这两个API已废弃。
/** 死锁的suspend/resume。 suspend并不会像wait一样释放锁,故此容易写出死锁代码 */
public void suspendResumeDeadLockTest() throws Exception {
// 启动线程
Thread consumerThread = new Thread(() -> {
if (baozidian == null) { // 如果没包子,则进入等待
System.out.println("1、进入等待");
// 当前线程拿到锁,然后挂起
synchronized (this) {
Thread.currentThread().suspend();
}
}
System.out.println("2、买到包子,回家");
});
consumerThread.start();
// 3秒之后,生产一个包子
Thread.sleep(3000L);
baozidian = new Object();
// 争取到锁以后,再恢复consumerThread
synchronized (this) {
consumerThread.resume();
}
System.out.println("3、通知消费者");
}
/** 导致程序永久挂起的suspend/resume */
public void suspendResumeDeadLockTest2() throws Exception {
// 启动线程
Thread consumerThread = new Thread(() -> {
if (baozidian == null) {
System.out.println("1、没包子,进入等待");
try { // 为这个线程加上一点延时
Thread.sleep(5000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 这里的挂起执行在resume后面
Thread.currentThread().suspend();
}
System.out.println("2、买到包子,回家");
});
consumerThread.start();
// 3秒之后,生产一个包子
Thread.sleep(3000L);
baozidian = new Object();
consumerThread.resume();
System.out.println("3、通知消费者");
consumerThread.join();
}
第二种wait和notify这些方法只能由同一对象锁的持有者线程调用,也就是写在同步代码块里面,否则会抛出IllegalMonitorStateException异常。
wait方法会将当前线程等待,加入该对象的等待集合中,并且放弃当前持有的对象锁。
notify/notifyAll方法会唤醒一个或所有正在等待这个对象锁的线程。
如果在notify被调用之后,才开始wait方法的调用,线程会永远处于waiting状态。
/** 正常的wait/notify */
public void waitNotifyTest() throws Exception {
// 对象中增加一个wait线程
new Thread(()->{
synchronized (this) {
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("我也被唤醒了");
}
}).start();
// 启动线程
new Thread(() -> {
synchronized (this) {
while (baozidian == null) { // 如果没包子,则进入等待
try {
System.out.println("1、进入等待");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println("2、买到包子,回家");
}).start();
// 3秒之后,生产一个包子
Thread.sleep(3000L);
baozidian = new Object();
synchronized (this) {
this.notifyAll();
System.out.println("3、通知消费者");
}
}
/** 会导致程序永久等待的wait/notify */
public void waitNotifyDeadLockTest() throws Exception {
// 启动线程
new Thread(() -> {
if (baozidian == null) { // 如果没包子,则进入等待
try {
Thread.sleep(5000L);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
synchronized (this) {
try {
System.out.println("1、进入等待");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println("2、买到包子,回家");
}).start();
// 3秒之后,生产一个包子
Thread.sleep(3000L);
baozidian = new Object();
synchronized (this) {
this.notifyAll();
System.out.println("3、通知消费者");
}
}
第三种
1、unpark先执行唤醒,线程中的park后执行,该线程还能继续被唤醒,不会有先后顺序的问题;
2、同步代码块中不会释放锁
/** 正常的park/unpark */
public void parkUnparkTest() throws Exception {
// 启动线程
Thread consumerThread = new Thread(() -> {
while (baozidian == null) { // 如果没包子,则进入等待
System.out.println("1、进入等待");
try {
// 模拟业务场景,主线程中的unpark先执行唤醒,线程中的park后执行,该线程还能继续被唤醒
Thread.sleep(6000L);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
LockSupport.park();
}
System.out.println("2、买到包子,回家");
});
consumerThread.start();
// 3秒之后,生产一个包子
Thread.sleep(3000L);
baozidian = new Object();
LockSupport.unpark(consumerThread);
System.out.println("3、通知消费者");
}
/** 死锁的park/unpark */
public void parkUnparkDeadLockTest() throws Exception {
// 启动线程
Thread consumerThread = new Thread(() -> {
if (baozidian == null) { // 如果没包子,则进入等待
System.out.println("1、进入等待");
// 当前线程拿到锁,然后挂起
synchronized (this) {
LockSupport.park();
}
}
System.out.println("2、买到包子,回家");
});
consumerThread.start();
// 3秒之后,生产一个包子
Thread.sleep(3000L);
baozidian = new Object();
// 争取到锁以后,再恢复consumerThread
synchronized (this) {
LockSupport.unpark(consumerThread);
}
System.out.println("3、通知消费者");
}