栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

浅析java多线程(二)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

浅析java多线程(二)

1.线程的状态

下面说的线程状态指的是java虚拟机中的线程状态,并不是线程在操作系统中的状态。
新建
线程创建后还未启动

运行
在java虚拟机中运行,但是在操作系统层面可能在运行,也可能在等待资源调度,具体有没有运行看操作系统的资源调度

阻塞
线程等待获取锁,锁还没有获得

无限期等待
线程需要等待其它线程显示地唤醒。
等待和阻塞的区别在于等待是主动的,而阻塞是被动的(被动等待获取锁),等待是线程自己调用了wait()等方法进入的。

有限期等待
线程无需等待其他线程显示唤醒,在一定时间后会被系统自动唤醒。
调用sleep(),设置了Timeout参数的wait()方法,以及设置了Timeout参数的join()方法线程就进入了有限期等待状态。

结束
线程结束了任务,或者产生异常就进入了结束状态。

2.线程中断操作

线程执行完毕之后会自动结束,如果在运行过程中发生异常会提前结束。
2.1 InterruptedException和interrupt()
通过调用一个线程的interrupt()来中断该线程,如果该线程处于阻塞、限期等待或者无限期等待状态,那么就抛出InterruptedException,从而提前结束该线程。但是不能中断I/O阻塞和sychronized锁阻塞。
如下:在线程中调用了sleep(2000),线程进入了有限期等待状态,如果在线程等待状态下调用线程的interrupt(),这个时候就会抛出InterruptedException,从而提前结束线程,不执行之后的语句

        public void run() {
            try {
                Thread.sleep(2000);
                System.out.println("Thread normally end");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

2.2 interrupted()

如果一个线程的 run() 方法执行一个无限循环,并且没有执行 sleep() 等会抛出 InterruptedException 的操作,那么调用线程的 interrupt() 方法就无法使线程提前结束。
调用 interrupt() 方法虽然在线程没有异常抛出的情况下不会中断线程,但是会设置线程的中断标记,此时再调用 interrupted() 方法会返回 true。在循环体中使用 interrupted() 方法来判断线程是否处于中断状态,从而提前结束线程。

        public void run() {
            while (!interrupted()) {
                // 循环体
            }
            System.out.println("Thread end");
        }

2.3 Executor 的中断操作
Executor 的 线程中断操作可以分为中断所有线程和中断单个线程。
中断所有线程:
调用 Executor 的 shutdown() 方法会等待线程都执行完毕之后再关闭
调用 Executor 的 shutdownNow() 方法,立即中断每个线程,相当于调用每个线程的 interrupt() 方法。

    executorService.execute(() -> {
        try {
            Thread.sleep(2000);
            System.out.println("Thread run");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
    executorService.shutdownNow();
    System.out.println("Main run");

控制台输出:

Main run
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    .....

中断某个指定线程:
只想中断 Executor 中的一个线程,可以通过使用 submit() 方法来提交一个线程,它会返回一个 Future 对象,通过调用该对象的 cancel(true) 方法就可以中断线程。

Future future = executorService.submit(() -> {
    // 任务内容
});
future.cancel(true);
3.资源的互斥访问

Java 提供了两种锁机制来控制多个线程对共享资源的互斥访问,一个是 JVM 实现的 synchronized,而另一个是 JDK 实现的 ReentrantLock。

synchronized

synchronized是一个关键字,作用域如下
1.同步一个代码块
只作用于同一个对象,如果调用两个对象上的同步代码块,就不会进行同步。
2.同步一个方法
和同步代码块一样,作用于同一个对象。
3.同步一个类
作用于整个类,也就是说两个线程调用同一个类的不同对象上的这种同步语句,也会进行同步。
4.同步一个静态方法
作用于整个类。

ReentrantLock

ReentrantLock 是 java.util.concurrent(J.U.C)包中的锁,是一个类。

public class LockExample {
    private Lock lock = new ReentrantLock();
    public void func() {
        lock.lock();
        try {
            for (int i = 0; i < 10; i++) {
                System.out.print(i + " ");
            }
        } finally {
            lock.unlock(); // 确保释放锁,从而避免发生死锁。
        }
    }
}

synchronized和ReentrantLock两者比较

1.synchronized 是 JVM 实现的,而 ReentrantLock 是 JDK 实现的。synchronized由java虚拟机自动实现锁的获取与释放操作,ReentrantLock 需用户通过代码手动获取和释放锁
2.是否等待可中断
当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,改为处理其他事情。
ReentrantLock 可中断,而 synchronized 不行。
3.是否为公平锁
公平锁是指多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁。
synchronized 中的锁是非公平的,ReentrantLock 默认情况下也是非公平的,但是也可以是公平的。
4.锁绑定多个条件
一个 ReentrantLock 可以同时绑定多个 Condition 对象。

总结分析:
等待可中断、公平锁、锁绑定多个条件这些是ReentrantLock 的高级功能,除非需要用到这些功能,不然优先选择synchronized,使用 synchronized 不用担心没有释放锁而导致死锁问题,因为 JVM 会确保锁的释放。

4.线程间的协作

当多个线程一起工作去解决某个问题时,如果某些部分必须在其它部分之前完成,那么就需要对线程进行协调。

线程类提供的join()方法
在线程中调用另一个线程的 join() 方法,会将当前线程挂起,而不是忙等待,直到目标线程结束。

Object类提供的wait() notify() notifyAll()方法
调用 wait() 使得线程等待某个条件满足,线程在等待时会被挂起,当其他线程的运行使得这个条件满足时,其它线程会调用 notify() 或者 notifyAll() 来唤醒挂起的线程。
只能用在同步方法或者同步控制块中使用,否则会在运行时抛出 IllegalMonitorStateException

wait() 和 sleep() 的区别:
wait() 是 Object 的方法,而 sleep() 是 Thread 的静态方法;
wait() 会释放锁,sleep() 不会。

JUC类库中的 Condition 类提供的await() signal() signalAll()
java.util.concurrent 类库中提供了 Condition 类来实现线程之间的协调,可以在 Condition 上调用 await() 方法使线程等待,其它线程调用 signal() 或 signalAll() 方法唤醒等待的线程。
和Object类提供的wait()等方法相比,await()一般配合ReentrantLock使用。

JUC类库中的 CountDownLatch类
用来控制一个或者多个线程等待多个线程。

维护了一个计数器 cnt,每次调用 countDown() 方法会让计数器的值减 1,减到 0 的时候,那些因为调用 await() 方法而在等待的线程就会被唤醒。

JUC类库中的 CyclicBarrier类
用来控制多个线程互相等待,只有当多个线程都到达时,这些线程才会继续执行。

和 CountdownLatch 相似,都是通过维护计数器来实现的。线程执行 await() 方法之后计数器会减 1,并进行等待,直到计数器为 0,所有调用 await() 方法而在等待的线程才能继续执行

CyclicBarrier 和 CountdownLatch 的一个区别是,CyclicBarrier 的计数器通过调用 reset() 方法可以循环使用,所以它才叫做循环屏障。

JUC类库中的 Semaphore类
Semaphore 类似于操作系统中的信号量,可以控制对互斥资源的访问线程数。

java.util.concurrent.BlockingQueue 接口
BlockingQueue 接口有以下阻塞队列的实现:
FIFO 队列 :LinkedBlockingQueue、ArrayBlockingQueue(固定长度)
优先级队列 :PriorityBlockingQueue

接口提供了阻塞的 take() 和 put() 方法:如果队列为空 take() 将阻塞,直到队列中有内容;如果队列为满 put() 将阻塞,直到队列有空闲位置。

BlockingQueue 用来实现生产者消费者问题

ForkJoin
主要用于并行计算中,把大的计算任务拆分成多个小任务并行计算。也就是分治法。

ForkJoin 使用 ForkJoinPool 来启动,它是一个特殊的线程池,线程数量取决于 CPU 核数。

public class ForkJoinPool extends AbstractExecutorService

作为并行计算的任务类继承RecursiveTask,并重写compute()方法,通过调用任务类的fork()方法开启小任务,调用join()方法汇总结果。

比较分析:
多用同步工具类,少用 wait() 和 notify()。CountDownLatch, CyclicBarrier, Semaphore 等同步类简化了编码操作,而用 wait() 和 notify() 实现复杂控制流比较困难;其次,这些同步类是由最好的企业编写和维护。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1020360.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号