进程包含如下三个特征。
> 独立性:进程是系统中独立存在的实体,它可以拥有自己独立的资源,每一个进程都拥有自己私
有的地址空间。在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地
址空间。
> 动态性:进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活
动的指令集合。在进程中加入了时间的概念。进程具有自己的生命周期和各种不同的状态,这
些概念在程序中都是不具备的。
> 并发性:多个进程可以在单个处理器上并发执行,多个进程之间不会互相影响。
注意 :
并发性(concurrency)和并行性(parallel)是两个概念,并行指在同一时刻,有多条
指令在多个处理器上同时执行;并发指在同一时刻只能有一条指令执行,但多个进程指令
被快速轮换执行,使得在宏观上具有多个进程同时执行的效果。
创建线程的三种方式对比
通过继承 Thread 类或实现 Runnable、Callable 接口都可以实现多线程,不过实现 Runnable 接口与实现 Callable 接口的方式基本相同,只是 Callable 接口里定义的方法有返回值,可以声明抛出异常而已。因此可以将实现 Runnable 接口和实现 Callable 接口归为一种方式。这种方式与继承 Thread 方式之间的主要差别如下。
采用实现 Runnable、Callable 接口的方式创建多线程的优缺点:
> 线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。
在这种方式下,多个线程可以共享同一个 target 对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将 CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
> 劣势是,编程稍稍复杂,如果需要访问当前线程,则必须使用 Thread.currentThread()方法。
采用继承 Thread 类的方式创建多线程的优缺点:
> 劣势是,因为线程类已经继承了 Thread 类,所以不能再继承其他父类。
>优势是,编写简单,如果需要访问当前线程,则无须使用Thread.currentThread()方法,直接使用this 即可获得当前线程。
鉴于上面分析,因此一般推荐采用实现 Runnable 接口、Callable 接口的方式来创建多线程。
线程生命周期
sleep()
如果希望调用子线程的 start()方法后子线程立即开始执行,程序可以使用 Thread.sleep(1)来让当前运行的线程(主线程)睡眠1 毫秒 ,1 毫秒就够了,因为在这 1 毫秒内 CPU不会空闲,它会去执行另一个处于就绪状态的线程,这样就可以让子线程立即开始执行。
线程阻塞
所有现代的桌面和服务器操作系统都采用抢占式调度策略,但一些小型设备如手机则可能采用协作式调度策略,在这样的系统中,只有当一个线程调用了它的 sleep()或 yield()方法后才会放弃所占用的资源 — 也就是必须由该线程主动放弃所占用的资源。
当发生如下情况时,线程将会进入阻塞状态。
> 线程调用 sleep()方法主动放弃所占用的处理器资源。
> 线程调用了一个阻塞式 IO方法,在该方法返回之前,该线程被阻塞。
> 线程试图获得一个同步监视器,但该同步监视器正被其他线程所持有。关于同步监视器的知识、后面将有更深入的介绍。
> 线程在等待某个通知(notify)。
>程序调用了线程的 suspend()方法将该线程挂起。但这个方法容易导致死锁,所以应该尽量避免使用该方法。
解除阻塞
针对上面几种情况,当发生如下特定的情况时可以解除上面的阻塞,让该线程重新进入就绪状态。
> 调用 sleep()方法的线程经过了指定时间。
>线程调用的阻塞式 IO方法已经返回。
>线程成功地获得了试图取得的同步监视器。
>线程正在等待某个通知时,其他线程发出了一个通知。
>处于挂起状态的线程被调用了 resume()恢复方法。
线程死亡
线程会以如下三种方式结束,结束后就处于死亡状态。
> run()或 call()方法执行完成,线程正常结束。
> 线程抛出一个未捕获的 Exception 或 Error。
> 直接调用该线程的 stop()方法来结束该线程,该方法容易导致死锁,通常不推荐使用。
sleep()方法和 yield()方法的区别
> sleep()方法暂停当前线程后,会给其他线程执行机会,不会理会其他线程的优先级;
但 yield()方法只会给优先级相同,或优先级更高的线程执行机会。
>sleep()方法会将线程转入阻塞状态,直到经过阻塞时间才会转入就绪状态;
而 yield()不会将线程转入阻塞状态,它只是强制当前线程进入就绪状态。因此完全有可能某个线程调用 yield()方法暂停之后,立即再次获得处理器资源被执行。
> sleep()方法声明抛出了 InterruptedException 异常,所以调用 sleep()方法时要么捕捉该异常,要么显式声明抛出该异常;
而 yield()法则没有声明抛出任何异常。
> sleep()方法比 yield()方法有更好的可移植性,通常不建议使用 yield()方法来控制并发线程的执行。
线程同步
1.同步代码块
synchronize(obj){
...
//此处代码就是同步代码块
}
2.同步方法
public synchronize void draw(){
...
//同步方法
}
3.同步锁
class X{
// 定义锁对象
private final ReentrantLock lock = new ReentrantLock () ;
public void m (){// 定义需要保证线程安全的方法
lock. lock () ;// 加锁
try{
// 需要保证线程安全的代码
// ... method body
}
finally{// 使用 finally 块来保证释放锁
lock. unlock () ;
}
}
}
Java 线程池
Java通过Executors提供四种线程池,分别为:
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO,优先级)执行。