参考书籍:疯狂JAVA讲义
1.进程和线程;
进程是处于运行过程中的程序;并且具有一定的独立功能;进程是系统进行系统资源分配和调度的一个独立单位.
一般而言,进程包括以下三个特征:
独立性:进程是系统中存在的实体,它可以拥有自己独立的资源,每一个进程都拥有自己私有的地址空间.在没有经过进程本身允许的情况下,一个用户进程不可以访问其他进程的地址空间.
动态性:进程与程序的区别在于,程序是一个静态的指令集合,而进程是一个正在系统中活动的指令集合.在进程中加入了时间的概念,进程具有自己的生命周期和不同的状态,这些概念都是程序不具备的.
并发性:多个进程可以在单个处理器上并发执行,多个进程之间不会互相影响.
什么是并发?
并发指的是同一时刻只有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果.
线程是进程的执行单元,当进程被初始化后,主线程就创建了.
操作系统可以同时执行多个任务,每一个任务就是一个进程,进程可以同时执行多个任务,每个任务就是线程.
2.线程的创建和启动;
JAVA使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例.
2.1,继承Thread创建线程类
--定义Thread的子类,并重写run();该方法的方法体就代表了线程需要完成的任务,因此把run()称为线程执行体.
--创建该类的实例,即创建了线程对象.
--调用线程对象的start();启动线程.
2.2,实现Runnable接口创建线程类
--定义Runnable接口的实现类,并重写该接口的run();该方法的方法体同样是该线程的线程执行体.
--创建Runnable实现类的实例,并以此作为Thread的参数来创建Thread对象,这个对象才是线程对象.
--调用线程对象的额start();启动线程.
2.3,使用Callable和Future创建线程
--创建Callable接口的实现类,并实现call();该call()将作为线程执行体,且该call()有返回值,再创建Callable实现类的实例.
--使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()的返回值.
--使用FutureTask对象作为Thread的参数创建并启动线程.
--调用FutureTask对象的get()来获得子线程执行结束后的返回值.
- 线程的生命周期;
3.1,新建状态(New):新创建了一个线程对象。
3.2,就绪状态(Runnable):当线程对象调用了start();该线程处于就绪状态;并没有开始运行,只是表明该线程可以运行了,至于什么时候开始运行,取决于JVM里的线程调度器的调度.
注意:这里不能直接调用run();否则系统把线程对象当作普通对象,把run()当成普通方法,而不是线程执行体.
3.3,运行状态(Running):就绪状态的线程获取了CPU,执行run()的线程执行体.
3.4,阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权;暂时停止运行。直到线程进入就绪状态.才有机会转到运行状态.阻塞的情况分三种:
(一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中;(wait()会释放持有的锁);
(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中;
(三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态,当sleep()状态超时,join()等待线程终止或者超时,或者I/O处理完毕时,线程重新转入就绪状态;(注意:sleep()是不会释放持有的锁).
3.5,死亡状态(Dead):run(),call()执行完毕,或抛出未捕获异常,该线程结束生命周期。
/**为了测试某个线程是否死亡,可以调用线程对象的isAlive(),当线程处于,就绪,运行,阻塞状态时返回ture,在新建和死亡状态时返回false*/
4.控制线程;
4.1,守护线程
当程序中只剩下守护线程时,他会自杀;
JVM的垃圾回收线程就是守护线程;
调用Thread的setDaemon(true);可以将指定线程设置成守护线程.
注意:要将某个线程设置成守护线程,必须在该线程启动之前,即start()调用之前.
4.2,线程睡眠
通过调用Thread的static方法sleep()让当前的线程进入暂停一段时间,进入阻塞状态.
4.3,线程让步
yield()方法也是Thread的static方法,他不会使该线程进入阻塞状态,只是转入就绪状态,完全有可能的是:当某个线程调用了yield()暂停之后,线程调度器又将其调度出来重新执行;
注意:只有优先级和当前线程相同或者比当前线程更高的处于就绪状态的线程才会获得执行的机会.
4.4,改变线程优先级
每个线程执行时都有一定的优先级,优先级高的线程获得较多的执行机会,而优先级较低的线程则获得较低的执行机会,;默认情况下main线程具有优先级,由main线程创建的子线程也具有优先级;
Thread类提供了setPriority(int newPriority);getPriority();方法来改变和获取线程的优先级,范围在1-10之间.
4.5,join()线程
Thread提供了一个线程等待另一个线程执行的方法,当在某个程序的执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到被join()方法加入的线程执行完为止.
Join():等待join线程执行完成
Join(long millis):等待join线程完成时间最多为millis毫秒,如果在millis毫秒内该线程还没有结束,则不再等待.
5.线程同步;
5.1,线程安全问题
这个案例中,你和你老婆相当于俩个并发线程,都在修改存款,系统在判断你的条件满足后正好执行线程切换,切换给你老婆去执行,所以会产生问题.
5.2,同步代码块
为了解决这个问题,java的多线程引入了同步监视器来解决这个问题.
同步监视器的目的:组织2个线程对同一个共享资源进行并发访问.
synchronized(obj) {
//此处的代码就是同步代码块
}
上述obj就是同步监视器,当线程开始执行同步代码块之前,必须获得对同步监视器的锁定.
注意:任何时刻只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完毕后,该线程会释放对该同步代码块的锁定.
5.3,同步方法
即使用synchronized修饰某个方法(非static修饰),则该方法就是同步方法,无需指定同步监视器,同步方法的同步监视器是this,也就是调用该方法的对象.
5.4,释放同步监视器的锁定
线程在如下情况会释放对同步监视器的锁定:
--当线程的同步方法,同步代码块执行结束,会释放对同步监视器的锁定.
--当前线程在同步代码块,同步方法中出现了break,return终止了该代码块,方法的执行,会释放对同步监视器的锁定.
--当前线程在同步代码块,同步方法中出现了未处理的Exception,Error,导致了该方法,代码块异常结束,会释放同步监视器的锁定.
--当前线程在同步代码块,同步方法中执行了同步监视器对象的wait(),则当前线程暂停,并释放同步监视器的锁定.
在如下所示的情况下,线程不会释放对同步监视器的锁定:
--线程在执行同步代码块,同步方法时,程序调用Thread.sleep();Thread.yield();暂停当前线程的执行,当前线程不会释放同步监视器的锁定.
5.5,死锁
当2个线程互相等待对方释放同步监视器时就会发生死锁.
5.6,线程池
系统启动一个线程池的成本是比较高的,因为它涉及到与操作系统交互,在这种情况下使用线程池可以很好的提高性能.
线程池在系统启动时即创建了大量空闲的线程,程序将一个Runnable对象或者Callable对象传给线程池,线程池就会启动一个线程来执行他们的run();或者call();当方法结束后,线程不会死亡,而是返回线程池中成为空闲状态,等待下一个对象.
使用Executors工厂类来产生线程池,该工厂类包含如下几个静态方法来创建线程池:
Executors.newCachedThreadPool();创建一个具有缓存功能的线程池,系统根据需要创建线程,这些线程将会被缓存在线程池中.
Executors.newFixedThreadPool(int nThreads);创建一个可重用的,具有固定线程数的线程池.
Executors.newSingleThreadExecutor();创建一个只有单线程的线程池,相当于调用newFixedThreadPool(),方法参数传1.
以上三个方法,返回一个ExecutorService对象,该对象代表一个线程池.
Executors.newScheduledThreadPool(int corePoolSize);创建具有指定线程数的线程池,它可以在指定延迟后执行任务,corePoolSize指的是线程池中所保存的线程数.
此方法返回一个ScheduledExecutorService线程池,它是ExecutorService的子类,它可以在指定延迟后执行任务.
使用线程池来执行线程任务的步骤
5.6.1,调用Executor的静态工厂方法创建一个ExecutorService对象,该对象代表一个线程池;
5.6.2,创建Runnable实现类的实例,作为线程执行任务;
5.6.3,调用ExecutorService对象的submit()方法来提交Runnable实例.
5.6.4,当不想提交任何任务时,调用ExeccutorService对象的shutdown();来关闭线程池.