【并发】Java线程

一. 线程实现

HotSpot中的每个线程都是直接映射到一个操作系统原生线程来实现的,而且中间没有额外的间接结构,所以HotSpot不会去干涉线程调度的(可以设置线程优先级给操作系统提供调度建议,优先级不会严格保证顺序),全权交给底下的操纵系统去处理,所以何时冻结或唤醒线程、该给线程分配多少处理器执行时间、该把线程安排给哪个处理器核心去执行等,都是由操作系统完成的。

二. 状态转换

Java语言定义了6种线程状态,在任意一个时间点中,一个线程只能有且只有其中的一个状态,并且可以通过特定的方法在不同状态之间转换。这6种状态分别是:

  1. 新建(New): 创建后尚未启动的线程处于这种状态。
  2. 运行(Runnable): 包括操作系统线程状态中的Running和Ready,也就是处于此状态的线程有可能正在执行,也有可能正在等待着操作系统为它分配执行时间。
  3. 无限期等待(Waitting): 处于这种状态的线程不会被分配处理器执行时间,要等待被其他线程显式唤醒。以下方法会让线程陷入无限期的等待状态:
  • 没有设置timeout参数的Object::wait()方法
  • 没有设置timeout参数的Thread::join()方法
  • LockSupport::park()方法
  1. 限期等待(Timed Waiting): 处于这种状态的线程也不会被分配处理器执行时间,不过无须等待被其他线程显示唤醒,在一定时间后它们会由系统自动唤醒。以下方法会让线程进入限期等待状态:
  • Thread::sleep()方法
  • 设置了timeout参数的Object::wait()方法
  • 设置了timeout参数的Thread::join()方法
  • LockSupport::parkNanos()方法
  • LockSupport::parkUntil()方法
  1. 阻塞(Blocked): 线程被阻塞了,“阻塞状态”与“等待状态”的区别是“阻塞状态”在等待着获取到一个排他锁,这个事件将在另外一个线程放弃这个锁的时候发生;而“等待状态”则是在等待一段时间或者唤醒动作的发生。在程序等待进入同步区域的时候,线程将进入这种状态。
  2. 结束(Terminated): 已终止线程的线程状态,线程已经结束执行。

上述6种状态在遇到特定事件发生的时候会互相转换,转换关系如下图:

在这里插入图片描述

三. 线程中断

3.1 核心方法

在Java的线程Thread类中有三个方法与线程中断相关,比较容易混淆。

  • this.interrupt():发出一个中断请求,把标志位设定为中断状态,并不会使得线程停止。

  • this.isInterrupted():测试Thread实例对象是否已经是中断状态,但不清除状态标志。

  • Thread.interrupted():Thread类的静态方法,测试当前线程是否已经是中断状态,执行后将状态标志清除,重新置为false。当前进程是指运行代码的进程,例如在main函数中调用,则当前进程即为main线程。

3.2 响应中断

一般来说,阻塞函数,如Thread.sleepThread.joinObject.wait等在检查到线程的中断状态时,会抛出InterruptedException,同时会清除线程的中断状态,即重新设置为false。

synchronized在获锁的过程中是不能被中断的,意思是说如果产生了死锁,则不可能被中断。与synchronized 功能相似的reentrantLock.lock()方法也是一样,它也不响应中断,即如果发生死锁,那么reentrantLock.lock()方法无法终止,如果调用时被阻塞,则它一直阻塞到它获取到锁为止。

但是如果调用带超时的tryLock方法 reentrantLock.tryLock(long timeout, TimeUnit unit),那么如果线程在等待时被中断,将抛出一个 InterruptedException异常,这是一个非常有用的特性,因为它允许程序打破死锁。你也可以调用reentrantLock.lockInterruptibly()方法,它就相当于一个超时设为无限的tryLock方法。

3.3 判定线程是否是中断状态

Thread类提供了两个方法:

  • Thread.interrupted(): 静态方法,检测的是当前线程是否中断。该方法会清除中断标志。
public static boolean interrupted() {
    return currentThread().isInterrupted(true);
}
  • this.isInterrupted(): 实例方法,检测Thread实例对象是否中断。该方法不会清除中断标志。
private native boolean isInterrupted(boolean ClearInterrupted);

下面通过代码示例来深入理解两个方法的区别:

【示例1】

public class MyThread extends Thread {

    private final long start = System.currentTimeMillis();

    @Override
    public void run() {
        while (System.currentTimeMillis() - start <= 2000) {
        }
    }

    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
        thread.interrupt();
        System.out.println("是否停止1:" + thread.interrupted());
        System.out.println("是否停止2:" + thread.interrupted());
    }
}

输出结果为:

//输出为false,因为interrupted检测的是main线程,而不是thread线程。
是否停止1:false
是否停止2:false

将最后的两行代码改为:

System.out.println("是否停止1:" + thread.isInterrupted());
System.out.println("是否停止2:" + thread.isInterrupted());

则输出结果为:

是否停止1:true
//仍为true,证明isInterrupted方法不会清除中断标志
是否停止2:true

【示例2】

public class MyThread extends Thread {

    @Override
    public void run() {
        try {
            sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        try {
            MyThread thread = new MyThread();
            thread.start();
            thread.interrupt();
            Thread.sleep(100);
            System.out.println("是否停止1:" + thread.isInterrupted());
            System.out.println("是否停止2:" + thread.isInterrupted());
        } catch (InterruptedException e) {
        }
    }
}

输出结果为:

是否停止1:false
是否停止2:false

之所以输出结果为false,是因为线程处于可响应中断的阻塞状态时,若被中断会抛出InterruptedException异常,并且会清除掉中断标志。

四. 线程停止

严禁使用stop方法停止线程,原因如下:

  1. 可能使一些清理性的工作无法完成;
  2. 对锁定的对象进行解锁,导致数据得不到同步的处理,出现数据不一致的问题。

一般有两种方式来正确地停止线程:

  • 使用中断加异常捕获的方式
  • 使用volatile标志位的方式
4.1 通过中断停止线程
public class MyThread extends Thread {
    @Override
    public void run() {
        try {
            while (true) {
                if (interrupted()) {
                    System.out.println("线程发生中断");
                    throw new InterruptedException();
                }
            }
        } catch (InterruptedException e) {
            System.out.println("进入MyThread类run方法的catch语句");
        }
    }

    public static void main(String[] args) {
        try {
            MyThread thread = new MyThread();
            thread.start();
            thread.interrupt();
            thread.join();
            System.out.println("end");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

使用中断停止线程要注意以下场景:

public void run() {
    while (!isInterrupted()) {
        try {
            sleep(1000);
        } catch (InterruptedException e) {
            System.out.println("进入MyThread类run方法的catch语句");
        }
    }
}

上述代码发生中断仍旧无法停止,因为线程处于可响应中断的阻塞状态时,若被中断会抛出InterruptedException异常,并且会清除掉中断标志,导致无法跳出while循环。正确的做法是将循环写到try语句内,这样发生中断后就会跳出循环,进入catch语句。

4.2 使用volatile标志位停止线程
public class MyThread extends Thread {

    private volatile boolean stop = false;

    @Override
    public void run() {
        while (true) {
            if (stop) {
                System.out.println("线程停止");
                return;
            }
        }
    }

    public static void main(String[] args) {
        try {
            MyThread thread = new MyThread();
            thread.start();
            Thread.sleep(1000);
            thread.stop = true;
            thread.join();
            System.out.println("end");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

五. 暂停及恢复

suspendresume方法也已废弃,主要是容易造成公共的同步对象的独占,使得其他线程无法访问公共同步对象。因为suspend并不会使得线程释放锁,容易造成死锁,同时也容易造成数据的不同步。

例如以下例子,线程thread1修改了变量name之后调用suspend,导致变量pwd未被改变,此时线程thread2将读取到name=a,pwd=11的错误数据。

public class MyObject {
    private String name = "1";
    private String pwd = "11";

    public void setValue(String name, String pwd) {
        this.name = name;
        if (Thread.currentThread().getName().equals("a")) {
            System.out.println("停止a线程");
            Thread.currentThread().suspend();
        }
        this.pwd = pwd;
    }

    public void print() {
        System.out.println(name + " " + pwd);
    }
}
 public static void main(String[] args) throws InterruptedException {
    final MyObject myObject = new MyObject();
    Thread thread1 = new Thread(() -> myObject.setValue("a", "aa"));
    thread1.setName("a");
    thread1.start();
    Thread.sleep(500);
    Thread thread2 = new Thread(() -> myObject.print());
    thread2.start();
}

六. 线程通信

6.1 wait/notify机制

wait()方法是Object类的方法,在调用之前需要获得++所在对象++的对象锁,即需要在synchronized方法或synchronized语句块中调用,否则会抛出IllegalMonitorStateException异常。在执行wait()方法之后,当前线程会释放锁,并进入等待状态,当被其他线程唤醒后,仍然需要重新获得锁才能往下执行。

notify()方法也需要获得所在对象的对象锁,否则也会抛出IllegalMonitorStateException异常。如果有多个线程等待,则挑选出其中一个呈wait状态的线程,对其发出通知notify。需要注意的是,notify方法并不会释放锁,需要退出synchronized代码后线程才会释放锁,而被唤醒的线程此时需要重新获取锁,才能执行剩余代码逻辑。

代码示例如下:

public class ThreadA extends Thread {

    private Object lock;

    public ThreadA(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized (lock) {
            try {
                System.out.println(getName() + "开始等待:" + System.currentTimeMillis());
                lock.wait();
                System.out.println(getName() + "结束等待:" + System.currentTimeMillis());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class ThreadB extends Thread {

    private Object lock;

    public ThreadB(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized (lock) {
            try {
                System.out.println(getName() + "开始等待:" + System.currentTimeMillis());
                lock.notify();
                sleep(5000);
                System.out.println(getName() + "结束等待:" + System.currentTimeMillis());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class App {
    public static void main(String[] args) {
        try {
            Object lock = new Object();
            ThreadA threadA = new ThreadA(lock);
            threadA.setName("ThreadA");
            threadA.start();
            Thread.sleep(100);
            ThreadB threadB = new ThreadB(lock);
            threadB.setName("ThreadB");
            threadB.start();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

结果输出如下:

ThreadA开始等待:1609056243352
ThreadB开始等待:1609056243456
ThreadB结束等待:1609056248461
ThreadA结束等待:1609056248462
6.2 Lock的Condition机制

关键字synchronizedwait/notify/notifyAll方法结合可以实现等待/通知功能,而Lock结合Condition对象也可以实现等待/通知模型。一个Lock对象里面可以创建多个Condition对象,可以实现“选择性通知”。

wait/notify类似的是,调用Condition对象的await方法之前,需要通过Lock对象的lock方法获得锁,否则也会抛出IllegalMonitorStateException异常。

简单代码示例如下:

public class MyService {
    private Lock lock = new ReentrantLock();
    private Condition conditionA = lock.newCondition();
    private Condition conditionB = lock.newCondition();

    public void awaitA() {
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName() + "开始等待A:" + System.currentTimeMillis());
            conditionA.await();
            System.out.println(Thread.currentThread().getName() + "结束等待A:" + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void signalA() {
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName() + "开始唤醒A:" + System.currentTimeMillis());
            conditionA.signal();
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + "结束唤醒A:" + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void awaitB() {
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName() + "开始等待B:" + System.currentTimeMillis());
            conditionB.await();
            System.out.println(Thread.currentThread().getName() + "结束等待B:" + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void signalB() {
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName() + "开始唤醒B:" + System.currentTimeMillis());
            conditionB.signal();
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + "结束唤醒B:" + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

public class ThreadA extends Thread {

    private MyService service;

    public ThreadA(MyService service) {
        this.service = service;
    }

    @Override
    public void run() {
        service.awaitA();
    }
}

public class ThreadB extends Thread {

    private MyService service;

    public ThreadB(MyService service) {
        this.service = service;
    }

    @Override
    public void run() {
        service.awaitB();
    }
}

public class App {
    public static void main(String[] args) {
        try {
            MyService service = new MyService();
            ThreadA threadA = new ThreadA(service);
            threadA.setName("ThreadA");
            threadA.start();
            ThreadB threadB = new ThreadB(service);
            threadB.setName("ThreadB");
            threadB.start();
            Thread.sleep(100);
            service.signalA();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

输出结果如下:

ThreadB开始等待B:1609058045382
main开始唤醒A:1609058045488
main结束唤醒A:1609058046489
ThreadA结束等待A:1609058046490
//ThreadB未被唤醒,等待中...

综上所述,Condition对象的await/signal方法的使用与Object对象的wait/notify方法的使用是相当类似的,区别在于一个Lock对象可以创建多个Condition对象,从而达到“精确控制”的功能,而Object对象相当于只有一个Condtion对象的情况。

七. 参考资料

  • 《深入理解Java虚拟机第三版》
  • 《Java多线程编程核心技术》
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值