多线程编程,处理多线程的并发问题(线程池)

  • Post author:
  • Post category:其他








线程对象是可以产生线程的对象。比如在


Java


平台中Thread对象,Runnable对象。线程,是指正在执行的一个指点令序列。在java平台上是指从一个线程对象的start()开始,运行run方法体中的那一段相对独立的过程。相比于多进程,多线程的优势有:

(1)进程之间不能共享数据,线程可以;

(2)系统创建进程需要为该进程重新分配系统资源,故创建线程代价比较小;

(3)Java语言内置了多线程功能支持,简化了java多线程编程。




1、线程实现方式:




* 第一种方式:



*         1.自定义类,继承Thread;



*         2.重写run( )方法;



*         3.启动线程:



*             1).实例化自定义线程对象;



*             2).调用自定义线程对象的start()方法启动线程;



*



* 第二种方式:



*         1.自定义类,实现Runnable接口;



*         2.重写run( )方法:



*         3.启动线程:



*             1).实例化自定义的Runnable子类对象;



*             2).实例化一个Thread对象,将自定义对象作为参数传给Thread的构造方法;



*             3).调用Thread对象的start()启动线程;







区别:



一种是扩展java.lang.Thread类



另一种是实现java.lang.Runnable接口



好处:




在实际开发中通常以实现Runnable接口为主,因为实现Runnable接口相比继承Thread类可以避免继承的局限,一个类可以继承多个接口,适合于资源的共享。










线程和进程的区别:



进程:



每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,



一个进程包含1–n个线程。



(进程是资源分配的最小单位)。



线程:



同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。



(线程是cpu调度的最小单位)。








2、多线程




好处:



1.可以充分利用CPU资源;



2.对于线程中代码,可以独立于”主线程”单独运行,它不用等待前面的代码执行完毕;



也就意味着,我们的程序可以同时做多件事情;







问题:



1.多线程有几种实现方案,分别是哪几种?



*  三种:



*     1.继承Thread,重写run()方法;



*     2.实现Runnable接口,重写run()方法;



*     3.(JDK5)实现Callable接口,重写call()方法;



*






3.启动一个线程是run()还是start()?它们的区别?



1).start()启动线程;



2).run是普通方法,我们把线程中需要做的事情写在这里;



start方法用于启动线程,它会调用run()方法;






4.sleep( )和wait( )方法的区别:



1).sleep:



a.属于Thread类;



b.让当前线程休眠指定的时间,当时间到,会自动醒来;



c.在同步方法中,不会释放锁;



2).wait:



a.属于Object类;



b.可以指定时间,也可以不指定;当时间到时,或者使用notify()或notifyAll()方法后,会醒来;



c.在同步方法中,会释放锁;






5.为什么wait( ),notify( ),notifyAll( )等方法都定义在Object类中:



1).因为任何对象都有可能被多线程并发访问,所以,任何对象都有让当前访问的线程”等待”的权利,也有需要”唤醒”的义务;






6.线程的生命周期



新建:



用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新生状态。



处于新生状态的线程有自己的内存空间,通过调用start方法进入就绪状态(runnable)。



就绪:



处于就绪状态的线程已经具备了运行条件,但还没有分配到CPU,处于线程就绪队列,



等待系统为其分配CPU。



一旦获得CPU,线程就进入运行状态并自动调用自己的run方法。



运行:



处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。



阻塞:



处于运行状态的线程在某些情况下,如执行了sleep(睡眠)方法,或等待IO设备等资源,



将让出CPU并暂时停止自己的运行,进入阻塞状态。在阻塞状态的线程不能进入就绪队列。



死亡:



当线程的run()方法执行完,或者被强制性地终止,就认为它死去。







7.”并行”与”并发”:



1).”并行”:是指多个线程在”某个时间段内”,在同时运行;



2).”并发”:是指多个线程在”某个时间点上”,同时访问一个共享资源;








3、并发




定义:



是指多个线程在”某个时间点上”,同时访问一个共享资源;







同步的方式:



1.synchronized:



1).同步代码块;




即有synchronized关键字修饰的语句块。





被该关键字修饰的语句块会自动被加上内置锁,从而实现同步







注:





同步是一种高开销的操作,因此应该尽量减少同步的内容。




通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。






2).同步方法;




即有synchronized关键字修饰的方法。




由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,



内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。



2.(JDK5)

ReentrantLock

锁:




在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。





ReentrantLock类是可重入、互斥、实现了Lock接口的锁,





它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力。






常用方法有:







ReentrantLock() : 创建一个ReentrantLock实例







lock() : 获得锁







unlock() : 释放锁






注意:





ReentrantLock()还有一个可以创建公平锁的构造方法,但由于能大幅度降低程序运行效率,不推荐使用




例子:




//


存钱





public


void

addMoney(

int


money) {






lock.lock();


//


上锁





try


{






count

+=

money;





System.out.println(System.currentTimeMillis()

+ “存进:” +

money);









}


finally


{






lock.unlock();


//


解锁


}





}




3.使用特殊域变量(volatile)实现线程同步




a.volatile关键字为域变量的访问提供了一种免锁机制,




b.使用volatile修饰域相当于告诉

虚拟机

该域可能会被其他线程更新,



c.因此每次使用该域就要重新计算,而不是使用寄存器中的值



d.volatile不会提供任何原子操作,它也不能用来修饰final类型的变量







好处:解决了并发性访问的问题;



弊端:由于要处理线程的等待、阻塞等问题,所以效率会降低;








4、线程池




1).获取线程池对象:JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法:



public static ExecutorService newCachedThreadPool():创建一个可根据需要创建新线程的线程池



public static ExecutorService newFixedThreadPool(int nThreads):创建一个可重用固定线程数的线程池



public static ExecutorService newSingleThreadExecutor():创建一个使用单个 worker 线程的 Executor






2).操作线城池:



这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。



它提供了如下方法



Future<?> submit(Runnable task)



<T> Future<T> submit(Callable<T> task)







3).使用线程池的好处




第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。





第二:提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。





第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,





还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。





但是要做到合理的利用线程池,必须对其原理了如指掌。









4).线程池的处理流程如下





  1. 首先线程池判断


    基本线程池


    是否已满?没满,创建一个工作线程来执行任务。满了,则进入下个流程。





  2. 其次线程池判断


    工作队列


    是否已满?没满,则将新提交的任务存储在工作队列里。满了,则进入下个流程。





  3. 最后线程池判断


    整个线程池


    是否已满?没满,则创建一个新的工作线程来执行任务,满了,则交给饱和策略来处理这个任务。








5) .线程池的组成部分





一个比较简单的线程池至少应包含线程池管理器、工作线程、任务列队、任务接口等部分。







其中线程池管理器的作用是创建、销毁并管理线程池,将工作线程放入线程池中。工作线程是一个可以循环执行任务的线程,在没有任务是进行等待;任务列队的作用是提供一种缓冲机制,将没有处理的任务放在任务列队中;任务接口是每个任务必须实现的接口,主要用来规定任务的入口、任务执行完后的收尾工作、任务的执行状态等,工作线程通过该接口调度任务的执行。







线程池管理器至少有下列功能:创建线程池,销毁线程池,添加新任务。







工作线程是一个可以循环执行任务的线程,在没有任务时将等待。






任务接口是为所有任务提供统一的接口,以便工作线程处理。任务接口主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等。







6). 线程的终止(shutdown、shutdownnow)



ExecutorService线程池就提供了shutdown和shutdownNow这样的生命周期方法来关闭线程池自身以及它拥有的所有线程。



shutdown:





(1)线程池的状态变成SHUTDOWN状态,此时不能再往线程池中添加新的任务,否则会抛出RejectedExecutionException异常。






(2)线程池不会立刻退出,直到添加到线程池中的任务都已经处理完成,才会退出








shutdown做了几件事:






1. 检查是否能操作目标线程







2. 将线程池状态转为SHUTDOWN






3. 中断所有空闲线程







shutdownNow:





(1)线程池的状态立刻变成STOP状态,此时不能再往线程池中添加新的任务。







(2)终止等待执行的线程,并返回它们的列表;






(3)试图停止所有正在执行的线程,试图终止的方法是调用Thread.interrupt()


,但是大家知道,





如果线程中没有sleep 、wait、Condition、定时锁等应用, interrupt()方法是无法中断当前的线程的。





所以,ShutdownNow()并不代表线程池就一定立即就能退出,





它可能必须要等待所有正在执行的任务都执行完成了才能退出。










版权声明:本文为qq_32816979原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。