Java多线程

184 阅读5分钟

进程和线程

进程是所有线程的集合,每一个线程是进程中的一条执行路径。多线程的好处就是为了提高程序的运行效率。

线程状态(State)

线程状态有六种,在java.lang.Thread源码中

public enum State {
    NEW, // 新建,线程刚创建创建,还没有启动
    RUNNABLE, // 可运行,包含运行状态
    BLOCKED, // 阻塞
    WAITING, // 无限期等待,需要别的线程去唤醒
    TIMED_WAITING, // 限期等待,等待一段时间就继续执行
    TERMINATED;  // 线程终止状态
}

  1. 新建(New)
Thread thread = new Thread();
System.out.println(thread.getState()); // NEW
  1. 可运行(Runnable)
  • 这个状态的线程,其正在JVM中执行,但是这个"执行",不一定是真的在运行,也有可能是在等待CPU资源
  • 包含了操作系统线程状态中的Running和Ready, Ready表示资源一到可以随时执行,Running表示真正的执行中
Thread thread = new Thread();
thread.start();
System.out.println(thread.getState()); // RUNNABLE
  1. 阻塞(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锁,获取失败,进入阻塞状态

  1. 无限期等待(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)
  1. 限期等待(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)
  1. 死亡(Terminated)
    可以是线程结束任务之后自己结束,或者产生了异常而结束

创建线程与运行

  1. 实现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);
  1. 实现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
    }
}
  1. 继承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张票
...

多线程访问同一共享资源产生的问题

解决方法:加锁

线程死锁

线程死锁是指两个及两个以上的线程争夺资源而互相等待的现象。同步中嵌套同步,导致锁无法释放

死锁产生的四个条件

  1. 互斥条件:该资源同时只能由一个线程占用,其他线程只能等待该线程释放资源
  2. 请求并持有条件:指一个线程已经持有了至少一个条件,但又提出了新的资源请求,而新资源已经被其他线程占用,所以当前线程会阻塞。
  3. 不可剥夺条件:指线程获得的资源在使用完之前不能被其他线程抢占
  4. 环路等待条件:指在发生死锁后,必然存在一个线程一资源的环形链