java核心-多线程(4)-线程类基础知识

###1.并发java

<1>使用并发的一个重要缘由是提升执行效率。因为I/O等状况阻塞,单个任务并不能充分利用CPU时间。因此在单处理器的机器上也应该使用并发。
<2>为了实现并发,操做系统层面提供了。可是进程的数量和开销都有限制,而且多个进程之间的数据共享比较麻烦。另外一种比较轻量的并发实现是使用线程,一个进程能够包含多个线程。线程在进程中没有数量限制, 数据共享相对简单。线程的支持跟语言是有关系的。Java 语言中支持多线程。
<3>Java 中的多线程是抢占式的。这意味着一个任务随时可能中断并切换到其它任务。因此咱们须要在代码中足够的谨慎,防范好这种切换带来的反作用。

###2.基础缓存

<1>Runnable 它能够理解成一个任务。它的run()方法就是任务的逻辑,执行顺序;
    <2>Thread 它是一个任务的载体,虚拟机经过它来分配任务执行的时间片。Thread中的start方法能够做为一个并发任务的入口。不经过start方法来执行任务,那么run方法就只是一个普通的方法;
    <3>线程的状态
        见下图
    <4>Callable<T> 它是一个带返回的异步任务,返回的结果放到一个Future对象中。
    <5>Future<T> 它能够接受Callable任务的返回结果。在任务没有返回的时候调用get方法会阻塞当前线程。cancel方法会尝试取消未完成的任务(未执行->直接不执行,已经完成->返回false,正在执行->尝试中断)。
    <6>FutureTask<T> 同时继承了Runnable, Callable 接口。
    <7>Java 1.5以后,再也不推荐直接使用Thread对象做为任务的入口。推荐使用Executor管理Thread对象。Executor是线程与任务之间的的一个中间层,它屏蔽了线程的生命周期,再也不须要显式的管理线程。而且ThreadPoolExecutor 实现了此接口,咱们能够经过它来利用线程池的优势。
    <8>线程池涉及到的类有:Executor, ExecutorService, ThreadExecutorPool, Executors, FixedThreadPool, CachedThreadPool, SingleThreadPool。
    <9>Executor 只有一个方法,execute来提交一个任务;
    <10>ExecutorService 提供了管理异步任务的方法,也能够产生一个Future对象来跟踪一个异步任务。
主要的方法以下:
•	submit 能够提交一个任务
•	shutdown 能够拒绝接受新任务
•	shutdownNow 能够拒绝新任务并向正在执行的任务发出中断信号
•	invokeXXX 批量执行任务

    <11>ThreadExecutorPool 线程池的具体实现类。线程池的好处在于提升效率,能避免频繁申请/回收线程带来的开销。
它的使用方法复杂一些,构造线程池的可选参数有:
1.	corePoolSize : int 工做的Worker的数量。
2.	maximumPoolSize : int 线程池中持有的Worker的最大数量
3.	keepAliveTime : long 当超过Workder的数量corePoolSize的时候,若是没有新的任务提交,超过corePoolSize的Worker的最长等待时间。超过这个时间以后,一部分Worker将被回收。
4.	unit : TimeUnit keepAliveTime的单位
5.	workQueue : BlockingQueue 缓存任务的队列, 这个队列只缓存提交的Runnable任务。
6.	threadFactory : ThreadFactory 产生线程的“工厂”
7.	handler : RejectedExecutionHandler 当一个任务被提交的时候,若是全部Worker都在工做而且超过了缓存队列的容量的时候。会交给这个Handler处理。Java 中提供了几种默认的实现,AbortPolicy, CallerRunsPolicy, DiscardOldestPolicy, DiscardPolicy。

    <12>Executors类提供了几种默认线程池的实现方式。
1.	CachedThreadExecutor 工做线程的数量没有上限(Integer的最大值), 有须要就建立新线程。
2.	FixedThreadExecutor 预先一次分配固定数量的线程,以后再也不须要建立新线程。
3.	SingleThreadExecutor 只有一个线程的线程池。若是提交了多个任务,那么这些人物将排队,每一个任务都在上一我的物执行完以后执行。全部任务都是按照它们的提交顺序执行的。

    <13>sleep(long) 当前线程 停止 一段时间。它不会释放锁。Java1.5以后提供了更加灵活的版本。TimeUnit 能够指定睡眠的时间单位。

    <14>优先级 绝大多数状况下咱们都应该使用默认的优先级。不一样的虚拟机中对应的优先级级别的总数,通常用三个就能够了 MAX_PRIORITY, NORM_PRIORITY, MIN_PRIORITY。

    <15>让步 Thread.yield()建议相同优先级的其它线程先运行,可是不保证必定运行其它线程。

    <16>后台线程 一个进程中的全部非后台线程都终止的时候整个进程也就终止,同时杀死全部后台线程。与优先级没有什么关系。

    <17>join() 线程 A 持有线程T,当在线程T调用T.join()以后,A会阻塞,直到T的任务结束。能够加一个超时参数,这样在超时以后线程A能够放弃等待继续执行任务。

    <18>捕获异常 不能跨线程捕获异常。好比说不能在main线程中添加try-catch块来捕获其它线程中抛出的异常。每个Thread对象均可以设置一个UncaughtExceptionHandler对象来处理本线程中抛出的异常。线程池中能够经过参数ThreadFactory来为每个线程设置一个UncaughtExceptionHandler对象。

###3.访问共享资源多线程

<1>在处理并发的时候,将变量设置为private很是的重要,这能够防止其它线程直接访问变量。
    <2>synchronized 修饰方法在不加参数状况下,使用对象自己做为锁。静态方法使用Class对象做为锁。同一个任务能够屡次得到对象锁。
    <3>显式锁 Lock,相比synchronized更加灵活。可是须要的代码更多,编写出错的可能性也更高。只有在解决特殊问题或者提升效率的时候才用它。
    <4>原子性 原子操做就是永远不会被线程切换中断的操做。不少看似原子的操做都是非原子的,好比说long,double是由两个byte表示的,它们的全部操做都是非原子的。因此,涉及到并发异常的地方都加上同步吧。除非你对虚拟机十分的了解。
    <5>volatile 这个关键字的做用在于防止多线程环境下读取变量的脏数据。这个关键字在c语言中也有,做用是相同的。
    <6>原子类 AtomicXXX类,它们可以保证对数据的操做是知足原子性的。这些类能够用来优化多线程的执行效率,减小锁的使用。然而,使用难度仍是比较高的。
    <7>临界区 synchronized关键字的用法。不是修饰整个方法,而是修饰一个代码块。它的做用在于尽可能利用并发的效率,减小同步控制的区域。
    <8>ThreadLocal 这个概念与同步的概念不一样。它是给每个线程都建立一个变量的副本,并保持副本之间相互独立,互不干扰。因此各个线程操做本身的副本,不会产生冲突。

###4.终结任务并发

<1>一个线程不是能够随便中断的。即便咱们给线程设置了中断状态,它也仍是能够得到CPU时间片的。只有由于sleep()方法而阻塞的线程能够当即收到InterruptedException异常,因此在sleep中断任务的状况下能够直接使用try-catch跳出任务。其它状况下,均须要经过判断线程状态来判断是否须要跳出任务(Thread.interrupted()方法)。
    <2>synchronized方法修饰的代码不会在收到中断信号后当即中断。ReentrantLock锁控制的同步代码能够经过InterruptException中断。
    <3>Thread.interrupted方法调用一次以后会当即清空中断状态。能够本身用变量保存状态。

###5.线程协做异步

<1>wait/notifyAll wait/notifyAll是Object类中的方法。调用wait/notifyAll方法的对象是互斥对象。由于Java中全部的Object均可以作互斥量(synchronized关键字的参数),因此wait/notify方法是在Object类中的。
    <2>wait与sleep 不一样在于sleep方法是Thread类中的方法,调用它的时候不会释放锁;wait方法是Object类中的方法,调用它的时候会释放锁。
           调用wait方法以前,当前线程必须持有这段逻辑的锁。不然会抛出异常,不能继续执行。
    <3>wait方法能够将当前线程放入等待集合中,并释放当前线程持有的锁。此后,该线程不会接收到CPU的调度,并进入休眠状态。有四种状况肯能打破这种状态:
1.  有其它线程在此互斥对象上调用了notify方法,而且恰好选中了这个线程被唤醒;
2.  有其它线程在此互斥对象上调用了notifyAll方法;
3.  其它线程向此线程发出了中断信号;
4.  等待时间超过了参数设置的时间。线程一旦被唤醒以后,它会像正常线程同样等待以前持有的全部锁。直到恢复到wait方法调用以前的状态。还有一种不常见的状况,spurious wakeup(虚假唤醒)。就是在没有notify,notifyAll,interrupt的时候线程自动醒来。查了一些资料并无弄清楚是为何。不过为了防止这种现象,咱们要在wait的条件上加一层循环。

    <4> 当一个线程调用wait方法以后,其它线程调用该线程的interrupt方法。该线程会唤醒,并尝试恢复以前的状态。当状态恢复以后,该线程会抛出一个异常。
            notify 唤醒一个等待此对象的线程。
            notifyAll 唤醒全部等待此对象的线程。

###6.错失信号工具

<1>当两个线程使用notify/wait或者notifyAll/wait进行协做的时候,不恰当的使用它们可能会致使一些信号丢失。
         见下图
信号丢失是这样发生的:
当T2执行到Point1的时候,线程调度器将工做线程从T2切换到T1。T1完成T2条件的设置工做以后,线程调度器将工做线程从T1切换回T2。虽然T2线程等待的条件已经知足,但仍是会被挂起。

    <2>解决的方法比较简单:
         见下图
将竞争条件放到while循环的外面便可。在进入while循环以后,在没有调用wait方法释放锁以前,将不会进入到T1线程形成信号丢失。
    
    <3>Condition 他是concurrent类库中显式的挂起/唤醒任务的工具。它是真正的锁(Lock)对象产生的一个对象。其实用法跟wait/notify是一致的。await挂起任务,signalAll()唤醒任务。
    <4>生产者消费者队列 Java中提供了一种很是简便的容器,BlockingQueue。已经帮你写好了阻塞式的队列。除了BlockingQueue,使用PipedWriter/PipedReader也能够方便的在线程之间传递数据

###7.死锁优化

<1>死锁有四个必要条件,打破一个便可去除死锁。
         四个必要条件:
1.  互斥条件。 互斥条件:一个资源每次只能被一个进程使用。
2.  请求与保持条件:一个线程因请求资源而阻塞时,对已得到的资源保持不放。
3.  不剥夺条件:线程已得到的资源,在末使用完以前,不能强行剥夺。
4.  循环等待条件:若干线程之间造成一种头尾相接的循环等待资源关系

###8.其余工具操作系统

<1>CountDownLatch 同步多个任务,强制等待其它任务完成。它有两个重要方法countDown,await以及构造时传入的参数SIZE。当一个线程调用await方法的时候会挂起,直到该对象收到SIZE次countDown。一个对象只能使用一次。
    <2>CyclicBarrier 也是有一个SIZE参数。当有SIZE个线程调用await的时候,所有线程都会被唤醒。能够理解为全部运动员就位后才能起跑,早就位的运动员只能挂起等待。它能够重复利用。
    <3>DelayQueue 一个无界的BlockingQueue,用来放置实现了Delay接口的对象,在队列中的对象只有在到期以后才能被取走。若是没有任何对象到期,就没有头元素。
    <4>PriorityBlockingQueue 一种自带优先级的阻塞式队列。
    <5>ScheduledExecutor 能够把它想象成一种线程池式的Timer, TimerTask。
    <6>Semaphore 互斥锁只容许一个线程访问资源,可是Semaphore容许SIZE个线程同时访问资源。
    <7>Exchanger 生产者消费者问题的特殊版。两个线程能够在都‘准备好了’以后交换一个对象的控制权。
    <8>ReadWriteLock 读写锁。 读-读不互斥,读-写互斥,写-写互斥。

以上来自《think in java》线程

相关文章
相关标签/搜索