一.进程与线程进程:一个应用程序(exe文件)。单进程:一个时刻只能够执行一个应用程序。多进程:操作系统能够同时执行多个应用程序。因为CPU在某一个时刻只能够被一个程序所使用,本质上是因为CPU在以一个飞快速度在各个进程(应用程序)之间进行切换。线程:一个应用程序(进程)的执行分支(应用程序的分支功能)。线程与进程的区别:每个进程都需要操作系统为其分配独立的内存地址空间,而同一进程中的所有线程在同一块地址空间中工作,这些线程可以共享同一块内存和系统资源。 二.线程的生命周期及五种基本状态
关于Java中线程的生命周期,首先看一下下面这张较为经典的图:
上图中基本上囊括了Java中多线程各重要知识点。掌握了上图中的各知识点,Java中的多线程也就基本上掌握了。主要包括:
新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();
1. 继承Thread类,并重载run()方法;每个线程都能拥有独立的一份(数据不共享),但会受到java里面单继承(一个子类只能够去继承一个父类)的约束,不利于功能更深扩展。
2. 实现runnable接口,并调用Thread提供的构造函数。各个线程之间实现数据的共享,不仅可以实现多个接口(功能扩展丰富),还可以继承别的类。
就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;
运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:
1.等待阻塞 -- 运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
2.同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
3.其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
三、状态间的相互转变
1.创建并运行线程
当调用start方法后,线程开始执行run方法中的代码。线程进入运行状态。可以通过Thread类的isAlive方法来判断线程是否处于运行状态。当线程处于运行状态时,isAlive返回true,当isAlive返回false时,可能线程处于等待状态,也可能处于停止状态。 2.挂起和唤醒线程
一但线程开始执行run方法,就会一直到这个run方法执行完成这个线程才退出。但在线程执行的过程中,可以通过两个方法使线程暂时停止执行。这两个方法是suspend和sleep。在使用suspend挂起线程后,可以通过resume方法唤醒线程。而使用sleep使线程休眠后,只能在设定的时间后使线程处于就绪状态(在线程休眠结束后,线程不一定会马上执行,只是进入了就绪状态,等待着系统进行调度)。
虽然suspend和resume可以很方便地使线程挂起和唤醒,但由于使用这两个方法可能会造成一些不可预料的事情发生,因此,这两个方法被标识为deprecated(抗议)标记,这表明在以后的jdk版本中这两个方法可能被删除,所以尽量不要使用这两个方法来操作线程。下面的代码演示了sleep、suspend和resume三个方法的使用。
3.终止线程的三种方法
①使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
②使用stop方法强行终止线程(这个方法不推荐使用,因为stop和suspend、resume一样,也可能发生不可预料的结果)。
③使用interrupt方法中断线程。 四、线程的几个方法:
join():等待此线程死亡后再继续,可使异步线程变为同步线程。
interrupt():中断线程,被中断线程会抛InterruptedException。
线程通信:wait(),notify():
wait() 表示等待获取锁,执行了该方法的线程释放对象的锁,JVM会把该线程放到对象的等待池中,该线程等待其它线程唤醒。 notify() 执行该方法的线程唤醒在对象的等待池中等待的一个线程,JVM从对象的等待池中随机选择一个线程,把它转到对象的锁池中,使线程由阻塞队列进入就绪状态。
sleep():让当前正在执行的线程休眠,有一个用法可以代替yield函数——sleep(0)。
yield():暂停当前正在执行的线程对象,并执行其他线程。也就是交出CPU一段时间。
sleep和yield区别: 1、sleep()方法会给其他线程运行的机会,而不考虑其他线程的优先级,因此会给较低线程一个运行的机会;yield()方法只会给相同优先级或者更高优先级的线程一个运行的机会。
2、当线程执行了sleep(long millis)方法后,将转到阻塞状态,参数millis指定睡眠时间;当线程执行了yield()方法后,将转到就绪状态。
3、sleep()方法声明抛出InterruptedException异常,而yield()方法没有声明抛出任何异常。
4、sleep()方法比yield()方法具有更好的移植性。
如果希望明确地让一个线程给另外一个线程运行的机会,可以采取以下的办法之一: 1、 调整各个线程的优先级;
2、 让处于运行状态的线程调用Thread.sleep()方法;
3、 让处于运行状态的线程调用Thread.yield()方法;
4、 让处于运行状态的线程调用另一个线程的join()方法。
五、数据同步:
线程同步的特征:
1、 如果一个同步代码块和非同步代码块同时操作共享资源,仍然会造成对共享资源的竞争。因为当一个线程执行一个对象的同步代码块时,其他的线程仍然可以执行对象的非同步代码块。(所谓的线程之间保持同步,是指不同的线程在执行同一个对象的同步代码块时,因为要获得对象的同步锁而互相牵制);
2、 每个对象都有唯一的同步锁;
3、 在静态方法前面可以使用synchronized修饰符;
4、 当一个线程开始执行同步代码块时,并不意味着必须以不间断的方式运行,进入同步代码块的线程可以执行Thread.sleep()或者执行Thread.yield()方法,此时它并不释放对象锁,只是把运行的机会让给其他的线程;
5、 Synchronized声明不会被继承,如果一个用synchronized修饰的方法被子类覆盖,那么子类中这个方法不在保持同步,除非用synchronized修饰。