写点什么

Java 并行程序基础

  • 2022 年 4 月 26 日
  • 本文字数:3873 字

    阅读完需:约 13 分钟

Thread.yield();


}


});


t1.start();


Thread.sleep(2000);


t1.interrupt();


}


  1. 这看起来跟前面增加标志位的手法非常相似,但是中断的功能更为强劲。比如,如果在循环体中,出现了类似于 wait()或者 sleep()这样的操作,则只能通过中断来识别了。

  2. 线程的睡眠 sleep


==========


public static native void sleep(long millis) throws InterruptedException;


  1. ?该方法会让当前线程休眠若干时间,会抛出 InterruptedException 中断异常。InterruptedException 不是运行时异常,也就是程序必须捕获并且处理它,当线程在 sleep 休眠时,如果被中断,这个异常就会发生。


public static void main(String[] args) throws InterruptedException {


Thread t1 = new Thread(()->{


while (true) {


if (Thread.currentThread().isInterrupted()) {


System.out.println("Interrupt!");


break;


}


try {


Thread.sleep(2000);


} catch (InterruptedException e) {


System.out.println("Interrupted When Sleep");


Thread.currentThread().interrupt();


}


Thread.yield();


}


});


t1.start();


Thread.sleep(1000);


t1.interrupt();


}


  1. 如果在 sleep 的时候,线程被中断,则程序会抛出异常,并进入异常处理。在 catch 字句里,由于已经捕获了中断,我们可以立即退出线程,但是并没有这么做。因为**也许在这段代码中,还必须进行后续的处理,保障数据的一致性和完整性。**因此,执行了 interrupt()方法再次中断自己,置上中断标志位。只有这么做,在检查 isInterrupted(),才能发现当前线程已经被中断了。可以试一下将 catch 的 interrupt 注释掉进行验证。

  2. Thread.sleep()方法由于中断而抛出异常,此时,它会清除中断标记,如果不加处理,那么在下一次循环开始时,就无法不会这个中断,所以在异常处理中,再次设置中断标志位。

  3. 等待(wait)和通知(notify)


===================


  1. wait 和 notify 不是在 Thread 类中的方法,而是在 Object 类中,意味着任何对象都能调用这两个方法。

  2. 如果一个线程调用了 wait()方法,那么它就会计入 object 对象的等待队列。这个等待队列中,可能会有多个线程,因为系统运行多个线程同时等待同一个对象。当 notify()被调用是,它就会从这个等待队列中,随机选择一个线程,并将其唤醒。但是这个选择不是公平的,并不是先等待的线程会优先被选择,这个选择完全是随机的。

  3. notifyAll()方法会唤醒这个等待队列的所有线程。

  4. 无论是 wait()或者是 notify()方法,必须包含在对应的 synchronized 语句中,无论是 wait()或者 notify()都需要首先获取目标对象的一个监视器。

  5. 而 wait()方法执行后,会释放这个监视器,当被重新 notify()后,**要做的第一件事不是继续执行后续的代码,而是要尝试重新获取 object 的监视器。**如果暂时无法获得,线程还必须要等待这个监视器。当监视器顺利获得后,才可以真正意义上的继续执行。

  6. wait()方法和 sleep()的区别就是,wait 会释放对象的锁,而 sleep 不会释放锁。

  7. 挂起(Suspend)和继续执行(resume)


========================


  1. 被挂起的线程必须要等到 resume 操作后,才能继续指定、

  2. 但是已经被标注为废弃方法,不推荐使用。因为 suspend()在导致线程暂停的同时,并不会去释放任何资源。此时,任何线程想要访问被它暂用的锁,都会备受牵连,导致无法正常运行。直到对应的线程上进行了 resume()操作,被挂起的线程才能继续操作。但是如果 resume 操作在 suspend 之前就执行了,那么被挂起的线程就很难有机会被继续执行了。

  3. 如果想要实现 suspend 跟 resume,可以通过 wait 跟 notify 进行使用。

  4. 等待线程结束(join)和谦让(yield)


==========================


  1. 一个线程的输入可能非常依赖于另外一个或者多个线程的输出,所以,这个线程就需要等待依赖线程执行完毕,才能继续执行。


public final void join() throws InterruptedException


public final synchronized void join(long millis) throws InterruptedException


  1. 第一个 join()方法表示无限等待,它会一直阻塞当前线程,知道目标线程执行完毕。

  2. 第二个 join()给出了一个最大等待时间,如果超过给定时间目标线程还在执行,当前线程也会因为“等不及”,而继续往下执行。

  3. join 就是加入的意思,因此一个线程要加入另外一个线程,那么最好的方法就是等着它一起走


public class JoinMain {


public volatile static int i = 0;


public static class AddThread extends Thread {


@Override


public void run() {


for (i = 0; i < 1000000; i++);


}


}


public static void main(String[] args) throws InterruptedException {


AddThread at = new AddThread();


at.start();


at.join();


System.out.println(i);


}


}


  1. 主函数中,如果不用 join()等待 AddThread,那么得到的 i 很可能是 0 或者一个非常小的数字。因为 AddThread 还没执行完,i 的值就已经被输出了。但使用 join 方法后,表示主线程愿意等待 AddThread 执行完毕,跟着 AddThread 一起往前走,所以在 join()返回,AddThread 已经执行完成,故 i 总是 1000000;

  2. join 的本质是让调用线程 wait()在当前线程对象实例上。


if (millis == 0) {


while (isAlive()) {


wait(0);


}


}


  1. 可以看到,它调用线程在当前线程对象上进行等待。当执行完成后,被等待的线程会在退出前**调用 notifyAll()**通知所有的等待线程继续执行。

  2. 因此需要注意,不要在应用程序中,在 Thread 对象上使用类似 wait()或者 notify()等方法,因为这很有可能影响系统 API 的工作,或者被系统 API 所影响。


public static native void yield();


  1. yield()这是个静态方法。一旦执行,它会使当前线程让出 CPU。当前线程让出 CPU 后,还会进行 CPU 资源的争夺,但是是否能被再次分配到,就不一定了。

  2. ???????volatile 与 Java 内存模型(JMM)


=================================


  1. ???????Java 内存模型都是围绕着原子性,有序性,可见性展开。

  2. Java 使用了一些特殊的操作或者关键字来什么,告诉虚拟机,在这个地方,尤其注意,不能随意变动优化目标指令volatile 就是其中之一。

  3. volatile:易变的,不稳定的。

  4. 当 volatile 去申明一个变量,就等于告诉虚拟机。这个变量极有可能会被某些线程修改。为了确保这个变量被修改后,应用程序范围内所有线程都能够“看到”。虚拟机就必须采用一些特殊的手段,保证这个变量的可见性等特点。

  5. volatile 并不能替代锁。也无法保证一些符合操作的原子性。volatile 无法保证 i++原子性操作。


6 《一线大厂 Java 面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》无偿开源 威信搜索公众号【编程进阶路】 . volatile 能保障数据的可见性和有序性。


  1. ???????守护线程(Daemon)


=======================


  1. ???????守护线程是一种特殊的线程,是系统的守护者,在后台默默地完成一些系统性的服务。比如垃圾回收线程,JIT 线程就可以理解为守护线程。

  2. 线程的优先级


======


  1. Java 使用 1~10 表示线程优先级。数字越大优先级越高。****???????

  2. 同步方法以及同步块


=========


  1. 线程同步




  1. 由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保障数据在方法中被访问时的正确性,在访问时加入锁机制**synchronized,**当一个线程获得对象的排他锁,独占资源,其他线程必须等待,使用后是否锁即可。但是存在以下问题:

  2. 一个线程持有锁会导致其他所有需要此锁的线程挂起。

  3. 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题

  4. 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先倒置,引起性能问题。

  5. 关键字 synchronized 的作用是实现线程间的同步。它的工作是对同步的代码加锁,使得每一次,只能有一个线程进入同步块,从而保证线程间的安全性。

  6. 指定加锁对象:对给定对象加锁,进入同步代码前要获得给定对象的锁。

  7. 直接作用于实例方法:相当于对当前实例加锁,进入同步代码前要获得当前实例的锁。

  8. 直接作用于静态方法:相当于对当前类加锁,进入同步代码块要获得当前类的锁。

  9. synchronized 除了保证**线程同步,还可以保证线程之间的可见性和有序性。**从可见性上来说,synchronized 可以完全代替 volatile,只是使用上没那么方便。就有序性而言,由于 synchronized 限制每次只有一个线程可以访问同步块,无论同步块内的代码如何被乱序执行,只要保证串行语义一致性,那么执行结果总是一样的。

  10. 同步方法




  1. 可以通过 private 关键字来保证数据对象只能被方法访问,所以只需要针对方法提出一套机制,这套机制就是 synchronized 关键字。有 synchronized 方法synchronized 块

  2. synchronized 方法控制对“对象”的访问,每个对象对应一把锁,每个 synchronized 方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就会独占该锁,直到方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行

  3. 缺陷:若将一个大的方法申明为 synchronized 将会影响效率。

  4. 同步块




  1. 同步块:synchronized(obj){}

  2. Obj 称之为?同步监视器

  3. obj 可以使任何对象,推荐使用共享资源作为同步监视器

  4. 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是 this,就是这个对象本身,或者是 class

  5. 同步监视器的执行过程

  6. 第一个线程访问,锁定同步监视器,执行其中代码

  7. 第二个线程访问,返现同步监视器被锁定,无法访问

  8. 第一个线程访问完毕,解锁同步监视器

  9. 第二个线程访问,发现同步监视器没有锁,然后锁定并访问

  10. 死锁


==


  1. 多个线程各自占有一些资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方资源释放,都停止执行的情形,某一个同步块同时拥有“两个以上对象的锁”时,就可能发生“死锁”

  2. 产生死锁的四个必要条件:

  3. **互斥条件:**一个资源每次只能被一个进程使用

  4. **请求与保持条件:**一个进程因请求资源而阻塞时,对已获得的资源保持不放

用户头像

还未添加个人签名 2022.04.13 加入

还未添加个人简介

评论

发布
暂无评论
Java并行程序基础_Java_爱好编程进阶_InfoQ写作社区