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

第一章  多线程技能

一、多线程的创建

        1、集成Thread类

        2、实现Runnable接口

二、线程的方法

        1、currentThread()

                获取当前运行的线程。

                该方法将返回代码段正在被哪个线程调用的信息。

        2、isAlive()

                判断当前的线程是否处于活动状态。

                如果线程未start(),返回false;

                如果线程start(),未运行结束,返回true;

                如果线程已经运行结束,返回false;

        3、sleep()

                在指定的毫秒数内让当前“正在执行的线程”休眠(暂停执行)。

                这个“正在执行的线程”是指 this.currentThread() 返回的线程。

        4、getId()

                获取线程的唯一标识。

三、停止线程的相关方法

        1、interrupt()

                该方法仅仅是在当前线程打了一个标记,标记为中断状态,并不是真的停止线程。

                如果想要停止线程,需要自己在线程类run()方法中通过获取当前线程的状态,进行判断和处理。

                在sleep()状态下interrupt线程,会抛出InterruptedException,并清除interrupt状态。

                先把线程打上interrupt()标记,再让线程sleep(),同样会抛出InterruptedException,并清除interrupt状态。

                总结:只要一个线程同时满足interrupt()状态和sleep()状态,就会抛出InterruptedException,并且会清除interrupt状态。

        2、interrupted()

                测试当前线程是否已经中断。

                执行本方法后,将会自动清除掉当前线程的interrupt()状态,即将已经打过interrupt()标记的线程,变为没有interrupt()标记。

        3、isInterrupted()

                测试线程是否已经中断。不会改变interrupt()状态。

        4、异常法

                在run()方法中,通过判断this.interrupted(),抛出一个异常,以此打断线程的正常执行顺序。

                同时,在run()方法中,try()catch()异常。

        5、暴力停止——stop()

                该方法已经被作废。该方法将会抛出ThreadDeath异常,此异常不需要显示地捕捉。

                作废原因:

                                ①有可能使一些请理性的工作得不到完成

                                ②对锁定的对象进行了“解锁”,导致数据得不到同步的处理,出现数据不一致的问题。

        6、return

                与interrupt()配合使用,和异常法相似。

                更加推荐使用异常法,因为过多的return将会污染程序,而使用异常流能更好、更方便地控制程序的运行流程。

四、暂停线程的相关方法

        1、suspend()

                使一个线程进入暂停状态,可恢复运行。

                suspend()状态的线程isAlive()为true。

        2、resume()

                让一个陷入暂停状态的线程恢复运行。

        3、suspend()和resume()的缺点

                ①独占

                在使用公共的同步对象,也就是加锁对象时,由于自身陷入了暂停状态,锁得不到释放,其他线程也就无法获取加锁对象了。

                例:System.out.println();

                ②不同步

                因为线程的暂停而导致数据不同步。

                方法执行一半被暂停了,方法的目的是改变对象的两个属性,结果刚改变了一个属性就被暂停,导致了数据的不同步。

        4、yield()

                放弃当前的CPU资源,将它让给其他的任务去占用CPU执行时间。

                有可能刚刚放弃,马上又获得CPU时间片。      

五、线程的优先级

        1、setPriority()

                线程可以划分优先级,优先级较高的线程得到的CPU资源较多。

                设置线程优先级有助于帮“线程规划期”确定下一次选择哪一个线程来优先执行。

                在Java中,线程的优先级分为1~10这10个等级,如果小于1或者大于10,则JDK将会抛出异常throw new IllegalArgumentException()。

                JDK中使用三个常量来预置定义优先级的值,代码如下:

    /**
     * The minimum priority that a thread can have.
     */
    public final static int MIN_PRIORITY = 1;

   /**
     * The default priority that is assigned to a thread.
     */
    public final static int NORM_PRIORITY = 5;

    /**
     * The maximum priority that a thread can have.
     */
    public final static int MAX_PRIORITY = 10;

        2、继承特性

                在Java中,线程的优先级具有继承性,比如A线程启动B线程,则B线程的优先级与A是一样的。

        3、规则性

                CPU尽量将执行资源让给优先级比较高的线程。

        4、随机性

                线程的优先级与代码执行顺序无关。

                优先级较高的线程不一定先执行完。

六、守护线程

        Java线程中有两种线程,一种是用户线程,另一种就是守护(Daemon)线程。

        1、什么是守护线程?

                守护线程是一种特殊的线程,它的特性有陪伴的含义,当进程中不存在非守护线程了,则守护线程自动销毁。

                典型的守护线程就是垃圾回收线程(GC),当进程中没有非守护线程了,则垃圾回收线程也就没有存在的必要了,自动销毁。

 

 

第二章  对象及变量的并发访问

一、synchronized同步方法

        1、A线程先持有object对象的Lock锁,B线程可以以异步的方式调用object对象中的非synchronized类型的方法。

        2、A线程先持有object对象的Lock锁,B线程如果需要在这时调用object对象中的synchronized类型的方法则需等待,也就是同步。

        3、脏读:只对赋值操作加锁,未对取值操作加锁。

        4、synchronized锁重入

                ①前提

    • 不在函数内使用静态或全局数据。
    • 不返回静态或全局数据,所有数据都由函数的调用者提供。
    • 使用本地数据(工作内存),或者通过制作全局数据的本地拷贝来保护全局数据。
    • 不调用不可重入函数

                ②可重入与线程安全

                         可重入的函数一定是线程安全的,反之则不一定成立。

                ③synchronized可重入锁的实现

       每个锁关联一个线程持有者和一个计数器。当计数器为0时表示该锁没有被任何线程持有,那么任何线程都可能获得该锁而调用相应方法。当一个线程请求成功后,JVM会记下持有锁的线程,并将计数器计为1。此时其他线程请求该锁,则必须等待。而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增。当线程退出一个synchronized方法/块时,计数器会递减,如果计数器为0则释放该锁。

                ④可重入锁具有继承性

当存在父子类继承关系时,子类是完全可以通过“可重入锁”调用父类的同步方法的。

这里的继承性,不是指的方法,是指的锁。

        5、出现异常,锁自动释放

        6、同步不具有继承性

                父类某个方法加同步锁,子类继承父类,子类无锁。  

      

二、synchronized同步语句块

        1、synchronized(this)代码块是锁定当前对象的

        2、可以将任意对象作为对象监视器

        3、静态同步synchronized方法与synchronized(class)代码块

                ①synchronized加到static静态方法上是给Class类上锁。

                ②synchronized加到非static静态方法上是给对象上锁。

        4、不使用String作为锁对象

        5、同步synchronized方法无限等待与解决

                如果一个类有多个方法都持有当前对象当锁,若其中一个方法进入死循环,其他同步对象将陷入无限等待。

                解决:都用new Object()当锁,方法之间异步。

        6、死锁问题

                ①死锁是程序设计的Bug,在设计程序时就要避免双方互相持有对方的锁的情况。

                ②死锁问题与锁是否嵌套无关,只要互相等待对方释放锁就有可能出现死锁。

                在jdk安装目录的bin目录下,执行jps查看当前运行的main方法所在类的进程id,

                再输入jstack -l id查看执行结果。

        7、内部类与静态内部类

                synchronized绑定了相同对象就是同步的,绑定不同对象就是异步的。

                针对同步方法,只要对象不变,即使对象的属性被改变,运行的结果还是同步。

 

三、volatile关键字

        关键字volatile的主要作用是使变量在多个线程间可见。

        线程安全包含原子性可见性两个方面。volatile解决的是可见性,但是在JDK1.7之前才有用。

  1. volatile重要工作是避免线程脏读:当线程对volatile变量进行读操作时,会先将自己工作内存中的变量置为无效,之后再通过主内存拷贝新值到工作内存中使用。
  2. volatile解决的是变量在多个线程之间的可见性,但不能完全保证数据的原子性。
  3. 现在JVM经过优化,已不会出现liveness failure 。所以没事别用volatile。

    

第三章 线程间通信

一、等待/通知机制

    1、wait()

            释放锁,可以加时间自动唤醒。

    2、notify()

            随机唤醒一个以参数为锁的线程,但是在当前线程的方法执行完之前,不会释放锁。

    3、notifyAll()

            唤醒所有线程。

    4、通过管道进行线程间的通信:字节流

            在Java的JDK中提供了4个类来使线程间可以进行通信:

            1)PipedInputStream 和 PipedOutputStream

            2)PipedReader 和 PipedWriter

 

二、join的使用

    在很多情况下,主线程创建并启动子线程,如果子线程中要进行大量的耗时运算,主线程往往将早于子线程结束之前结束。这时,如果主线程想等待子线程执行完成之后再结束,比如子线程处理一个数据,主线程要取得这个数据中的值,就要用到join()方法了。

    方法join()的作用是等待线程对象销毁。

    1、作用

            方法join的作用,是使对象x正常执行run()方法中的任务,而使当前线程z进行无限期的阻塞,等待线程x销毁后再继续执行线程z后面的代码。

            方法join具有使线程排队运行的作用,有些类似同步的运行效果。

    2、join与synchronized的区别是:

                    join在内部使用wait()方法进行等待,而sychronized关键字使用的是“对象监视器”原理做为同步。

    3、join()方法与interrupt()方法如果彼此遇到,则会出现异常。

    4、join(long)具有释放锁的特点。

 

三、类ThreadLocal的使用

    变量值的共享可以使用public static变量的形式,所有的线程都使用同一个public static 变量。如果想实现每一个线程都有自己的共享变量该如何解决呢?JDK中提供的类ThreadLocal正是为了解决这个问题。

    类ThreadLocal主要解决的就是每个线程绑定自己的值,可以将ThreadLocal类比喻成全局存放数据的盒子,盒子中可以存储每个线程的私有数据。

    1、ThreadLocal解决的是变量在不同线程间的隔离性,也就是不同线程拥有自己的值,不同线程中的值可以放入ThreadLock类中进行保存。

    2、get()

            默认返回null,可以通过继承ThreadLocal重写initialValue()方法的方式,修改默认返回值。

    

四、类InheritableThreadLocal的使用

    使用类InheritableThreadLocal可以在子线程中取得父线程继承下来的值。

 

 

第四章 Lock的使用

一、使用ReentrantLock类

    1、获取锁的方法

           a) lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁;

           b) tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false;

           c) tryLock(long timeout,TimeUnit unit), 如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;

           d) lockInterruptibly:如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到或者锁定,或者当前线程被别的线程中断;

    2、unlock()方法

            释放锁,最好放在finally代码块中执行。

    3、Condition对象

  • Object类中的wait()方法,相当于Condition类中的await()方法。二者都会释放锁。
  • Object类中的wait(long timeout)方法相当于Condition类中的await(long time, TimeUnit unit)方法。
  • Object类中的notify()方法相当于Condition类中的signal()方法。notify()不会释放锁,signal()会释放锁。
  • Object类中的notifyAll()方法相当于Condition类中的signalAll()方法。

    4、公平锁与非公平锁

            公平锁:线程获取锁的顺序是按照线程加锁的顺序来分配的,即先来先得的FIFO先进先出顺序。

            非公平锁:抢占机制,随机获得锁。

            通过new ReentrantLock(true)可以获取公平锁,new ReentrantLock(false)可以获取非公平锁。

            new ReentrantLock()是非公平锁。

    5、方法的讲解

  • int getHoldCount():查询当前线程保持此锁定的个数,也就是调用lock()方法的次数。
  • int getQueueLength():返回正在等待获取此锁定的线程估计数。比如有5个线程,1个线程执行await()方法,那么在调用getQueueLength()方法后,返回值是4,说明有4个线程同时在等待lock的释放。
  • int getWaitQueueLength(Condition condition):返回等待与次锁定相关的给定条件Condition的线程估计数。比如有5个线程,每个线程都执行了同一个condition对象的await()方法,则调用本方法的返回值是5。
  • boolean hasQueuedThread(Thread thread):查询指定的线程是否正在等待获取此锁定。
  • boolean hasQueuedThread():查询是否有线程正在等待获取此锁定。
  • boolean hasWaiters(Condition condition):查询是否有线程正在等待与此锁定有关的condition条件。
  • boolean isFair():判断是不是公平锁。
  • boolean isHeldByCurrentThread():查询当前线程是否保持此锁定。
  • boolean isLocked():查询此锁定是否由任意线程保持。
  • void lockInterruptibly():如果当前线程未被中断,则获取锁定,如果已经被中断则出现异常。
  • boolean tryLock():仅在调用时锁定未被另一个线程保持的情况下,才获取该锁定。
  • boolean tryLock(long timeout, TimeUnit unit):如果锁定在给定等待时间内没有被另一个线程保持,且当前线程未被中断,则获取该锁定。
  • void awaitUninterruptibly():使用await()方法,且不会抛出InterruptedException异常。
  • boolean awaitUntil(Date deadline):指定时间自己唤醒自己,也可以被提前唤醒。

 

二、使用ReentrantReadWriteLock类

    1、读读共享

    2、写写互斥

    3、读写互斥

    4、写读互斥

你可能感兴趣的:(Java多线程)