多线程知识点总结(二)

 
第三章  线程间通信
 
32、除了等待/通知机制,采用sleep 和 while(true)也可以实现多个线程间的通信,但是有一个弊端是线程要不断的通过while语句轮询机制来检测某一个条件,这样就会浪费CPU资源,如果轮询的时间间隔太小,更加浪费CPU资源,如果太大,就会丢失某些数据。所以更好的解决方法是采用wait/notify机制。
 
33、在调用wait( )方法前线程必须获得该对象的对象级别锁,即只能在同步方法或者同步代码块中调用wait( )方法。如果调用wait()方法前没有会的适当的锁,就会抛出 IllegalMonitorStateException,它是RunTimeException的一个子类,因此不需要try-catch语句捕获异常。
 
34、在执行notify( )方法之后,当前线程不会马上释放该对象锁,wait()状态的线程也不会马上获取该对象锁,要等到执行notify()方法的线程将程序执行完,也就是 退出synchronized代码块,才会释放对象锁。
        当wait()状态的线程运行完毕之后,它释放掉该对象锁,此时如果该对象没有再使用notify语句,则即便该对象已经空闲,其他wait状态等待的线程由于没有获得该对象的通知,仍然会阻塞在wait状态,直到这个对象 发出一个notify 或者  notifyAll.
 
35、线程状态切换示意图
36、每个锁对象都有两个队列,一个是就绪队列,一个是阻塞队列。就绪队列存储了将要获得锁的线程,阻塞队列存储了被阻塞的线程。一个线程被唤醒后,才会进入就绪队列,等待CPU的调度,反之,一个线程被wait后,就会进入阻塞队列,等待下一次被唤醒。
 
37、方法wait( )被执行后,会自动释放锁;执行完 notify( )方法后,锁不会被自动释放,必须等到执行完同步代码块;sleep方法不会自动释放锁。
 
38、当线程呈wait( )状态时,调用对象的interrupt()方法会出现 InterruptException异常,这时线程会终止,锁也会释放。
 
39、如果通知过早,或者等待wait的条件发生变化,都会造成程序逻辑的混乱。
 
40、生产者消费者问题:
①一生产与一消费:操作值     这时可以正常运行
②多生产与多消费:操作值--假死。  “假死”的现象其实就是线程进入waiting状态。虽然在代码中已经通过wait/notify进行通信了,但是不能保证notify唤醒的是异类,也许是同类,比如“生产者”唤醒“生产者”,“消费者”唤醒“消费者”,累积下来,就会造成所有的线程都处于waiting状态,导致“假死”。解决假死的办法是 将 notify( )改为 notifyAll( ),这样可以不仅唤醒同类,也可以唤醒异类。
③一生产与一消费:操作栈    就是生产者向堆栈List对象中放入数据,消费者从List堆栈中取出数据。可以运行正常。
④一生产与多消费:操作栈    wait条件改变时可能没有得到及时的响应,所以多个呈wait状态的线程被唤醒,因此后面会出现异常,解决方法是 将逻辑判断条件的  if  改为 while;解决假死的方法是 notifyAll( )方法。
⑤多生产与一消费:操作栈    同样逻辑条件是while语句,为了避免假死 ,使用notifyAll
⑥多生产与多消费:操作栈    同样逻辑条件是while语句,为了避免假死 ,使用notifyAll
 
41、使用管道流(pipeStream)是一种特殊的流,用于在不同的线程间直接传递数据,一个线程发送数据到输出管道,另外一个先后才能从输入管道中读取数据。
在Java中提供了4个类来使线程间进行通信:
①PipedInputStream 和 PipedOutputStream 传递的是字节流
②PipedReader 和 PipedWriter 传递的是字符流
42、方法join的使用
① 方法join的作用是使所属的线程对象x 正常执行run( )方法中的任务,而使当前线程z进行无限期的等待(比如说一个主线程和一个子线程,让子线程执行run()方法,主线程一直等待),等待线程x销毁后再继续执行线程z 后面的代码。
②方法join具有使线程排队运行的作用,有些类似同步的运行效果。join与synchronized的区别是:join在内部使用wait方法进行等待,而synchronized关键字使用的是“对象监视器”原理做同步。
③在join的过程中,如果当前线程对象被中断,则当前线程出现异常。
④join(long) 的功能在内部是使用wait(long) 方法来实现的,所以 join(long) 释放锁;sleep(long) 方法不释放锁。
 
43、类ThreadLocal可以解决的问题是每个线程绑定自己的值。
       使用的方法如下:
 
        在第一次调用ThreadLocal类的get( )方法时,其返回值为null,设置默认值的方法是:
 
44、类InheritableThreadLocal 可以在子线程中取得父线程继承下来的值
    
    使用InheritableThreadLocal类需要注意的一点是,如果子线程在取得值的同时,主线程将 InheritableThreadLocal 中的值进行更改,那么子线程取到的值还是旧值。
 
 
第四章  Lock的使用
 
45、等待/通知模式的两种实现方式的对比:
        使用类 ReentrantLock 借助于 Condition对象可以实现等待/通知模式,具有更好的灵活性,可以实现多路通知的功能,也就是说在一个lock 对象里面可以创建多个 Condition (即对象监视器)实例,线程对象可以注册在指定的 Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。
        使用synchronized 相当于整个Lock对象中只有一个单一的Condition 对象,所有的线程都注册在它一个对象的身上,线程开始 notifyAll() 时,需要通知所有的 WAITING  线程,没有选择权,会出现很大的效率问题。
 
46、Condition实现等待/通知:
Object类中的wait( )方法相当于Condition类中的  await( )方法;
Object类中的notify( )方法相当于Condition类中的  signal( )方法;
Object类中的notifyAll( )方法相当于Condition类中的  signalAll( )方法;
47、锁 Lock可以分为“公平锁”和“非公平锁”,公平锁表示线程获取锁的顺序是按照线程加锁的顺序来分配的,即先来先得先进先出的顺序。而非公平锁就是一种获取锁的抢占机制,是随机获得锁的,和公平锁不一样的就是先来的不一定先得到锁,这个方式可能造成某些线程一直拿不到锁,结果就是不公平的了。
 
48、lock的常用方法总结:
    ① lock.getHoldCount( )的作用是查询当前线程保持此锁定的个数,也就是调用lock()方法的次数。
    ② lock.getQueueLength( )的作用是返回正等待获取此锁定的线程估计数。
    ③ lock.getWaitQueueLength(Condition condition)的作用是返回等待与此锁定相关的给定条件Condition的线程估计数。
    ④ lock.hasQueuedThread(Thread  thread) 的作用是查询指定的线程是否正在等待获取此锁定。
    ⑤ lock.hasQueuedThreads( ) 的作用是查询是否有线程正在等待获取此锁定。
    ⑥ lock.hasWaiters(Condition condition) 的作用是查询是否有线程正在等待与此锁定有关的condition条件。
    ⑦ lock.isFair( )的作用是判断是不是公平锁。(默认ReentrantLock类使用的是非公平锁)
    ⑧ lock.isHeldByCurrentThread( )的作用是查询当前线程是否保持此锁定。
    ⑨ lock.isLocked( )的作用是查询此锁定是否由任意线程保持。
    ⑩ lock.lockInterruptibly( )的作用是:如果当前线程未被中断,则获取锁定,如果已经中断则出现异常。
    (11)lock.tryLock( )的作用是:仅在调用时锁定未被另一个线程保持的情况下,才获取该锁定。
    (12)lock.tryLock( long timeout,TimeUnit unit)的作用是,如果锁定在给定等待时间内没有被另一个线程保持,切当前线程未被中断,则获取该锁定。
 
49、使用Condition 对象可以对线程执行的业务进行排序规划。
 
50、类 ReentrantLock 具有完全互斥排他的效果,即同一时间只有一个线程在执行 ReentrantLock.lock( ) 方法后面的任务,可以使用读写锁ReentrantReadWriteLock 来提升运行速度。
 
第五章  定时器Timer
 
51、在实际业务开发中,完成定时任务常采用的是 Spring 定时任务之 @Scheduled cron表达式,而不是采用Timer完成定时任务。
 
52、定时计划任务功能在Java中主要使用的就是 Timer 对象,它在内部使用 多线程的方式进行处理。
 
53、Timer 类的主要作用就是设置计划任务,但封装任务的类是 TimerTask 类,因此需要将执行任务的代码放入到 TimerTask的子类中,因为TimerTask 是一个抽象类。
 
54、Timer 类的使用,创建一个Timer 就是启动一个新的线程
//并不是守护线程,任务虽然执行完了,但是进程并没有销毁,是红色状态
private static Timer timer = new Timer();
 
//设置成守护线程,程序运行后迅速结束当前线程,并且TimerTask中的任务不再被运行,因为进程已经结束了
private static Timer timer = new Timer(true);
在实际项目的开发中,就是要求不能是守护线程,因为守护线程的话,TimerTask类中的任务就不能执行了。
 
55、如果计划的时间早于当前时间,那么任务就会立即执行task 任务。
 
56、Timer类中允许有多个 TimerTask任务,TimerTask任务是以队列的方式一个一个被顺序执行的,所以执行的时间与预期的时间可能不一样,因为前面的任务可能消耗的时间比较长,则后面的任务执行的时间也要被延迟。举个例子:比如是每5分钟执行一次任务,如果前面的任务执行了6分钟,那么后面的任务不会在第五分钟的时候执行,它会延迟一分钟,在第六分钟的时候执行。
 
57、TimerTask类的cancel()方法:是将自身从任务队列中被移除,不会影响其他任务。比如一个类是TimerTask类的子类,即extends  TimerTask,那么调用cancel()方法的时候直接   this.cancel( ) 即可。
       Timer类的cancel()方法:是将全部任务都被清除,并且进程被销毁,按钮由红色变成灰色,但是有时并不一定会停止执行计划任务,而是正常执行,因为有时并没有抢到 queue锁。
 
58、方法 schedule 和 方法 scheduleAtFixedRate的区别:
 
59、schedule方法不具有追赶执行性,scheduleAtFixedRate方法具有追赶执行性。
        所谓追赶执行性,指的是在写代码的过程中,会写一个参考时间,以这个参考时间为基本,每隔多少秒执行一次,但是当程序运行到相应的语句时,时间肯定已经晚于设置的参考时间,那么在这个参考时间到当前的时间之间,如果按照设置的频率,那么应该会执行几次。采用 schedule方法,那么这段时间应该执行的那几次就不会执行了,直接从当前时间开始,按照设置的频率进行执行,而scheduleAtFixedRate方法就会在 当前时间的这一刻 将刚刚需要执行的几次补上,补够了再按照设置的频率执行。参考下面两个截图。

 
第六章  单例模式与多线程
 
60、在多线程的编程过程中,需要注意的是怎样保证单例模式时安全正确的。
 
61、立即加载(饿汉模式) 与 延迟加载(懒汉模式)
    立即加载(饿汉模式):就是在使用类的时候已经将对象创建完毕,常见的实现办法是直接new 实例化,“饿汉模式”。
    延迟加载(懒汉模式):就是在调用get( )方法时实例才被创建,常见的实现办法是在get( )方法中进行new实例化。但是在多线程的情况下,延迟加载就会取出多个实例。
 
62、解决在多线程的情况下延迟加载取出多个实例的情况:
① 声明synchronized 关键字同步方法,虽然可行,但是效率较低。
② synchronized同步代码块,同样可行,但是效率比较低。
③ synchronized同步代码块,但是只是针对部分重要的代码进行同步,不能保证是单个实例。
④ DCL(Double-Check  Locking)双检查锁机制(常用的解决方案)
    
⑤ 使用静态内置类实现单例模式
     局限性:如果遇到序列化对象时,使用静态内置类得到的对象还是多例的。
⑥ 在反序列化时,使用 readResolve( )方法。
⑦ 使用static代码块实现单例模式
     静态代码块中的代码在使用类的时候就已经开始执行了,可以利用这个特性实现单例设计模式。    
⑧ 使用enum枚举数据类型实现单例模式
     枚举 enum 和静态代码块的特性相似,在使用枚举类时,构造方法会被自动调用,利用这个特性实现单例设计模式。
    
 
第七章  拾遗增补
 
63、线程对象在不同的运行时期有不同的状态,状态信息就存放在 State枚举类中。线程状态有以下几种状态:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING 、TERMINATED,调用与线程有关的方法是造成线程状态改变的主要原因。
NEW:线程实例化后还从未执行start()方法时的状态。
RUNNABLE:线程进入运行的状态。
TERMINATED:线程被销毁时的状态。
TIMED_WAITING:线程执行了Thread.sleep( )方法,呈等待状态,等待时间到达,继续向下运行。
BLOCKED:出现在某一个线程在等待锁的时候。
WAITING:线程执行了Object.wait( )方法后所处的状态。
 
64、线程组的作用就是:可以批量的管理线程或线程组对象,有效的对线程或线程组对象进行组织。
 
65、在实例化一个ThreadGroup 线程组x 时如果不指定所属的线程组,则x 线程组自动归到当前线程对象所属的线程组中,也就是隐式地在一个线程组找那个添加了一个子线程组。
 
66、JVM的根线程组就是system,再取其父线程组则会出现空指针异常。
 
67、通过将线程归属到线程组中,当调用线程组 ThreadGroup 的 interrupt()方法时,可以将该组中的所有正在运行的线程批量停止。
 
68、SimpleDateFormate主要负责日期的转换与格式化,但是在多线程的环境中,使用此类容易造成数据转换及处理的不准确,因为SimpleDateFormate类并不是线程安全的。
    解决异常方法一是创建多个SimpleDateFormate类的实例。
    
    解决异常方法二是ThreadLocal 类能使线程绑定到指定的对象,使用该类也可以解决该问题。
    
69、线程中出现异常的处理:在Java多线程技术中,可以对多线程中的异常进行捕捉,使用的是 UncaughtExceptionHandler类,从而对发生的异常进行有效的处理。
 
方法setUncaughtExceptionHandler( )的作用是对指定的线程对象设置默认的异常处理器。
方法setDefaultUncaughtExceptionHandler( )的作用是对指定的线程类的所有线程对象设置默认的异常处理器。
 
70、在默认的情况下,线程组中的一个线程出现异常不会影响到其他线程的运行。
        
   
参考资料:《Java多线程编程核心技术》

猜你喜欢

转载自www.cnblogs.com/Demrystv/p/9164210.html