版权声明:此文章转载自infocool
原文链接:http://www.infocool.net/kb/Java/201607/168795.html
如需转载请联系听云College团队成员小尹 邮箱:yinhy#tingyun.com
一、进程与线程介绍
进程是正在执行中的程序,每一个进程的执行都是一个执行的顺序,该顺序就是一个执行路径或者叫控制单元。
线程是进程中一个独立的控制控制单元,线程在控制着进程的执行,一个进程中至少有一个线程。Java虚拟机启动时会启动一个java.exe的进程,该进程中至少有一个线程来负责java程序的执行,而且这个程序运行的代码存在与main方法中,该线程称之为主线程。
进程是程序在计算机上的一次执行活动。当运行一个程序时,系统就启动了一个进程。程序是死的(静态的),进程是活的(动态的)。
区别:
1、进程内的线程共享所在进程的地址空间。而每个进程都有自己独立的地址空间。
2、进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源。
3、线程是处理器调度的基本单位,但进程不是。
4、一个程序至少有一个进程,一个进程至少有一个线程.
二、线程的创建方式
1、创建线程的第一种方式:继承Thread类。步骤为:
(1)定义类继承Thread类
(2)覆Thread类中的run方法,目的是将自定义的代码存储在run方法中让线程执行
(3)建立子类对象的同时也创建了线程对象,调用线程的start方法,该方法的作用是开启线程,调用run方法。
当存在多线程时,每次运行的结果都不同,是因为多个线程都在抢夺cpu的执行权,cpu在做着快速的切换,已达到看上去同时执行的效果,这就是多线程的一个特性:随机性。
2、创建线程第二种方式:声明实现Runnable接口。步骤为:
(1)定义类实现Runnable接口
(2)覆盖Runnable接口中的run方法,将线程要运行的代码存入在该run方法中。
(3)通过Thread类建立线程对象
(4)将Runnable接口的子类对象作为实际参数传递给Thread类中的构造函数,因为自定义的run方法所属的对象是Runnable接口的子类对象,所以要让线程去执行指定对象的run方法,必须明确run方法所属的对象。
(5)调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。
3、实现方式与继承方式的区别:实现方式避免了继承的局限性(单继承),在定义线程时建议使用实现方式,继承方式线程代码存在于Thread的子类run方法中,实现方式的线程代码存放在Ruannable接口的子类run方法中
4、线程的五种运行状态:被创建、运行、冻结、阻塞、消亡。线程被创建后通过调用start方法让线程开始执行,线程进入运行状态,通过调用sleep或者wait方法,线程进入冻结状态,当调用sleep方法时,时间一到线程结束冻结状态,当调用wait方法时,使用notify或notifyAll使线程推出冻结状态,线程推出冻结状态后不一定会抢到cpu的执行权,从而进入阻塞状态,当线程执行完则消亡。其中运行状态既有执行资格又有执行权,阻塞状态是有执行资格但没有执行权,冻结状态没有执行资格。
5、线程池:线程池顾名思义就是装着线程的池子,线程池在系统启动时就会创建大量空闲线程,程序将一个Runnable对象传给线程池, 线程池就 会启动一条线程来执行该对象的run方法,当run方法执行结束后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个Runnable对象的run方法。提高系统性能,对于请求量很少的时候看起来没多少作用。但是当 系统使用人数多了,请求数量很多的时候就会为系统节约大量的资源,让系统不去忙于线程的创建和销毁,而让系统更好的 完成他的功能。除此之外,线程池可以通过设置参数来控制系统中并发线程数目保证系统性能。使用线程池来执行线程的步骤:
(1)调用Exectors类的静态工厂方法创建一个ExectorService对象,该对象代表一个线程池。
(2)创建Runnable实现类的实例,作为线程执行任务。
(3)调用ExectorService的submit方法来提交Runnable实例。
(4)当不想提交任何任务时调用ExectorService对象的shutdown方法来关闭线程池。
三、线程的安全问题
1、当多条语句执行同一个线程的共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行,导致共享数据的错误。我们采取的解决办法就是采用同步代码块或者同步函数,让一个线程执行完所有操作共享数据的语句,在执行过程中,其他线程不可以参与执行,同步代码块的格式为synchronized(对象){需要同步的代码},其中的对象就像一把锁,持有锁的线程可以在同步中执行,没有锁的线程即使获得cpu的使用权,也进不去,因为没有锁。
同步的前提是必须要有两个或两个以上的线程,同时这些线程要使用同一把锁,当我们在写同步代码块时首先要明确哪些代码是多线程要运行的代码,再明确哪些是共享数据,最后 明确多线程运行代码中哪些语句是操作共享数据的。
同步函数中并没有明确锁,但我们通过代码实现可以得出同步函数使用的锁是所属对象的引用,既this,而当同步函数被static所修饰时,使用的锁是该方法所在函数所在类的字节码文件对象,因为静态进入内存时没有对象,但类对应的字节码文件却已经加载进了内存。
2、同步中嵌套同步而锁不同,容易发生死锁。
四、线程间通讯与结束
1、多线程中的等待唤醒机制:wait,notify,notifyAll三种方法都使用在同步中,因为它们都要对持有锁的线程进行操作,只有在同步中才有锁的概念,而把他们定义在object类中则是因为这些方法在操作同步线程时,都必须要标识它们所操作线程持有的锁,只有同一个锁上的被等待线程才能被同一个锁的notify或者notifyAll所唤醒,也就是说等待和唤醒必须是同一把锁,而锁可以是任意对象,所以把可以被任意对象调用的方法定义在object类中。
2、当两个线程同时操作同一个资源时,我们通过if语句判断标记,使一个线程wait同时使用notify方法唤醒另一个线程,从而实现两个线程同时运行的目的,当有两个以上的线程时,为了保证每个线程交互执行,需要用while语句判断标记,同时使用notifyAll方法唤醒所有等待的线程,以保证不会出现所有线程都处于等待的情况。
3、结束线程:线程中定义了stop方法,但已经过时了,因此我们要结束线程需要用到其他的方法,通常我们在线程中定义的线程代码都是循环结构,只要控制住循环我们就可以结束run方法,线程也就结束了,有了这个思想,我们就可以在线程中定义标记,通过改变标记的方法结束线程,再者在Thread类中提供了interupt方法,可以强制清除冻结状态,这种方法一般用于同步中,当线程处于冻结状态,就不会读取到标记,那么线程就不会结束了,当没有指定的方式让冻结的线程回复到运行状态,我们就可以用此方法对冻结进行清除,这样就可以让线程强制恢复到运行状态中来,我们调用此方法的目的在于让线程停止,所以可以在抛出异常的处理代码中改变标记,从而使线程结束。
五、Thread类的方法简介
Thread类中的其他方法:Thread类中还有一些我们经常用到的方法,setDaemon(boolean)方法是设置线程为守护线程,当运行的线程都是守护线程时,JVM退出程序,换句话说就是当前线程结束,守护线程会随之结束,该方法要在开启线程前使用。join方法的作用是让主线程释放执行权,等待调用该方法的线程运行结束后才会获得执行权。toString方法覆盖了object类中的方法,用于输出线程的名称、优先级和线程组,优先级的设置我们可以通过setPriority方法实现,同时在Thread类中也对优先级的三个常用值进行了封装,分别为MAX_PRIORITY最高优先级(10)、NORM_PRIORITY默认优先级(5)、MIN_PRIORITY最低优先级(1),此外静态的yield方法可以暂停当前线程,执行其他线程