《Java多线程编程核心技术》读书笔记

本文详细探讨了Java线程的基础概念和技术细节,包括线程创建、中断管理、锁机制及同步控制等内容。针对线程生命周期、中断处理、锁类型、线程间通信等关键问题进行了详尽的解释。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>> hot3.png

1.使用继承Thread类的方式运行线程最大的问题在于java只支持单继承,如果要继承的类已经继承了另外一个类,那么就不能使用这种方式了。

2.调用interupt()方法仅仅是在当前线程中打了一个停止的标记,并不是真的停止线程。

3.注意Thread.interrupted()方法是测试当前线程是否已经中断,其与调用该方法的引用无关,比如如下代码:

Thread thread = new Thread(...);
thread.start();
thread.interrupted();

如果在main方法中执行这段代码,thread.interrupted()检测还是main线程是否中断了,而与调用方无关,因为interrupted()方法是一个静态方法。另外,interrupted()方法会清除当前的中断状态,也就是说无论之前是否中断了,调用该方法之后再次调用该方法,那么结果始终返回false,除非在两次调用之间有另外的线程对该线程发出了中断申请。

4.Thread.isInterrupted()方法不是一个静态方法,其会测试调用该方法的线程是否是中断状态,但不会清除状态标志。

5.Thread.stop()方法已经作废,作废原因有两个:①stop方法会强制让线程停止,这会导致一些清理工作得不到完成;②对锁定的对象进行了解锁,导致数据得不到同步的处理,出现数据不一致的问题。

6.中断线程较好的方式:在调用线程中执行Thread.interrupt()方法来标记执行线程已经被中断了,然后由执行线程调用Thread.isInterrupted()方法来检测当前线程是否已经中断,如果中断了则执行一些回收任务,通过抛出异常或者使用return;来中断当前线程,通过这种方式能够比较平滑的停止当前线程,这里还是建议使用“抛异常”的方式来实现线程的停止,因为在catch块中还可以将异常向上抛,使线程停止的事件得以传播。

7.Thread.suspend()方法可以使当前正在运行的线程暂停,但是其缺陷在于暂停的线程并没有释放其持有的锁,因而后续线程也无法执行锁住的代码。

8.优先级高的任务优先执行完run()方法中的任务,但这个结果不能太肯定,原因有两个:①操作系统不一定支持这种对线程执行优先级的设定;②即使操作系统支持,但是线程的优先级还具有“随机性”,也就是优先级高的线程不一定都先执行完,先执行完只是一个平均结果。

9.守护线程:守护线程可以理解为后台运行的线程,只有当jvm中所有的非守护线程都结束的时候,守护线程才随着jvm一同结束,只要有一个非守护线程没有结束,守护线程就不会结束。最典型的守护线程是GC(垃圾回收器)。

10.可重入锁的概念是:自己可以再次获取自己的内部锁。

11.当存在子父类关系时,子类可以通过可重入锁调用父类的同步方法。

12.当一个线程执行的代码出现异常时,其所持有的锁会自动释放。

13.如果某个线程获取了子类对象的锁,那么其他的试图获取该子类对象隐式父对象的锁的时候会被阻塞。

14.使用synchronized(非this对象x)的用法是将x对象本身作为对象监视器,通过这种写法可以得出三个结论:①当多个线程同时执行synchronized(x){}同步代码块时呈同步状态;②当其他线程执行x对象中synchronized同步方法时呈同步效果;③当其他线程执行x对象方法里面synchronized(this)代码块时呈现同步效果。

15.synchronized关键字加到static静态方法上是给Class类上锁,而synchronized关键字加到非static静态方法上是给对象上锁。

16.synchronized用在static静态方法上后持有的锁是当前类的Class对象,因而对于所有调用该方法(无论是通过静态调用还是通过实例对象调用)都是竞争的该Class对象的锁,都会被同步。这里需要注意的是,如果实例化了多个该对象的实例,多个线程分别使用不同的实例调用该静态方法时,因为竞争的是同一个Class对象锁,因而还是会被同步,尽管调用的是不同对象的统一方法。

17.将synchronized直接用于类X的静态方法上与使用synchronized(X.class){}的效果一致,都是竞争的X类的Class对象的锁。

18.在选择对象作为锁的时候一般不选择String类型的对象,因为String有一个常量池,比如String a = "AA";和String b = "AA";这里a和b的引用值是相同的,表面上是两个不同的对象,代表不同的锁,但实际上是同一个锁,可能会造成互斥的问题。

19.在编写并发类时,对于同一个类中的不同方法,如果可以,尽量不要使其获取同一个锁,因为如果两个线程分别调用不同的方法,那么这两个线程就存在锁竞争的问题,如果某个方法无法正常执行完,那么另一个线程将会一直被阻塞。

20.查看死锁可以在jdk的bin目录执行jps查看当前运行的进程是哪一个,然后通过jstack -l pid来查看当前进程中线程的状态。

21.对于用于同步的对象,只要对象的引用没有变化,竞争的就还是同一个锁,其与对象的某个属性值的变化无关。

22.volatile关键字能够强制线程从公共堆栈设置或者读取变量的值,而不是从线程的私有数据栈操作值。

23.synchronized与volatile关键字的区别:

①关键字volatile是线程同步的轻量级实现,所以volatile性能肯定比synchronized要好,并且volatile只能修饰于变量,而synchronized可以修饰方法,以及代码块。随着JDK新版本的发布,synchronized关键字在执行效率上得到很大提升,在开发中使用synchronized关键字的比率还是比较大的;

②多线程访问volatile不会发生阻塞,而synchronized会出现阻塞;

③volatile能保证数据的可见性,但不能保证原子性;而synchronized可以保证原子性,也可以间接保证可见性,因为它会将私有内存和公共内存中的数据做同步;

④再次重申一下,关键字volatile解决的是变量在多个变量之间的可见性问题;而synchronized关键字解决的是多个线程之间访问资源的同步性。

24.同步synchronized不仅可以解决一个线程看到对象处于不一致的状态,还可以保证进入同步方法或者同步代码块的每个线程,都看到由同一个锁保护之前所有的修改效果。synchronized会强制同步所有线程在进入同步代码块之前对当前对象的所有修改效果,无论其是否是通过同步代码块进行的修改。

25.调用wait()方法可以使处于临界区内的线程进入等待状态,同时释放同步对象的锁。而notify()操作可以唤醒一个因调用了wait操作而处于阻塞状态中的线程,使其进入就绪状态。被重新唤醒的线程会试图重新获得临界区的控制权,也就是锁,并继续执行临界区内wait()之后的代码。如果发出notify()操作时没有处于阻塞状态中的线程,那么该命令会被忽略。

26.①执行完同步代码块就会释放对象的锁;②在执行同步代码块的过程中,遇到异常而导致线程终止,锁也会被释放;③在执行同步代码块的过程中,执行了锁所属对象的wait()方法,这个线程会释放对象锁,而此线程对象会进入等待池中,等待被唤醒。

27.wait(long milliseconds)方法会在等待milliseconds时长之后返回,返回之后继续执行后续方法,其只有在等待过程中被其他线程打断了才会退出等待而抛出异常。

28.“假死”的现象就是线程进入WAITING等待状态。如果全部线程都进入WAITING状态,则程序就不在执行任何业务功能了,整个项目呈停止状态。这在使用生产者和消费者模式时经常遇到。

29.方法join()的作用是使所属的线程对象x正常执行run()方法中的任务,而使当前进行无限期的阻塞,直到线程x销毁后再执行当前线程后续的代码。

30.如果当前线程因为对某个线程调用了join()方法而进入等待状态,此时如果有另外一个线程对当前线程执行了interrupt()方法,那么当前线程就会爆出InterruptedException而退出等待。

31.join(long time)方法与sleep(long time)的区别在于join(long time)方法会释放当前线程获取到的锁而进入等待状态,而sleep(long time)方法则不会释放当前线程持有的锁。

32.ThreadLocal中的数据是可以重复获取的。

33.通过写一个类来重写ThreadLocal的initialValue()方法来给ThreadLocal来设置一个默认值。

34.通过重写ThreadLocal的initialValue()方法来对ThreadLocal设置默认值时,对于每个线程首次调用时都会执行一次该初始化方法,并且将计算结果保存在各自的线程内,再次调用时直接取线程内保存的值。

35.如果子线程和父线程在使用同一个InheritableThreadLocal对象,那么当子线程没有对该对象设值,通过该对象获取存储的值的时候其会获取到父对象最后对该对象设置的值。如果该类重写了initialValue()方法,当父线程调用过get()方法时会先执行该initialValue()方法,此时子线程再次调用get()方法时就不会执行initialValue()方法了,当父线程没有调用过该方法时,子线程调用get()方法时将会执行该initialValue()方法来初始化一个值。也就是说当父线程有值可继承的时候,子线程就不会调用初始化方法而继承该值,否则子线程将进行初始化操作。

36.InheritableThreadLocal类还有一个childValue(String parentValue);方法,parentValue是从父线程继承过来的值,在子线程继承父线程的值的时候其会先执行childValue(String parentValue)方法,并且将得到的结果设置到子线程的InheritableThreadLocal对象中。

37.使用InheritableThreadLocal类时需要注意,如果子线程在获取父线程的值之后,父线程对值进行了修改,那么子线程是无法获取到最新的值的。

38.ReentrantLock方法说明: 

  1. ReentrantLock.getHoldCount()方法返回当前线程重入当前锁的次数;
  2. ReentrantLock.getQueueLength()方法返回当前正在等待当前锁的线程数目;
  3. ReentrantLock.getWaitQueueLength(Condition condition)方法获取condition对象中正在等待的线程数目;
  4. ReentrantLock.hasQueuedThread(Thread thread)方法判断当前锁的阻塞队列中是否有thread指定的线程正在等待;
  5. ReentrantLock.hasWaiters(Condition condition)方法判断当前锁的指定Condition中是否有正在等待的线程;
  6. ReentrantLock.getWaitQueueLength(Condition condition)方法返回当前锁的指定Condition中等待的线程数目;
  7. ReentrantLock.isHeldByCurrentThread()用于判断当前锁是否被当前线程持有;
  8. ReentrantLock.isFair()判断当前锁是否为公平锁;
  9. ReentrantLock.lockInterruptibly()方法对当前线程尝试获得锁,如果没有获得则阻塞,并且在阻塞过程中响应中断操作;
  10. ReentrantLock.tryLock()方法对当前线程尝试获得锁,如果没有获取到则返回false;
  11. ReentrantLock.tryLock(long timeout, TimeUnit unit)方法对当前线程在指定时间内获取锁,如果没有获取到或者时间超限,则返回false;
  12. ReentrantLock.awaitUninterruptibly()方法使当前线程进入等待状态,并且该等待状态不响应中断;
  13. ReentrantLock.waitUntil(long time)方法指定一个到达时间,并且使当前线程进入等待状态,如果当前线程在到达时间之前被唤醒或者在到达指定时间之后将停止等待。

39.ReentrantReadWriteLock是一种读写锁,其内部维护了两个锁:一个读锁和一个写锁。读锁是多个线程可以同时获取的,而写锁与读锁互斥,也就是同一时刻只能有一个线程获取写锁,并且不能有线程使用读锁;当没有线程使用写锁的时候可以由多个线程同时获取读锁。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值