通过继承Thread类或者实现Runnable接口来实现多线程,
一、扩展java.lang.Thread类
使用继承Thread类来实现多线程,调用start方法的时候会使线程进入可运行态,具体代码运行由系统决定,start方法不能重复调用,会产生java.lang.illegalThreadStateException异常
二、实现Runnable接口
实现Runnable接口实现多线程,只需要重新run方法即可,启动多线程的时候,需要先通过Thread类的构造方法来创建对象,然后调用Thread对象的run方法执行代码。
三、Thread和Runnable的区别
若继承Thread类则不适合资源共享,但是实现Runnable接口就比较容易实现资源共享。
实现Runnable接口比Thread所具有的优势
1、适合相同的程序代码的线程去处理同一个资源
2、可以避免java单继承的限制
3、增加健壮性,代码可被多个线程共享,代码、数据独立。
4、线程池只能放入实现Runnable或callable的线程,不能直接放入Thread类。
java中线程都是同时启动的,具体代码的执行顺序,执行时间都是看那个线程先抢占到cpu资源,java中最少会有两个线程,一个是main方法的线程,一个是垃圾回收的线程。
四、线程状态转换
线程的几个阶段:
1、新建状态(New):创建了一个线程
2、就绪状态(Runnable):线程创建后调用了start方法,该在状态的线程位于可运行线程池,等待获取CPU的使用权。
3、运行状态(Run):就绪状态的线程获取到了CPU试用权,执行代码
4、阻塞状态(Blocked):阻塞状态的线程四因为某种原因放弃了CPU的使用权,暂时停止运行,直到线程进入就绪状态才有机会进去运行状态,阻塞的情况分三种:
4.1、等待阻塞:运行线程的wait()方法,JVM会把线程放入等待池中(会释放掉所有的锁)
4.2、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被其他线程占用,JVm 会把线程放入锁池中。
4.3、其他阻塞:运行的线程执行了sleep()或join方法,或发出了I/O请求时,JVM会把线程转为阻塞状态,当sleep()超时、join等待线程终止或超时或IO处理完后充新转入就绪状态(sleep不会释放持有的锁)
5、死亡状态(Dead):线程执行完毕或因异常终止退出了run方法,该线程结束生命周期。
五、线程调度
1、调整优先级:
优先级高的能获得较高的运行机会
java线程的优先级用数组表示,取值范围是1~10,Thread有三个常量:
static int MAX_PRIORITY:线程最高优先级取值为10
static int MIN_PRIORITY:线程最低优先级,取值为1
static int NORM_PRIORITY:线程默认优先级,取值为5
Thread类的setPriority()和getPriority()分别设置和获取的优先级
线程的优先级有继承关系,若A继承了B,则B的优先级就和A相同
JVM提供10个线程优先级,但是在常见系统中无法很好的映射,若需要程序能够移植到其他系统中,则需要使用系统优先级的三个常量
2、线程睡眠:
Thread.sleep(long millis)方法,使线程进入到阻塞状态,millis为睡眠时间(毫秒),睡眠结束后转入runnable状态,sleep()平台一致高。
3、线程等待:
Object中的wait()方法,导致当前线程等待,直到其他地方调用了该对象的notify方法,或调用notityAll()唤醒方法。
4、线程让步:
Thread.yield()方法暂停正在执行的线程对象,把执行机会让给优先级更高或相同的线程运行。
5.线程加入:
join()等待其他线程终止,在当前线程中调用另一个线程的join方法,则当前线程变为阻塞状态,知道另外一个线程结束,当前进行就由阻塞状态转回就绪状态。
6、线程唤醒:
Object类的notify()方法,唤醒此对象监控器上等待的单个线程,若所有线程都在这一对象上等待,那么会选择一个进行唤醒。线程通过调用其中一个wait()方法,在对象监视器上等待。直到线程放弃此对象上的锁定,才能继续执行被唤醒的线程,被唤醒线程将以常规的方式在该对象上主动同步其他线程进行竞争
六、常用函数
1、sleep(long millis):指定时间(毫秒)的线程休眠(暂停执行)
2、join():等待子线程终止
使用方式:join是Thread类的一个方法,在线程启动后直接调用,作用是“等待该线程结束”,该线程指主线程等待子线程的终止,就是子线程调用了join方法后,join后面的代码,只有等到子线程结束后才会执行
为什么要用join()方法:主线程生成并启动了子线程,子线程需要进行大量耗时的计算,主线程往往会在子线程结束之前结束掉,在主线程需要用到子线程计算的值时,往往需要用到这个join方法。
3、yield():暂停当前正在执行的线程对象,并执行其他线程。
yield()做的是让当前线程回到可运行状态,以允许具有相同优先级的其他获得允许机会。因此yield的目的是让相同优先级的线程之间适当的轮换执行,但是有可能会被线程调度程序再次选中。
yield()从未导致线程转换到等待/休眠/阻塞状态,大多数情况下从运行状态转换到可运行状态,但有时因为调度原因可能会没有效果。
yield()和sleep()的区别:
1.执行状态不同:sleep()使线程休眠,进入停滞状态,指定时间内肯定不会被执行;yield()只是使当前线程进入到可执行状态,有可能会进入到可执行状态后马上执行。
2.执行时间:sleep()使线程休眠一段时间,这段时间由程序控制,yield()是使当前线程让出CPU控制权,让出的时间不确定(对应如下操作,检测是否有相同优先级的线程处于可运行状态,如有则把CPU占有权交给这个线程,否则继续执行原线程,yield()又叫退让,把运行机会交给同优先级的其他线程)
3.执行优先级不同:sellp()允许较低优先级线程获取执行机会,但yield()执行时当前线程处于可运行状态,所以不可能让优先级低的线程获取CPU的使用权限。在一个系统中,如果一个较高优先级的线程没有调用sleep()方法,也没有受到I/O阻塞,那么优先级低的进程只能等待优先级高的进程执行完毕才有机会运行
4、setPriority()更改线程优先级
MAX_PRIORITY=10
MIN_PRIORITY=1
NORM_PRIORITY=5
5、interrupt():线程发送一个终断的信号,让线程在无限等待(死锁)时能抛出异常(InterruptdException),从而线程结束,但是你吃掉这个异常的话,这个线程还是不会终断的,
6、wait():obj.wait()与obj.notify()必须要与synchronized(obj)一起使用,也就是说wait()和notitty()是针对已经取得Obj锁的线程进行操作,语法角度来说就是wait()和notity()必须在代码块synchronized(obj){......}中;从功能上来说,wait()就是线程在获取对象锁后主动释放对象锁,并且本线程休眠,直到其他线程调用了该对象的notity()唤醒该线程才能继续获取对象锁,并继续执行,对应的notity()就是唤醒对象锁的操作;调用notity()后并不是马上获取到对象锁,而是在synchronized(){....}语句块执行结束,自动释放对象锁后,JVM会在wait()的对象锁的线程中随机选取一个线程赋予对象锁并唤醒线程,这样就提供了在线程间同步、唤醒的操作,Thread.sleep()和Object.wait()都能暂停当前线程,释放CPU资源,主要区别在于wait()释放CPU资源的时候同时释放了对象锁的控制。
7、sleep和wait的异同
相同点:
1、都是在多线程的环境下,都可以在程序调用处阻塞指定毫秒数并返回
2、都可以通过interrupt()方法打断线程的暂停状态(不建议使用该方法),从而抛出InterruptdException异常(InterruptdException是自己从内部抛出的,对某一线程调用interrupt()方法时,若线程在执行普通的代码时不会抛出,但是线程在进入到wait()/sleep()/join()就会抛出这个异常)
异同点:
1、Thread类的方法:sleep(),yield()
Object方法:wait()和notity()等方法
2、每个对象都有一个锁类控制访问,synchronized关键字可以和对象锁交互,实现线程同步
sleep()方法没有释放对象锁,而wait()释放了对象锁,使得其他线程可以使用同步控制块和方法
3、wait(),notity()和notityAll()方法只能在同步控制方法或同步控制代码块中使用,而sleep()可以在任何地方使用
4、sleep()使用时必须捕获异常,而wait(),notity()和notityAll()不需要捕获异常
sleep()和wait()方法最大区别在于:
sleep()休眠时保持对象锁,并占有该对象锁
wait()睡眠时释放对象锁
sleep()方法:
使当前线程进入停滞状态(阻塞当前线程),让出CPU的使用,目的是不让当前线程独自霸占改进程的CPU资源,留给其他线程运行的机会
sleep()是Thread类的静态方法,因此不能改变对象的锁,虽然休眠了但是锁并未释放,其他线程无法访问这个对象
在sleep()休眠结束后并不一定会立即执行,因为其他线程可能会处于运行状态,没有被调度为放弃执行,除非当前线程的优先级更高
wait()方法:
wait()是Object里面的方法,当一个线程执行到wait()时,它就进入到一个和该对象相关的线程池中,同时释放对象的锁(暂时失去对象锁,wait(long time)超时时间到后还会返回对象锁),其他线程可以访问;
wait()使用notity()、notityAll()或指定时间来唤醒当前等待池中的线程
wait()必须放在synchronized(){...}代码块中否则会在程序运行时抛出“java.lang.IllegalMonitorStateException"异常
七、常见线程名词
主线程:JVM调用main函数产生的线程
当前线程:一般指通过Thread.currentThread()来获取的线程
后台线程:指为其他线程提供服务的线程,也叫守护线程,JVM的垃圾回收线程就是一个后台进程,用户线程和守护线程区别在于是否等待主线程,依赖于主线程结束而结束
前台进程,接收后台服务的线程,前台线程创建的线程默认也是前台线程,可以通过isDaemon()和setDaemon()方法来判断和设置一个线程是否为后台线程
线程类的常用方法
sleep(long n):强迫一个线程休眠n毫秒
isAlive():判断一个线程是否处于活动状态
join():等待线程终止
activeCount():程序活跃的线程数
enumerate():枚举中线程的数量
currentThread():得到当前线程
isDaemon():判断是否为守护线程
setDeamon():设置一个线程是否为守护线程
setName():为线程设置一个名称
wait():强制一个线程等待
notity():通知一个线程继续运行
setPriority():设置一个线程的优先级
八、线程同步
1、synchronized()作用域有三种:
1.某对象实例内,synchronized aMethond(){}可以防止多个线程同时访问对象的synchronized()方法(一对象有多个synchronized()方法,只要一个线程访问了其中的synchronized()方法其他线程不能同时访问这个对象的其他任何一个synchronized()方法)
2、某个类的范围,synchronized static aStaticMethond{}防止多个线程同时访问这个类中的synchronized 静态方法,可以对类的所有对象实例起作用
3、synchronized()方法是不继承的,基类方法是synchronized m(){}在继承类中时m(){},需要手动知道某个方法为synchronized方法
synchronized可以作为函数的修饰符,也可以作为函数内的语句,也就是同步方法和同步语句块,再细的分类,synchronized可作用于instance变量,Object reference(对象引用),static函数,class literals(类名称字面常量)上。
若一个类中定义了一个synchronized的static 函数A,和一个synchronized的instance函数B,那么这一个类的同一对象Obj在多线程中分别访问A、B两个方法时不构成同步,因为他们两个锁不一样,A方法的锁是Obj这个对象,B方法的锁是Obj所属的那个class
2、线程同步的注意事项
1.线程同步的目的是为了保护多个线程访问同一个资源时对资源造成的破坏
2.线程同步方法通过锁来实现,每个对象都有且仅有一个锁,这个锁与特定对象关联,线程一旦获取到了对象锁,其他访问对象的线程就无法再访问该对象的其他非同步方法
3.对于静态同步的方法,锁是针对这个类的,锁对象是改类的class对象,静态和非静态方法的锁互不干扰,一个线程获得锁,当在一个同步方法中访问另一个对象中的同步方法时,会获得两个对象锁
4.对于同步要时刻清醒在哪个对象上同步,这是关键
5.编写线程安全的类,要时刻注意对多个线程竞争访问资源的逻辑和安全作出正确的判断,对于“原子”操作作出分析,并保证原子操作期间其他线程无法访问竞争资源
6.当多个线程访问对象锁时,没有获取到对象锁的线程将发生阻塞
7、死锁是线程间相互等待造成的,实际发生的概率比较低,一旦程序发生死锁程序将死掉
九、线程数据传递
传统同步开发模式下,调用函数时候通过函数的参数来传递数据,并通过函数返回值来返回结果,但在多线程异步开发模式下,线程的运行和结束是无法预知的,因此无法传递和返回数据时无法像调用函数一样传递。
1、通过构造函数传递参数
创建线程时必须要创建一个Thread类或其子类的实例,因此可以在调用start方法前通过Thread的构造方法传入参数,并通过变量保存起来,以便在线程中(run()方法里)使用。
2、通过变量和方法传递参数
向对象传入参数一般有两次机会,一个是在构造方法中传递进去,另外一个是通过类中的set方法来设置变量的参数,然后在线程方法中get获取传递的参数
3、通过回调函数传入参数
上面中设置参数的方法比较常用,但都是主动将数据传入线程类中,线程被动接受,第三种方法就是在线程的运行方法中调用一个类来动态获取参数信息