重点:多线程的概念
线程创建的两种方式
线程的生命周期及状态转换
线程的调度
线程的安全和同步
多线程通信
- 多线程的概念
指一个应用程序中有多条并发执行的线索,每条线索都被称作一个线程,他们会交替进行,彼此间可以进行通信。
- 线程创建的两种方式
- 继承java.lang包下的Thread类,覆盖Thread类的 run()方法,在run()方法中实现运行在线程上的代码
-
class test { public static void main (String[] args) { myThread myThread=new myThread();//创建线程myThread的线程对象 myThread.start();//开启线程 while(true){//通过死循环打印输出 System.out.println("main方法在执行"); } } } class myThread extends Thread{ public void run(){ while(true){//通过死循环打印输出 System.out.println("myThread方法在执行"); } } }
局限性:Java只支持单继承,一个类一旦继承了某个父类就无法再继承Thread类,为了克服这种弊端,Thread类提供另一个构造方法Thread(Runnable target),其中Runnable是一个接口,只有一个 run()方法,当Thread(Runnable target)构造方法创建对象时,只需为该方法传递一个实现Runnable接口的实例对象,这样创建的线程将调用实现了Runnable接口中的run()方法作为运行代码,不需要调用Thread类的run()方法
- 2.实现java.lang.Runnable接口,在run()方法中实现运行在线程上的代码
class test
{
public static void main (String[] args)
{
myThread myThread=new myThread();//创建线程myThread的线程对象
Thread thread=new Thread(myThread);//创建线程对象
thread.start();//开启线程,执行线程中的run方法
while(true){//通过死循环打印输出
System.out.println("main方法在执行");
}
}
}
class myThread implements Runnable{
public void run(){
while(true){//通过死循环打印输出
System.out.println("myThread方法在执行");
}
}
}
单线程程序:;
class test
{
public static void main (String[] args)
{
myThread myThread =new myThread();//创建myThread实例对象
myThread.run();//调用myThread类的run方法
while(true){
System.out.println("main方法在运行");
}
}
}
class myThread{
public void run(){
while(true){
System.out.println("myThread类的run方法在运行");
}
}
}
两种线程在实际中的应用案例:
假设售票厅有四个窗口可发售某日某次列车的100张车票。
使用继承Thread实现多线程,代码如下:
class test
{
public static void main (String[] args)
{
new TicketWindows().start();//创建一个TicketWindows并开启
new TicketWindows().start();//创建一个TicketWindows并开启
new TicketWindows().start();//创建一个TicketWindows并开启
new TicketWindows().start();//创建一个TicketWindows并开启
}
}
class TicketWindows extends Thread{
private int tickets=100;
public void run(){
while(true){
if(tickets>0){
Thread th=Thread.currentThread();//获取当前线程
String th_name=th.getName();//获取当前线程的名字
System.out.println(th_name+"正在发售第"+tickets--+"张票");
}
}
}
}
使用构造方法Thread(Runnable target,String name)创建线程对象的同时指定线程名字
class test
{
public static void main (String[] args)
{
TicketWindows tw=new TicketWindows();//创建一个TicketWindows实例对象tw
new Thread(tw,"窗口1");//创建线程对象并命名为窗口1,开启线程
new Thread(tw,"窗口2");//创建线程对象并命名为窗口2,开启线程
new Thread(tw,"窗口3");//创建线程对象并命名为窗口3,开启线程
new Thread(tw,"窗口4");//创建线程对象并命名为窗口4,开启线程
}
}
class TicketWindows implements Runnable{
private int tickets=100;
public void run(){
while(true){
if(tickets>0){
Thread th=Thread.currentThread();//获取当前线程
String th_name=th.getName();//获取当前线程的名字
System.out.println(th_name+"正在发售第"+tickets--+"张票");
}
}
}
}
Runnable接口相对于继承Thread类来说,有如下显著好处:
1.适合多个相同程序代码的线程去处理同一个资源的情况,把线程同程序代码、数据有效的分离,很好的体现了面向对象的设计思想
2.可以避免由于Java单继承带来的局限性。
事实上,大部分应用程序都会采用第二种方式来创建多线程,使用Runnable接口
后台线程
class test
{
class DamonThread implements Runnable{//创建DamonThread类,实现runnable接口
public void run(){//实现接口中的run方法
while(true){
System.out.println(Thread.currentThread().getName()+"---is running");
}
}
public static void main (String[] args)
{
System.out.println(Thread.currentThread().isDamon());
DamonThread dt=new DamonThread();//创建一个DamonThread对象dt
Thread t=new Thread(dt,"后台线程");//创建线程t共享dt资源
System.out.println(t.isDamon());//判断是否为后台线程
t.setDaemon(true);//将线程t设置为后台线程
t.start();//开启线程
for(int i=0;i<10;ix++){
System.out.println(i);
}
}
}
}
三、线程的生命周期及状态转换
线程整个生命周期分为五个阶段:
新建状态New
创建一个线程对象后,该线程对象就处于新建状态,此时他不能运行,和其他Java对象一样,仅仅由Java虚拟机为其分配内存,没有表现出任何线程的动态特征
就绪状态Runnable
当线程对象调用了start()方法后,该线程就进入就绪状态,处于就绪状态的线程位于可运行池中,此时他只是具备了运行的条件,能否获得CPU的试用权开始运行,还需要等待系统的调度
运行状态Running
如果处于就绪状态的线程获得去了CPU的使用权,开始执行run()方法中的线程执行体,则该线程处于运行状态,当一个线程启动后,他不可能一直处于运行状态(除非他的线程执行体足够短,瞬间就结束了)当使用完系统分配的时间后,系统就会剥夺该线程占用的CPU资源,让其他线程获得执行的机会。
注意:只有处于就绪状态的线程才可能转成运行状态
阻塞状态Blocked
列举线程由运行状态转换成阻塞状态的原因,以及如何从阻塞状态转成就绪状态:
- 当线程师徒获取某个对象的同步锁时,如果该锁被其他线程所持有,则当前线程会进入阻塞状态,如果想从阻塞状态进去就绪状态必须得获取到其他线程所持有的锁
- 当线程调用了一个阻塞式的IO方法时,该线程就会进入阻塞状态,如果新进入就绪状态就必须要等到这个阻塞的IO方法返回
- 当线程调用了某个对象的wait()方法时,也会使线程进入阻塞状态,如果想进入就绪状态就需要使用notify()方法唤醒该线程
- 当线程调用了Thread的sleep(long millis)方法,也会使该线程进入阻塞状态,在这种情况下,只需等到线程睡眠的时间到了以后,线程就会自动进入就绪状态
- 当在一个线程中调用了另一个线程的join()方法时,会使当前线程进入阻塞状态,在这种情况下,需要等到新加入的线程运行结束后才会结束阻塞状态,进入就绪状态
注意:线程从阻塞状态只能进入就绪状态,而不能直接进入运行状态,也就是说结束阻塞的线程需要重新进入可运行池中,等到系统的调度
死亡状态Terminated
线程的run()方法正常执行完毕或者线程抛出一个未捕获的一场,错误,线程就进入死亡状态,一旦进入死亡状态,线程就不在拥有运行的资格,也不能在转换到其他状态
四、线程的调度
线程调用有两种模型:
1.分时调度模型:让所有的线程轮流获得CPU的使用权,并且平均分配每个线程占用CPU的时间片
2.抢占式调度模型:让可运行池中优先级高的线程优先占用CPU,而对于优先级相同的线程,随机选择一个线程使其占用CPU,当他失去CPU的使用权后,咋随机选择其他线程获取CPU的使用权
Java虚拟机默认采用抢占式调度模型,大多数情况下,程序员不需要关心它,但在某些特定的需求下需要改变这种模式,由程序逐级来控制CPU调度
线程的优先级用1-10直接的证书来表示,数字越大优先级越高。
还可以使用Thread类中提供的三个静态常量表示线程的优先级
Static int MAX_PRIORITY 线程最高优先级,相当于值10
Static int MIN_PRIORITY线程最低优先级,相当于值1
Static int NORM_PRIORITY线程普通优先级,相当于值5
程序在运行期间,处于就绪状态的每个线程都有自己的优先级,然而线程优先级不是固定不变的,可以通过Thread类的setPriority(int newPriority)方法对其进行设置。
五、线程的安全和同步
六、多线程通信