进程和线程
进程是所有线程的集合,每一个线程是进程中的一条执行路径。多线程的好处就是为了提高程序的运行效率。
线程状态(State)
线程状态有六种,在java.lang.Thread源码中
public enum State {
NEW, // 新建,线程刚创建创建,还没有启动
RUNNABLE, // 可运行,包含运行状态
BLOCKED, // 阻塞
WAITING, // 无限期等待,需要别的线程去唤醒
TIMED_WAITING, // 限期等待,等待一段时间就继续执行
TERMINATED; // 线程终止状态
}
- 新建(New)
Thread thread = new Thread();
System.out.println(thread.getState()); // NEW
- 可运行(Runnable)
- 这个状态的线程,其正在JVM中执行,但是这个"执行",不一定是真的在运行,也有可能是在等待CPU资源
- 包含了操作系统线程状态中的Running和Ready, Ready表示资源一到可以随时执行,Running表示真正的执行中
Thread thread = new Thread();
thread.start();
System.out.println(thread.getState()); // RUNNABLE
- 阻塞(Blocked)
等待获取一个排它锁,如果其他线程释放了锁就会结束此状态
public class MyThread extends Thread {
private byte[] lock = new byte[0];
public MyThread(byte[] lock) {
this.lock = lock;
}
@Override
public void run() {
synchronized (lock){
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("done");
}
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
byte[] lock = new byte[0]; // 节约资源,只能成三条操作码,Object object = new Object()生成7条操作码
MyThread thread1 = new MyThread(lock);
thread1.start();
MyThread thread2 = new MyThread(lock);
thread2.start();
Thread.sleep(1000); //等一会再检查状态
System.out.println(thread2.getState()); // BLOCKED
}
}
线程1先执行,执行到synchronized 获取了Lock锁,线程2后执行,想要获取Lock锁,获取失败,进入阻塞状态
- 无限期等待(Waiting)
进入到此状态,一定执行了一些代码:
- Object.wait(): 当一个线程执行了Object.wait(), 它一定等待另一个线程执行Object.notify()或Object.notifyAll()进行唤醒,否则一直等待
public class MyThread extends Thread {
private byte[] lock = new byte[0];
public MyThread(byte[] lock) {
this.lock = lock;
}
@Override
public void run() {
synchronized (lock){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
byte[] lock = new byte[0];
MyThread thread1 = new MyThread(lock);
thread1.start();
Thread.sleep(100);
System.out.println(thread1.getState()); // WAITING
synchronized (lock){
lock.notify();
}
Thread.sleep(100);
System.out.println(thread1.getState()); // TERMINATED
}
}
- Thread.join(): 一个线程thread,其在主线程中被执行了thread.join()的时候,主线程即会等待该线程执行完成
public class MyThread extends Thread {
private byte[] lock = new byte[0];
public MyThread(byte[] lock) {
this.lock = lock;
}
@Override
public void run() {
try {
System.out.println("thread1 run");
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class MyThread1 extends Thread {
private Thread thread;
public MyThread1(Thread thread){
this.thread = thread;
}
@Override
public void run() {
try {
thread.join();
System.out.println("thread2 run");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
byte[] lock = new byte[0];
MyThread thread = new MyThread(lock);
thread.start();
MyThread1 thread1 = new MyThread1(thread);
thread1.start();
Thread.sleep(100);
System.out.println(thread1.getState()); // WAITING
}
}
- LockSupport.park(): 当一个线程执行了LockSupport.park()的时候,其在等待执行LockSupport.unpark(thread)
public class MyThread extends Thread {
private byte[] lock = new byte[0];
public MyThread(byte[] lock) {
this.lock = lock;
}
@Override
public void run() {
LockSupport.park();
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
byte[] lock = new byte[0];
MyThread thread = new MyThread(lock);
thread.start();
Thread.sleep(100);
System.out.println(thread.getState()); // WAITING
LockSupport.unpark(thread);
Thread.sleep(100);
System.out.println(thread.getState()); // TERMINATED
}
}
| 进入方法 | 退出方法 |
|---|---|
| 没有设置Timeout参数的Object.wait()方法 | Object.notify()/Object.notifyAll() |
| 没有设置Timeout参数的Thread.join()方法 | 被调用的线程执行完毕 |
| LockSupport.park()方法 | LockSupport.unpark(Thread) |
- 限期等待(Timed Waiting)
无需等待其他线程显示地唤醒,在一定时间之后会被系统自动唤醒。调用Thread.sleep()方法使线程进入限期等待状态时,常常用“使一个线程睡眠”进行描述。调用Object.wait()方法使线程进入限期等待或者无限期等待是,常常用“挂起一个线程”进行描述
- Thread.sleep(long)
public class MyThread3 extends Thread{
@Override
public void run() {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
Thread thread = new MyThread3();
thread.start();
Thread.sleep(100);
System.out.println(thread.getState()); // TIMED_WAITING
}
}
- Object.wait(long)
public class MyThread extends Thread {
private byte[] lock = new byte[0];
public MyThread(byte[] lock) {
this.lock = lock;
}
@Override
public void run() {
synchronized (lock){
try {
// 注意,此处1s之后线程醒来,会重新尝试去获取锁,如果拿不到,后面的代码也不执行
lock.wait(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("lock end");
}
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
byte[] lock = new byte[0];
Thread thread = new MyThread(lock);
thread.start();
Thread.sleep(100);
System.out.println(thread.getState()); // TIMED_WAITING
Thread.sleep(2000);
System.out.println(thread.getState()); // TERMINATED
}
}
- Thread.join(long)
- LockSupport.parkNanos()
- LockSupport.parkUntil()
| 进入方法 | 退出方法 |
|---|---|
| Thread.sleep()方法 | 时间结束 |
| 设置Timeout参数的Object.wait()方法 | 时间结束/Object.notify()/Object.notifyAll() |
| 设置Timeout参数的Thread.join()方法 | 时间结束被调用的线程执行完毕 |
| LockSupport.parkNanos()方法 | LockSupport.unpark(Thread) |
| LockSupport.parkUntil()方法 | LockSupport.unpark(Thread) |
- 死亡(Terminated)
可以是线程结束任务之后自己结束,或者产生了异常而结束
创建线程与运行
- 实现Runnable接口
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("I am a child thread");
}
}
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
- 实现Callable接口,与Runnable相比,Callable可以有返回值,返回值通过FutureTask进行封装
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
Thread.sleep(1000);
return Thread.currentThread().getName();
}
}
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<String> callable = new MyCallable();
FutureTask<String> futureTask = new FutureTask<>(callable);
Thread thread = new Thread(futureTask);
thread.setName("CallableTest");
thread.start();
System.out.println(futureTask.get()); // CallableTest
}
}
- 继承Thread类
public class MyThread extends Thread {
public void run(){
System.out.println("I am a child thread");
}
}
Thread thread = new Thread();
守护线程Daemon
- 守护线程是程序运行时在后台提供服务的线程,不属于程序中不可或缺的部分。
- 当所有非守护线程结束时,线程也就终止,同时会杀死所有守护线程
- 使用setDaemon()方法将一个线程设置为守护线程
sleep()
- 让当前正在执行的线程在指定的时间内暂停执行,进入阻塞状态
sleep()方法不会释放“锁标志”, 如果有synchronized同步块,其他线程仍然不能访问共享数据
wait()、notify()及notifyAll()
- wait() 方法需要和 notify() 及 notifyAll() 两个方法一起介绍,这三个方法用于协调多个线程对共享数据的存取,所以必须在 synchronized 语句块内使用。也就是说,调用 wait(),notify() 和 notifyAll() 的任务在调用这些方法前必须拥有对象的锁。注意,它们都是 Object 类的方法,而不是 Thread 类的方法。
- wait() 方法与 sleep() 方法的不同之处在于,wait()方法会释放对象的“锁标志”。当调用某一对象的 wait() 方法后,会使当前线程暂停执行,并将当前线程放入对象等待池中,直到调用了 notify() 方法后,将从对象等待池中移出任意一个线程并放入锁标志等待池中,只有锁标志等待池中的线程可以获取锁标志,它们随时准备争夺锁的拥有权。当调用了某个对象的 notifyAll() 方法,会将对象等待池中的所有线程都移动到该对象的锁标志等待池。
- 除了使用 notify() 和 notifyAll() 方法,还可以使用带毫秒参数的 wait(long timeout) 方法,效果是在延迟 timeout 毫秒后,被暂停的线程将被恢复到锁标志等待池。
- 此外,wait(),notify() 及 notifyAll() 只能在 synchronized语句中使用,但是如果使用的是ReenTrantLock实现同步,该如何达到这三个方法的效果呢?解决方法是使用 ReenTrantLock.newCondition() 获取一个 Condition 类对象,然后 Condition 的 await(),signal() 以及 signalAll() 分别对应上面的三个方法
yield()
yield() 方法和 sleep() 方法类似,也不会释放“锁标志”,区别在于,它没有参数,即 yield() 方法只是使当前线程重新回到可执行状态,所以执行 yield() 的线程有可能在进入到可执行状态后马上又被执行,另外 yield() 方法只能使同优先级或者高优先级的线程得到执行机会,这也和 sleep() 方法不同。
join()
join() 方法会使当前线程等待调用 join() 方法的线程结束后才能继续执行
- demo t1、t2、t3三个线程顺序执行
public class T1 extends Thread{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 正在执行中...");
}
}
public class T2 extends Thread{
private Thread t1;
public T2(Thread t1) {
this.t1 = t1;
}
@Override
public void run() {
try {
t1.join();
System.out.println(Thread.currentThread().getName() + " 正在执行中...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class T3 extends Thread{
private Thread t2;
public T3(Thread t2) {
this.t2 = t2;
}
@Override
public void run() {
try {
t2.join();
System.out.println(Thread.currentThread().getName() + " 正在执行中...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
T1 t1 = new T1();
t1.setName("t1");
T2 t2 = new T2(t1);
t2.setName("t2");
T3 t3 = new T3(t2);
t3.setName("t3");
t1.start();
t2.start();
t3.start();
// t1 正在执行中...
// t2 正在执行中...
// t3 正在执行中...
}
}
线程不安全问题
经典的卖票问题
public class ThreadTrain implements Runnable{
private int trainCount = 100;
@Override
public void run() {
while (trainCount > 0) {
try {
Thread.sleep(100);
System.out.println(String.format("%s:出售第%s张票", Thread.currentThread().getName(), (100 - trainCount + 1)));
trainCount--;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
ThreadTrain threadTrain1 = new ThreadTrain();
Thread thread1 = new Thread(threadTrain1, "1号窗口");
Thread thread2 = new Thread(threadTrain1, "2号窗口");
thread1.start();
thread2.start();
}
}
1号窗口:出售第1张票
2号窗口:出售第1张票
2号窗口:出售第3张票
1号窗口:出售第3张票
2号窗口:出售第5张票
1号窗口:出售第5张票
2号窗口:出售第7张票
1号窗口:出售第7张票
2号窗口:出售第9张票
1号窗口:出售第9张票
2号窗口:出售第11张票
1号窗口:出售第11张票
1号窗口:出售第13张票
2号窗口:出售第13张票
...
多线程访问同一共享资源产生的问题
解决方法:加锁
线程死锁
线程死锁是指两个及两个以上的线程争夺资源而互相等待的现象。同步中嵌套同步,导致锁无法释放
死锁产生的四个条件
- 互斥条件:该资源同时只能由一个线程占用,其他线程只能等待该线程释放资源
- 请求并持有条件:指一个线程已经持有了至少一个条件,但又提出了新的资源请求,而新资源已经被其他线程占用,所以当前线程会阻塞。
- 不可剥夺条件:指线程获得的资源在使用完之前不能被其他线程抢占
- 环路等待条件:指在发生死锁后,必然存在一个线程一资源的环形链