进程与线程
- 进程是系统中正在运行的一个程序
- 线程是操作系统能够进行运算调度的最小单位
线程和进程有什么区别
- 线程是进程的子集,一个进程可以有很多线程
- 每个进程都有自己的内存空间,可执行代码和唯一进程标识符
- 不同的进程使用不同的内存空间(进程自己的堆栈),而所有的线程共享一片相同的内存空间
Java如何创建线程
- 继承Thread类
- 实现Runnable接口并重写run()方法
- 实现Callable接口,重写call(),利用FutureTask包装Callable,并作为task传入Thread构造函数
- 利用线程池
继承Thread类
package javaThread;
class FirstThread extends Thread {
public void run() {
System.out.println("First thread");
}
}
public class Main {
public static void main(String[] args) {
Thread t1 = new FirstThread();
t1.start();
}
}
实现Runnable接口并重写run()方法
package javaThread;
class SecondThread implements Runnable {
@Override
public void run() {
System.out.println("Second thread");
}
}
public class Main {
public static void main(String[] args) {
Thread t2 = new Thread(new SecondThread());
t2.start();
}
}
实现Callable接口,重写call(),利用FutureTask包装Callable,并作为task传入Thread构造函数
package javaThread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThirdThread {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 1:使用FutureTask + runnable
FutureTask<String> runnableTask = new FutureTask<String>(new Runnable() {
@Override
public void run() {
int r = 0;
for (int i = 0; i < 100; i++) {
r += I;
}
}
}, "runnable task complete");
Thread t1 = new Thread(runnableTask);
t1.start();
String result1 = runnableTask.get();
// 获取runnable task完成后通知,返回自己传入的值(runnable task complete)
System.out.println(result1);
// 2:使用FutureTask + callable
FutureTask<Integer> callableTask = new FutureTask<Integer>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int r = 0;
for (int i = 0; i < 100; i++) {
r += I;
}
return r;
}
});
Thread t2 = new Thread(callableTask);
t2.start();
// 获取callable task的返回值,返回计算的结果(4950)
int result2 = callableTask.get();
System.out.println("callable task complete, result is:" + result2);
}
}
利用线程池
package javaThread;
import java.util.concurrent.*;
public class FourthThread {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ThreadPoolExecutor pool = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(20));
// 1:向线程池提交一个runnable任务
Future<String> f = pool.submit(new Runnable() {
@Override
public void run() {
int r = 0;
for (int i = 0; i < 100; i++) {
r += I;
}
}
}, "runnable task complete");
// 获取runnable任务完成后通知,返回自己传入的值(runnable task complete)
System.out.println(f.get());
// 2:向线程池提交一个callable任务
Future<Integer> t = pool.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int r = 0;
for (int i = 0; i < 100; i++) {
r += I;
}
return r;
}
});
// 获取callable任务的返回值,返回计算的结果(4950)
int result = (int) t.get();
System.out.println("callable task complete, result is:" + result);
pool.shutdown(); // 关闭线程池
}
}
启动线程
- 调用线程的对象的start()方法
start()和run()区别
- start()方法被用来启动新创建的线程,内部调用了run()方法
- 调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启动,start()方法才会启动新线程
线程协作通信的方式(阻塞和唤醒)
- suspend/resume(已经被弃用)
- wait/notify
- park/unpark
suspend/resume
线程被挂起以后不会释放锁,有顺序调用的问题.容易死锁
- 情况1:suspend挂起后没有释放锁
package threadCommunication;
/**
* 死锁情况1
* suspend挂起的时候不会释放锁
* 当线程suspend的时候持有锁,而resume的时候需要同一把锁的情况下,
* 导致resume无法获取锁,从而无法唤醒线程,产生死锁
*/
public class SuspendResume {
public static Object cake = null;
public void suspendResumeDeadLock() throws InterruptedException {
Thread consumerThread = new Thread(() -> {
while (cake == null) { //使用while防止伪唤醒,而不是if
System.out.println("1、没有蛋糕了,消费者进入等待");
// 当前线程拿到锁,然后挂起
synchronized (this) {
Thread.currentThread().suspend();
}
}
System.out.println("2、买到蛋糕,回家");
});
consumerThread.start();
// 2秒后生产一个蛋糕
Thread.sleep(2000L);
cake = new Object();
// 争取到锁以后,再恢复consumerThread
synchronized (this) {
consumerThread.resume();
}
System.out.println("3、通知消费者");
}
public static void main(String[] args) throws InterruptedException {
new SuspendResume().deadLock();
}
}
- 情况2: resume比suspend先执行
package threadCommunication;
/**
* 死锁情况2
* 如果线程在suspend之前执行了一个耗时操作,导致程序先执行了resume然后再执行suspend。
* 那么这个suspend后面将没有对应的resume将它唤醒,程序将一直处于挂起状态
*/
public class SuspendResume {
public static Object cake = null;
public void deadLock() throws InterruptedException {
Thread consumerThread = new Thread(() -> {
while (cake == null) { //使用while防止伪唤醒,而不是if
System.out.println("1、没有蛋糕了,消费者进入等待");
// 模拟一个耗时操作
try {
Thread.sleep(5000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread.currentThread().suspend();
}
System.out.println("2、买到蛋糕,回家");
});
consumerThread.start()
// 2秒后生产一个蛋糕
Thread.sleep(2000L);
cake = new Object();
Thread.currentThread().resume();
System.out.println("3、通知消费者");
}
public static void main(String[] args) throws InterruptedException {
new SuspendResume().deadLock();
}
}
wait/notify
wait方法导致当前线程等待,加入该对象的等待集合中,并且放弃当前对象持有的锁
notify/notifyAll唤醒一个或者所有等待这个对象锁的线程
必须在同步代码块里调用
wait自动解锁但是对调用顺序由要求,如果在notify调用之后才调用wait,线程将永远处于WAITING状态,导致死锁
package threadCommunication;
/**
* notify调用之后才调用wait,线程永远处于WAITING状态.导致死锁
*/
public class WaitNotify {
public static Object cake = null;
public void deadLock() throws InterruptedException {
new Thread(() -> {
while (cake == null) { //使用while防止伪唤醒,而不是if
// 模拟一个耗时操作
try {
Thread.sleep(5000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (this) {
System.out.println("1、没有蛋糕了,消费者进入等待");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println("2、买到蛋糕,回家");
}).start();
// 2秒后生产一个蛋糕
Thread.sleep(2000L);
cake = new Object();
synchronized (this) {
this.notifyAll();
System.out.println("3、通知消费者");
}
}
public static void main(String[] args) throws InterruptedException {
new WaitNotify().deadLock();
}
}
park/unpark
许可机制,不会释放锁
park方法等待“许可”,许可存在则运行
unpark方法为指定的线程提供“许可”
不要求park和unpark方法的调用顺序.
多次调用unpark之后,再调用park,线程会直接运行,但不会叠加.
package threadCommunication;
import java.util.concurrent.locks.LockSupport;
/**
* park/unpark死锁
*
*/
public class ParkUnpark {
public static Object cake = null;
public void deadLock() throws InterruptedException {
// 启动线程
Thread consumerThread = new Thread(() -> {
while (cake == null) { //使用while防止伪唤醒,而不是if
System.out.println("1、没有蛋糕了,消费者进入等待");
// 当前线程拿到锁,然后挂起
synchronized (this) {
LockSupport.park();
}
}
System.out.println("2、买到蛋糕,回家");
});
consumerThread.start();
// 2秒之后,生产一个蛋糕
Thread.sleep(2000L);
cake = new Object();
// 争取到锁以后,再恢复consumerThread
synchronized (this) {
LockSupport.unpark(consumerThread);
}
System.out.println("3、通知消费者");
}
public static void main(String[] args) throws InterruptedException {
new ParkUnpark().deadLock();
}
}
三种方式的比较
方式 | 是否释放锁 | 是否有顺序要求 |
---|---|---|
suspend/resume | 不释放 | 有 |
wait/notify | 释放 | 有 |
park/unpark | 不释放 | 没有 |
线程中断
中断可以理解为线程的一个标识位属性,它表示一个运行中的线程是否被其他线程进行了中断操作
使用interrupt()方法中断线程
当阻塞方法收到中断请求的时候就会抛出InterruptedException异常.
方法在抛出InterruptedException之前,Java虚拟机会先将该线程的中断标识位 清除,然后抛出InterruptedException,此时调用isInterrupted()方法将会返回false
interrupt()和isInterrupted()
- interrupt中断目标线程,interrupt并不意味着必然停止目标线程正在进行的工作,它仅仅是传递了请求中断的消息,线程自己会在下一个方便的时刻中断
- isInterrupted()判断线程是否中断, 它会清除当前线程的中断状态,并返回之前的值
结束线程
- 使用stop()强行终止线程(被弃用,有线程安全问题)
- 使用interrupt()中断线程
- 使用退出标志使线程正常退出
使用stop()强行终止线程
package javaThread;
/**
* stop()终止线程,线程安全性问题
*
*/
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);
}
}
public class StopThreadTest {
public static void main(String[] args) throws InterruptedException {
StopThread thread = new StopThread();
thread.start();
// 休眠1秒,确保i变量自增成功
Thread.sleep(1000);
thread.stop(); // 错误的终止
while (thread.isAlive()) {
// 确保线程已经终止
} // 输出结果
thread.print();
}
}
执行结果:
i=1 j=0
使用interrupt()中断线程
package javaThread;
/**
* 使用interrupt()正确中断线程
*/
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);
}
}
public class StopThreadTest {
public static void main(String[] args) throws InterruptedException {
StopThread thread = new StopThread();
thread.start();
// 休眠1秒,确保i变量自增成功
Thread.sleep(1000);
thread.interrupt(); // 正确终止
while (thread.isAlive()) {
// 确保线程已经终止
} // 输出结果
thread.print();
}
}
执行结果:
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at javaThread.StopThread.run(stopThreadTest.java:16)
i=1 j=1
使用退出标志使线程正常退出
package javaThread;
/**
* 使用退出标志使线程正常退出
*/
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);
}
}
public class StopThreadTest {
public volatile static boolean flag = true;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
try {
while (flag) { // 判断是否运行
System.out.println("运行中");
Thread.sleep(1000L);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
// 3秒之后,将状态标志改为False,代表不继续运行
Thread.sleep(3000L);
flag = false;
System.out.println("程序运行结束");
}
}
执行结果:
运行中
运行中
运行中
程序运行结束
线程状态
线程状态定义
- New: 尚未启动的线程的线程状态
- Runnable: 可运行线程的线程状态,等待CPU调度
- Block: 线程阻塞等待监视器锁定的线程状态
- Wait: 等待线程的线程状态
- Timed Waiting: 具有指定等待时间的等待线程的线程下状态
- Terminated: 终止线程的线程状态,线程正常完成执行或者出现异常
线程状态切换

package javaThread;
/**
* 第一种状态切换: 新建 -> 运行 -> 终止
*/
public class threadStatus {
public static void main(String[] args) throws InterruptedException {
System.out.println("第一种状态切换: 新建 -> 运行 -> 终止");
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程当前状态:" + Thread.currentThread().getState().toString());
System.out.println("线程执行了");
}
});
System.out.println("没调用start方法,线程当前状态:" + t.getState().toString());
t.start();
Thread.sleep(2000L); // 等待线程执行结束,再看状态
System.out.println("等待两秒,再看线程当前状态:" + t.getState().toString());
}
}
执行结果:
第一种状态切换: 新建 -> 运行 -> 终止
没调用start方法,线程当前状态:NEW
线程当前状态:RUNNABLE
线程执行了
等待两秒,再看线程当前状态:TERMINATED
package javaThread;
/**
* 第二种状态切换:新建 -> 运行 -> 等待 -> 运行 -> 终止
*/
public class ThreadStatus {
public static void main(String[] args) throws InterruptedException {
System.out.println("第二种状态切换:新建 -> 运行 -> 等待 -> 运行 -> 终止(sleep方式)");
Thread t = new Thread(new Runnable() {
@Override
public void run() {
try {// 将线程2移动到等待状态,1500后自动唤醒
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程当前状态:" + Thread.currentThread().getState().toString());
System.out.println("线程执行了");
}
});
System.out.println("没调用start方法,线程当前状态:" + t.getState().toString());
t.start();
System.out.println("调用start方法,线程当前状态:" + t.getState().toString());
Thread.sleep(200L); // 等待200毫秒,再看状态
System.out.println("等待200毫秒,再看线程当前状态:" + t.getState().toString());
Thread.sleep(3000L); // 再等待3秒,让线程执行完毕,再看状态
System.out.println("等待3秒,再看线程当前状态:" + t.getState().toString());
}
}
执行结果:
第二种状态切换:新建 -> 运行 -> 等待 -> 运行 -> 终止(sleep方式)
没调用start方法,线程当前状态:NEW
调用start方法,线程当前状态:RUNNABLE
等待200毫秒,再看线程当前状态:TIMED_WAITING
线程当前状态:RUNNABLE
线程执行了
等待3秒,再看线程当前状态:TERMINATED
package javaThread;
/**
* 第三种状态切换:新建 -> 运行 -> 阻塞 -> 运行 -> 终止
*/
public class ThreadStatus {
public static void main(String[] args) throws InterruptedException {
System.out.println("第三种状态切换:新建 -> 运行 -> 阻塞 -> 运行 -> 终止");
Thread t = new Thread(new Runnable() {
@Override
public void run() {
synchronized (ThreadStatus.class) {
System.out.println("线程当前状态:" + Thread.currentThread().getState().toString());
System.out.println("线程执行了");
}
}
});
synchronized (threadStatus.class) {
System.out.println("没调用start方法,线程当前状态:" + t.getState().toString());
t.start();
System.out.println("调用start方法,线程当前状态:" + t.getState().toString());
Thread.sleep(200L); // 等待200毫秒,再看状态
System.out.println("等待200毫秒,再看线程当前状态:" + t.getState().toString());
}
Thread.sleep(3000L); // 再等待3秒,让线程执行完毕,再看状态
System.out.println("等待3秒,让线程抢到锁,再看线程当前状态:" + t.getState().toString());
}
}
执行结果:
第三种状态切换:新建 -> 运行 -> 阻塞 -> 运行 -> 终止
没调用start方法,线程当前状态:NEW
调用start方法,线程当前状态:RUNNABLE
等待200毫秒,再看线程当前状态:BLOCKED
线程当前状态:RUNNABLE
线程执行了
等待3秒,让线程抢到锁,再看线程当前状态:TERMINATED
多线程的优缺点
优点
- 能利用更多的处理器核心
- 更快的响应时间
- 更好的编程模型
缺点
- 活跃度风险(死锁,活锁,饥饿)
- 性能风险:不一定更快(上下文切换,内存同步,阻塞)
网友评论