多线程及线程池学习心得
一、线程的应用与特点
多线程是程序员不可或缺的技术能力,多线程技术在各个方面都有应用,特别在性能优化上更是起到至关重要的作用。但是,如果多线程写得不好,往往会适得其反,特别是高并发时会造成阻塞、超时等现象。多线程具有以下特点:1、独立性,拥有自己独立的资源,拥有自己私有的地址空间;2、动态性,进程具有自己的生命周期和各种不同的状态;3、并发性,多个进程可以在单个处理器上并发执行,不会相互影响,并行是指同一时刻有多条指令在多个处理器上同时执行。线程是进程的组成部分,一个进程可以拥有多个线程,一个线程必须有一个父进程。
二、线程的创建
创建线程有两种方法:1、继承Thread类创建线程类,重写其run()方法。2、实现Runnable接口,重写run()方法。
两种创建线程方式的对比:实现Runnable接口,还可以继承其他类,同时还可以多个线程共享一个对象、同一个资源,但是要访问当前线程必须使用Thread.currentThread()方法;而通过继承Thread类,直接通过this即可获得当前线程。
三、线程的生命周期
在线程的生命周期中,经过新建、就绪、运行、阻塞、死亡五种状态。当程序使用关键词new创建一个线程后,该线程就处于新建状态。当对象调用start()方法后,该线程处于就绪状态,表示该线程可以运行了,至于什么时候运行取决于JVM线程调度器的调度(注意:启动线程使用start()方法,而不是run()方法,如果调用run()方法,系统就会把线程对象看做一个普通对象,而run()方法也是一个普通方法,会被立即执行,无法实现多线程并发执行)。当处于就绪状态的线程获得CPU,开始执行run()方法的执行体,该线程就处于运行状态。当正在执行的线程被阻塞后,其他线程可以获得执行的机会,被阻塞的线程在合适的时候重新进入就绪状态;当run()方法执行完成、线程抛出未捕获的异常或调用stop()方法时,线程处于死亡状态,处于死亡状态的线程将不能调用start()重新启动。
四、线程的调度
线程主要分为前台线程、守护线程(后台线程、精灵线程)两种。
后台线程的特征:前台线程都死亡,后台线程自动死亡,Thread对象调用setDaemon(true)方法设置成后台线程,必须放在调用start()方法之前。
Thread提供一个让线程等待另一个线程完成的方法,join()方法,当在某个程序执行流中调用其他线程的join()方法,调用线程被阻塞,直到join线程完成为止。
线程睡眠sleep()和线程让步yield()的区别:sleep()暂停当前线程,会给其他线程执行机会,不理会其他线程的优先级,而yield()只会给优先级相同或更高的线程执行机会,sleep()会让线程进入阻塞状态,而yield()让线程进入就绪状态,sleep()方法比yield()方法有更好的移植性。
线程执行时都具有一定的优先级,优先级高的线程获得较多的执行机会,而优先级较低的线程则获得较少的执行机会。每个线程默认的优先级都与创建它的父线程具有相同的优先级,main线程具有普通优先级,通过setPriority(int priority)和getPriority()来设置和获取优先级,MAX_PRIORITY的值为10,MIN_PRIORITY的值为1,NORM_PRIORITY的值为5。
五、线程安全
当两个或以上线程同时并发修改一个资源时,会发生线程安全问题,为了解决线程安全问题,多线程引入同步监听器,其通用的方法就是同步代码块synchronized,通过这种方式就可以保证并发线程在任何时刻只有一条线程可以进入。同步方法,使用synchronized关键字修饰某个方法,通过使用同步方法可以将某类变成线程安全类。此外,还有同步锁(Lock),它通过显式定义同步锁对象实现同步,同步锁应当使用Lock对象充当,Lock是控制多个线程对共享资源进行访问的工具,每次只能有一个线程对Lock对象加锁,建议使用finally块确保必要时释放锁。
死锁,当两个线程互相等待对方释放同步监视器时就会发生死锁,所以多线程编程时应该采取措施避免死锁现象。
六、线程的协调运行
对于使用synchronized实现同步,即使用隐式同步监视器,可以使用wait()、notify()、notifyAll()实现协调运行,wait()当前线程等待,直到其他线程调用该同步监视器的notify()或notifyAll()方法来唤醒该线程,notify()唤醒此同步监视器上等待的单个线程,如果有多个线程在该线程等待,则会选择唤醒其中一个线程,notifyAll()唤醒此同步监视器上所有等待的线程。而对于显式同步监视器,可以通过特定的Lock实例获得Condition实例,调用Lock对象的newCondition()方法即可,同样Condition提供三种协调方法:await()、signal()、signalAll()。
七、线程池
线程池在系统启动即创建大量空闲的线程,程序将一个Runnable对象传给线程池,线程池就会启动一条线程来执行该对象的run()方法,当run()方法结束后,该线程并不会死亡,而是再次返回线程池成为空闲状态。JDK1.5提供了一个Executors工厂类来产生线程池,该工厂类提供5种静态方法来创建线程池:
1、newCachedThreadPool():创建一个具有缓存功能的线程池,系统根据需要创建线程,将这些线程缓存在线程池中。
2、newFixedThreadPool(int nThreads):创建一个可重用的、具有固定线程数的线程池。
3、newSingleThreadExecutor():创建一个只有单线程的线程池,相当于newFixedThreadPool(1)。
4、newScheduleThreadPool(int corePoolSize):创建具有指定线程数的线程池,它可以在指定延迟后执行任务。
5、newSingleThreadScheduleExecutor():创建只有一条线程的线程池,他可以在指定延迟后执行任务。
前3个方法返回一个ExecutorService对象,代表尽快执行线程的线程池,而后2个方法返回一个ScheduleExecutorService对象,可以再指定延迟后执行线程任务。