多线程
一个进程都有一个主进程,一个进程也可以由多个进程组成,每条线程并行执行不同的任务,这多个线程共享同一个内存空间。
1.线程生命周期(从创建到消亡)
① 新建状态
用new创建完一个线程对象后,该线程处于新建状态,通过start()方法启动或通过stop()方法来停止。
② 就绪状态
当线程对象调用了start()方法后,线程处于就绪状态。
③ 运行状态
若处于就绪状态的线程获得了cpu时间片,开始执行run()方法的线程执
行体,该线程处于运行状态
④ 不可运行状态
当线程处于不可运行状态时,Java虚拟机不会给线程分配CPU,直到线程重新进入就绪状态,它才有机会转到运行状态,不可运行状态分为3种情况:
a. 等待状态:位于对象等待池中的等待状态,当线程运行时,如果执行了某个对象的wait()方法,Java虚拟机就会把线程放到这个对象的等待池中。
b. 阻塞状态:位于对象锁中的阻塞状态,当线程处于运行状态时,试图获得某个对象的同步锁时,如果该对象的同步锁已经被其他线程占用,jvm就会把这个线程放到这个对象的锁池中。
c. 睡眠状态:其他的阻塞状态,当前线程执行了sleep()方法或者调用了其他线程的join()方法,或者发出了I/O请求时,就会进入这个状态中。
⑤ 终止状态
如果线程的run()执行完成,或者调用了stop()方法,或者抛出了一个未捕获的异常等原因,则该线程就进入了终止状态。
方法 | 描述 | 当前状态 | 目的状态 |
Start() | 开始执行线程 | 新建状态 | 就绪状态 |
Stop() | 终止线程 | 运行状态 | 终止状态 |
Sleep(long),sleep(long,int) | 根据时间暂停线程 | 运行状态 | 睡眠状态 |
Suspend() | 挂起执行 | 运行状态 | 阻塞状态 |
Resume() | 恢复执行 | 阻塞状态 | 运行状态 |
Yield() | 放弃执行 | 运行状态 | 就绪状态 |
Wait() | 进入等待 | 运行状态 | 等待状态 |
Notify() | 等待状态解除 | 等待状态 | 就绪状态 |
编写线程程序
l 第一种方法:继承Thread类
实现多线程有两种方法:一种是继承Thread类并覆盖run()方法,另一种是实现Runnable接口来创建,Thread类是在java.lang包中定义的,一个类只要继承了Thread类同时覆写了本类中的run()方法就可以实现多线程操作了,但一个类只能继承一个父类,这是该方法的局限,继承Thread()类写法如下:
Public class Threadexample extends Thread{
Public void run(){
...
}
}
以上并未涉及多线程的概念,java.lang.Thread类是一个通用的线程类,其run方法默认为空,因此直接对Thread类进行继承并覆写Thread类中的run方法,来实现人们想要的功能,因此,方法一就是先继承Thread类建立子类,然后覆写run方法,再通过Thread子类声明线程对象。
l 第二种方法:实现Runnable接口
Runnable接口定义如下:
Public interface Runnable{
Public void run();
}
Runnable接口只有一个抽象方法run();因此要实现这个接口只需要实现这个抽象方法即可,而要建立一个线程类,必须要实现这个接口,例如:
Public class Runnablexample implement Runnable{
Public void run(){
...
}
}
通过线程类Runnablexample来创建一个线程例子如下:
Runnablexample t1=new Runnablexample();
Thread t1s=new Thread(t1);
t1s.start();
//t1s.setname(“窗口一”);给线程命名
l 两种方法比较
由于Java采用单继承,若类已经继承了某个类,就不能在继承Thread类了。Run()方法其实是Runnable接口独有的方法,但Thread类本身已经封装了Runnable接口,所以可以用继承Thread类来实现线程。实现Runnable接口可以共享一个目标对象,实现多个线程处理同一资源。
采用继承Thread类方式优缺点:
(1)优点:编写简单,如果需访问当前进程,无须使用Thread.currentThread()方法,直接使用this,即可获得当前进程。
(2)缺点:因为线程类已经继承了Thread类,所以不能再继承其他父类。
采用实现Runnable接口方式优缺点:
(1)优点:线程类只是实现了Runnable接口,还可以继承其他类,这种方式下,可以多个线程共享同一目标对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU代码和数据分开,形成清晰的模型,较好的体现面向对象的思想。
(2)缺点:编程稍微复杂,如果需要访问当前进程,必须使用Thread.currentThread()方法。
l 线程基本控制方法
对通过start()启动的线程,对线程的控制可以通过调用Thread对象已有的方法来实现,如下:
方法 | 说明 | 方法 | 说明 |
Stop() | 线程终止 | Suspend() | 线程暂停 |
isAlive() | 线程状态测试 | Resume() | 线程恢复 |
Sleep() | 线程睡眠 |
|
|
1.线程睡眠
Thread.sleep()使当前线程的执行暂停一段指定的时间,该方法只是放弃CPU一段时间,而不会放弃CPU之外的其他资源,sleep()有两个重载版本,一个以毫秒指定睡眠时间,另一个以纳秒(1000000纳秒等于1毫秒),指定睡眠时间,但不保证这些睡眠时间的精确性,因为它们受系统计时器和调度程序精确性和准确性的影响,等到指定时间结束后,暂停状态就会停止,然后继续执行没有完成的任务,另外,中断可以终止睡眠时间,当另一个线程中断了已经启动的sleep的当前进程时就抛出这个异常。
2. 线程唤醒
如果当一个线程已经睡眠状态时,而需要让它继续执行,那么一种方法等待线程一直睡眠到事先设定的时间自动醒来,第二种方法就是通过调用interrupt()来唤醒线程。
3.线程让步
线程让步是指让当前正在运行的线程对象退出运行状态,而让其他线程运行,这种让步是通过调用yield()方法来实现的,但注意,让步只是让当前线程退出,却不能指定将运行权让给谁,运行权仍然要依靠操作系统来分配,可用thread.yield();
4.线程等待
Join()方法是让一个线程等待另一个线程完成,比如t1,t2是两个线程对象,在t1中调用了t2.join(),就会导致t1线程暂停执行,一直等待到t2线程完成了,t1才恢复执行,join有以下方法,其中第一种方法是默认等待线程直到线程结束,后面两种方法是通过指定线程等待的上限时间,当上限时间到了以后,线程就不再等待继续执行了。
Void join():等待该线程直到线程结束
Void join(long millis):等待该线程终止时间最长为millis毫秒
Void join(long millis,int nanos):等待该线程终止的时间最长为millis毫秒+nanos纳秒
//long start=system.nanoTime():系统当前时间
l 线程互斥与同步
线程的运行权是通过线程的抢占方式来获得的,常常是一个线程运行到一半,就被另一个线程占了运行权,在多线程程序中,线程之间一般来说都不是孤立运行的,同时运行的多个线程常常共用资源,比如相同的对象或者变量,如果多个线程同时对一个变量进行读写操作,就很可能造成数据读和写的不正确性,因此,线程的同步就是为了让多个线程能够协调地并发运行,对线程进行同步处理可以通过同步方法和同步语句块来实现。
l 多线程同步的基本原理
在Java中,虚拟机是通过给每个对象加锁的方式来实现多线程的同步,Java虚拟机为每个对象(类对象或实例对象)准备一把锁和等候集,Java虚拟机通过锁来确保某个对象在任何同一个时刻最多只有一个线程能够运行与该对象相关联的同步语句块和同步方法。
同步块是指对程序中的语句块使用synchoronized修饰词进行同步,同步语句块的定义格式如下:
Synchoronized(obj){
同步语句块
}
需要注意的是:synchoronized获得的是参数中obj对象的锁,它必须获得obj这个对象的锁才能执行同步语句,否则只能等待获得锁,因为obj对象的作用范围不同,控制情况也不尽相同。
同步方法是指对整个方法使用synchoronized修饰词进行同步,同步方法的定义格式如下:
Synchoronized void f(){
代码
}
Java中锁的原理是每个线程运行与对象相关联的同步方法和同步语句块之前,必须获得锁,否则就不能运行,每次只能有一个线程获得锁,并进入运行这些代码,一旦有线程进入并运行代码,对象锁就自动锁上,从而是其他需要进去的进程处于阻塞状态,停留在等候集中等待,如果线程执行完同步方法或同步语句块并从中退出,则对象锁自动打开,等待中的线程就能获得锁,如果有多个线程等待进入运行同步方法和同步语句块,则由线程的优先级决定,如果优先级相同,则随机决定获得锁的线程。
l 后台线程
后台线程是指Daemon线程,又称为守护线程,它是指在后台执行服务的线程,如操作系统中的隐藏线程,Java的垃圾自动回收线程等,与后台线程相对应的就是前台线程,所有使用Thread建立的线程默认情况都是前台线程,例如,main主线程就是一个前台线程。
前台线程和后台进程两者的区别是在进程中,只要有一个前台进程未退出,进程就不会终止,而后台线程不管本身线程是否结束,只要所有的前台线程都已退出,该后台进程就会自动终结。
可以使用Thread类中的setDaemon(boolean on)方法来设置一个线程为后台线程,on设为true则为后台进程,设为false则为前台线程,但必须在线程启动之前调用setDaemon(boolean on)方法,这样才能将这个线程设置为后台线程,当设置完成一个后台线程后,可以使用Thread类中的isDaemon()方法来判断线程是否后台线程。