Java中的多线程(一)
一、理解线程
1.进程、线程、多线程
1.进程:在多任务系统中,每个独立执行的程序(或说正在进行的程序)称为进程。
2.线程:一个进程中又可以包含一个或多个线程,一个线程就是一个程序内部的一条执行线索(一部分代码)。
3.多线程:如果要一程序中实现多段代码同时交替运行,就需产生多个线程,并指定每个线程上所要运行的程序代码,即为多线程。
注:在单线程中,程序代码的执行是按调用顺序依次往下执行的,不能实现两端程序代码同时交替运行的效果。当我们的程序启动运行时,会自动产生一个线程,主函数mian就是在这个线程上运行的,当不再产生新的线程时,程序就是单线程的。所以,在这种情况下,该程序进程也就是主线程。
2.Thread类
(1)构造方法
Thread():创建一个线程对象 |
Thread(Runnable target):创建一个带Runnabke对象参数的线程对象 |
Thread(Runnable target, String name):创建一个带Runnable对象参数的线程对象 |
Thread(String name):创建一个线程对象,并指定线程的名称
Allocates a new |
(2)主要方法
static Thread currentThread():返回当前执行的是哪个线程对象
long getId():返回该线程的ID
String getName() :返回该线程的名称
int getPriority():返回该线程的优先级
void interrupt():打断该线程
boolean isAlive() :判定该线程是否为活动状态
boolean isDaemon():判断该线程是否为守护进程
void join():合并线程
void join(long millis):等待线程millis毫秒,然后分离到合并前状态
void run():当线程运行时,Runnale对象的run()方法被调用来执行相关代码
void setDaemon(boolean on):设置该线程为守护进程
void setName(String name) :设置更改该线程的名称为name
void setPriority(int newPriority):设置改变该线程的优先级(MAX_PRIORITY/MIN_PRIORITY/NORM_PRIORITY)
static void sleep(long millis):该方法使正在执行的线程进入睡眠状态且释放占用的CPU控制权,待millis毫秒后该线程重新被唤醒且再次获得CPU的控制权
void start():线程被创建后,调用该方法启动该线程,Java虚拟机调用线程的run方法
3.Runnable接口
(1)功能:如果我们定义一个类打算通过一个线程来执行的话,可以通过使该类继承这个Runnable接口并且在类中实现一个无参数的run()方法。继承于Runnable接口的类无需再继承Thread类,只需实例化该类的一个对象将其作为Thread对象参数即可实现在一个线程内运行自定义类代码。
(2)方法
void run():当一个对象继承于Runnable接口被用于创建一个线程,启动该线程将会是我们定义类中的run方法将会在一个单独的线程中执行。
二、创建线程的两种方法
Java中创建多线程主要有两种方法:继承Thread类和实现Runnable接口。
1.用Thread类创建线程
(1)原理
Java的线程是通过java.lang.Thread类来控制的,一个Thread类的对象代表一个线程,而且只能代表一个线程,通过Thread类和它定义的对象,我们可以获得当前线程对象、设置线程的优先级或获取某一线程的名称或者控制线程暂停一段时间等功能。
(2)开发思路
a.实现一个类,使该类继承于Thread类;
b.实现该类的run()方法,用于实现我们希望线程要完成的任务;
c.实例化该类一个对象,调用其start()方法启动该线程。
(3)源码举例
//主线程 public class DemoThread { public static void main(String arg[]) { TestThread thread = new TestThread(); //创建一个Thread子类对象 thread.start(); //启动该子线程 while(true) { System.out.println("main thread is running."); } } } //子线程的类 class TestThread extends Thread { public void run() { while(true) { System.out.println(Thread.currentThread().getName()+" is running."); } } }
分析:通过观察程序运行情况可知,程序有两个线程(多线程)无规律的交替运行。
效果:
升华笔记1:
1.要实现多线程,我们必须编写一个继承了Thread类的子类,子类必须覆盖Thread类中的run()方法,并且在子类的run方法中编写我们希望在新线程上实现的功能代码;
2.启动一个新的线程不是直接调用Thread子类对象的run()方法,二是调用Thread子类对象的start()方法,Thread类对象的start方法将产生一个新的线程,并在该线程上运行该Thread类对象中的run方法。
3.由于线程的代码段在run方法中,当run()方法执行完成后,相应的线程也就结束了,所以我们可以通过控制run方法中的循环条件来控制线程的终止。
2.使用Runnable接口创建多线程
(1)原理
当使用Thread(Runnale target)方法创建线程对象时,需为该方法传递一个实现了Runnable接口的类对象,这样创建的线程将调用哪个实现了Runnable接口的类对象中的run()的方法作为其运行代码,而不在调用Thread类中的run方法。
(2)开发思路
a.实现一个类,该类继承于Runnable接口并覆盖实现Runnable接口的run()方法;
b.实例化该类一个对象,并将该对象作为参数实例化一个Thread类对象;
c.调用Thread类的start()方法启动该子线程;
(3)源码举例
//主线程 public class RunnableTest { public static void main(String args[]) { TestRunnable runnable = new TestRunnable(); //创建一个类对象,该类继承于Runnable接口 Thread thread=new Thread(runnable); //创建一个带Runnable对象参数的线程 thread.start(); //启动该线程 while(true) { System.out.println("main thread is running."); } } } //子线程 class TestRunnable implements Runnable { @Override public void run() { while(true) { System.out.println(Thread.currentThread().getName()+" is running."); } } }
效果:
3.两种实现多线程方式的对比分析
通过上述两个例子,我们知道继承Thread类和实现Runnable接口都能实现多线程,并且也未看出两者有何区别。但是,当我们希望开发的多线程去处理同一资源(一个资源只能对应一个对象)时,继承Thread类方式就有点力不从心了。
举个例子:通过程序来模拟铁路售票系统,实现通过四个售票点发售某日某次列车的100张车票,一个售票点用一个线程来表示。
(1)继承Thread类方式的多线程
//主线程 public class SaleTickets { public static void main(String[] arg) { new Resource().start();//启动线程1 new Resource().start();//启动线程2 new Resource().start();//启动线程3 new Resource().start();//启动线程4 } } //唯一资源 class Resource extends Thread { private int tickets=100; //100张票 @Override public void run() { //线程每运行一次run方法,票数减1 while(tickets>0) { System.out.println(Thread.currentThread().getName()+" is saling tickets "+tickets--); } } }
分析:观察结果,我们知道在主线程中创建的四个线程各自卖各自的100张票,而不是去卖共同的100张票,也就是说这几个线程去处理的不是同一资源!实际上,在上面的程序中,我们创建了四个 Resource对象,就等于创建了四个资源,每个
Resource对象中都有100张票,每个线程在独立地处理各自的资源。
效果:
(2)继承Runnable类方法的多线程
//主线程 public class SaleTickets { public static void main(String[] arg) { Resource r = new Resource(); //实例化一个Runnbale子类对象,代表一个资源 /*依次创建4个子线程*/ new Thread(r).start(); new Thread(r).start(); new Thread(r).start(); new Thread(r).start(); } } //唯一资源 class Resource implements Runnable { private int tickets=100; //100张票 @Override public void run() { //线程每运行一次run方法,票数减1 while(tickets>0) { System.out.println(Thread.currentThread().getName()+" is saling tickets "+tickets--); } } }
分析:上面程序实现了模拟铁路售票系统的要求。从上面程序中可以看出,我们只创建了一个资源对象 Resource(该对象中包含要发售的那100张票)。然后,我们在创建四个线程,每个线程去调用同一个
Resource对象中的run()方法,因此这四个线程访问的是同一个对象中的变量(tickets)的实例,从而实现了多线程访问同一资源的功能。
效果:
升华笔记2:实现Runnable接口相对于继承Thread类优势?
1.实现Runnable接口适合多个相同程序代码的线程去处理同一资源的情况,把虚拟CPU(线程)同程序的代码、数据有效分离,较好地体现了面向对象的设计思想;
2.可以避免由于Java的单继承特性带来的局限。我们经常碰到这样一种情况,即当我们要将已经继承了某一类的子类放入多线程中,由于一个类不能同时有两个父类,所以不能用继承Thread类的方式,那么,这个类就只能采用实现Runnanle接口的方式了。
3.实现Runnable接口有利于程序的健壮性,代码能够被多个线程共享,代码与数据是独立的。当多个先的执行代码来自同一个类的实例时,即称他们共享相同的代码。多个线程可以操作相同的数据,于他们(线程)的代码无关。当共享访问相同的对象时,即它们共享相同的数据。当线程被构造时,需要的代码和数据通过一个对象作为构造函数实参传递进去,这个对象就是一个实现了Runnable接口的类的实例。
三、守护(后台)进程与联合进程
1.后台进程
(1)概述:
在上面的售票系统的例子中,我们在main方法中创建并启动新的线程后,main方法便结束了,主线程也就随着结束了。但是,我们知道Java程序在运行的过程中只要还有一个前台线程在运行,整个Java程序(进程)没有随之结束,子线程的宿主进程便成为了一个空进程。当然,如果我们将该启动的新线程设置为后台进程,如果一个进程中只有该后台进程运行,这个进程便随着main方法的结束而结束。
设置某个进程为后台进程方法:
Thread thread=new Thread(runnable);
thread.setDaemon(true); //设置子进程为守护进程
thread.start();
(2)源码举例
//主线程 public class RunnableTest { public static void main(String[] args) { int i=10; TestRunnable runnable = new TestRunnable(); //创建一个类对象,该类继承于Runnable接口 Thread thread=new Thread(runnable); //创建一个带Runnable对象参数的线程 thread.setDaemon(true); thread.start(); //启动该线程 while((--i>0)) { System.out.println("main thread is running."); } } } //子线程 class TestRunnable implements Runnable { @Override public void run() { while(true) { System.out.println(Thread.currentThread().getName()+" is running."); } } }
效果演示:
2.联合线程
所谓联合线程,就是当一个线程调用join()[如pp.join(),其中pp为线程对象方法后,把pp所对应的线程合并到调用pp.join()语句的线程中。
(1)Thread类的方法:
void join():合并线程
void join(long millis):线程合并millis毫秒后分离到合并前状态
void join(long millis,int nanos):线程合并millis毫秒nanos纳秒后分离到合并前状态
(2)源码举例
public class joinThead { public static void main(String[] args) { testThread t = new testThread(); //创建一个Runnable子类对象 Thread thread=new Thread(t); //创建一个线程 thread.start(); //启动线程 int i=100; while(i>0) { if(i==50) { try { thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("main thread:"+i--); } } } class testThread implements Runnable { private int i=100; public void run() { while(i>0) { i--; System.out.println(Thread.currentThread().getName()+": "+i ); } } }
效果分析:
经观察可知,当主线程的i递减=50时,子线程与主线程合并,此时只有子线程中的i在计算,直到子线程run方法代码运行完全两个线程再分离。最后,主线程继续执行后面的代码。
参考:http://docs.oracle.com/javase/8/docs/api/index.html