目录
程序、进程与线程
继承Thread类
实现Runnable接口
Thread类常用的构造方法
继承Thread类与实现Runnable接口的区别
编写线程类继承Thread类
编写线程类实现Runnable接口
实现Callable接口
线程的生命周期
线程的常用方法
例一:线程的常用方法一
例二:自定义线程的默认名称
例三:线程是否处于活跃状态
例四:线程的强制执行
例五:线程的休眠
例六:线程的礼让
程序、进程与线程
程序是一系列有序的指令集合,由程序员编写、由代码组成,用于让计算机执行一定
的 *** 作或用于实现一定的功能。
启动运行的程序被称为进程,系统会为进程分配一定的内存空间,进程由三部分组
成,分别为cpu、data和code.
线程是进程的组成部分,一个进程中有N多个线程,这些线程共享进程的内存资源,
线程负责具体的执行。
进程与进程之间是相互独立的,各个线程之间互相“抢占”CPU资源,哪个线程“抢占”到了CPU资源,CPU就会执行哪个进程中的线程,CPU执行线程的过程被称为CPU的调度。在一个时间片内CPU只能执行一个线程,当时间片结束后再调用执行其他的线程。所以当一个进程中的线程多了,那么被CPU调用执行的概率就大,所以多线程 *** 作可以提高程序的运行效率。
在Java中实现多线程的方式有三种,一种是继承Thread类,一种是实现runnable接口,第三种是从JDK1.5开始的通过callable接口实现多线程
继承Thread类一个普通类继承 Thread类,这个类就被称为具备多线程 *** 作能力的类,具备多线程
能力的类,要求重写父类Thread类中的run()方法,在run()方法中所编写的代码被称为
线程体
//继承Thread类,让自定义的类MyThread具备多线程 *** 作的能力
public class MyThread extends Thread{
//重写run方法
@Override
public void run(){
//编写线程体(线程所执行的代码)
System. out. println("MyThread中的run方法中的代码”);
}
}
public class TestMyThread {
//主方法负责java程序的运行,被称为主线程
public static void main(String[] args) {
//创建线程类的对象
MyThread my=new MyThread();
//启动线程
my.start();
System, out. println("-----------main方法中的代码");
/**在以上代码中,至少有两个线程,一个是主线程,一个是my线程*/
}
}
运行Java程序时启动了Java虚拟机,负责执行主线程(主方法)中的代码,在执行
MyThread my=new MyThread()之前有一个线程,线程的名称为main,是主线程,使用
my.start()启动my线程,这个时候有两个线程,一个是主线程,一个是my线程,由于哪个线程先“抢占”到CPU资源不确定,所以两句代码哪句先执行也不确定,执行结果如下图。
在线程体中使用循环,程序的运行效果会更加明显
【示例16-3】线程体中使用循环。
package cn. sxt.thread;
//继承Thread类,让自定义的类MyThread2具备多线程 *** 作的能力
public class MyThread2 extends Thread{
//重写run方法
@Override
public void.run(){
for(int i=0;i<10;i++){
System.out.println("MyThread2 中的 i="+i+"--------------"
}
}
}
public class TestMyThread2 {
public static void main(String[] args) {
//创建MyThread2线程类的对象
MyThread2 my2=new MyThread2();
//启动线程
my2.start();
//主线程中的循环
for(int i=0;i<10;i++){
System.out.println("============主方法中的for循环i="+i);
}
}
}
运行效果如下图所示
在启动线程时使用的是Thread类中start()方法,而非调用run()方法,如果使用my2.run(),那么线程my2将不会被启动,那么只有一个线程,那就是主线程,程序无论运行多少次,结果都相同。
Runnable接口中没start方法,要想启动线程,必须借助Thread类
Thread类常用的构造方法Thread() | 创建Thread对象 |
Thread(String name) | 创建Thread对象,并给线程起个名字 |
Thread(Runnable target) | 创建Runnable接口的实现类创建Thread对象 |
Thread(Runnable target,String name) | 根据Runable接口的实现类创建Thread对象,并给线程起个名字 |
//MyRunnable实现Runnable接口,MyRunnable就具备了多线程 *** 作的能力
public class MyRunnable implements Runnable {
//必须实现接口中的run()方法
@Override
public void run() {
//编写线程体,线程所要执行的代码
for (int i=0;i<5;i++){
System.out.println("Myrunnable类中run方法中的i="+i);
}
}
}
class testMyRunnable{
public static void main(String[] args) {
MyRunnable myRun=new MyRunnable();
//myRun.start();是不可以启动线程的,因为Runnable接口中没start方法,要想启动线程,必须借助Thread类
Thread t=new Thread(myRun);//根据Runnable接口的实现类创建Thread对象
t.start();
//主线程中的代码
for (int i=0;i<5;i++){
System.out.println("============main中的i="+i);
}
}
}
代码运行效果
继承Thread类与实现Runnable接口的区别Java中的类具有单继承的特点,如果一个类继承了Thread类,那么就不能再继承他的类了,所以继承Thread类实现多线程有一定的局限性。
使用Runnable接口实现多线程的优点如下:
(1)避免单继承。
(2)方便共享资源,同一份资源可以有多个代理访问。
例如:有三个窗口共卖5张票,每个窗口有1100个人排队,分别使用继承Thread类和Runnable接口实现。
编写线程类继承Thread类public class TicketThread extends Thread {
public TicketThread(String name){
super(name);//调用父类Thread类中带参的构造方法
}
private int ticket=5;//共享资源5张票
@Override
public void run() {
//编写变成体,100个人
for (int i=0;i<100;i++){
//判断是否有票
if (ticket>0){
System.out.println(super.getName()+"卖第"+(ticket--)+"张票");
}
}
}
}
class TestTicketThread{
public static void main(String[] args) {
//创建三个线程类的对象
TicketThread t1=new TicketThread("A窗口");
TicketThread t2=new TicketThread("B窗口");
TicketThread t3=new TicketThread("C窗口");
t1.start();//启动线程,开始售票
t2.start();
t3.start();
}
}
测试结果
由测试结果可以看到每个窗口各卖了5张票,3个窗口一共卖了15张,产生的原因是三个线程类的对象,每个对象都有一个独立的属性ticket。其内存分析图如图
编写线程类实现Runnable接口public class TicketRunnable implements Runnable {
//共享资源5张票
private int ticket=5;
@Override
public void run() {
//编写线程体
for (int i=0;i<100;i++){
//判断是否有票
if (ticket>0){
System.out.println(Thread.currentThread().getName()+"卖第"+(ticket--)+"张票");
}
}
}
}
class TestTicketRunnable{
public static void main(String[] args) {
//创建线程类的对象
TicketRunnable tr=new TicketRunnable();
//用一个Runnable的实现类创建三个代理类的对象
Thread t1=new Thread(tr,"A窗口");
Thread t2=new Thread(tr,"B窗口");
Thread t3=new Thread(tr,"C窗口");
//启动线程
t1.start();
t2.start();
t3.start();
}
}
测试结果
通过实现Runnable接口来实现多线程,达到了多个线程访问共享资源的目的。无论是继承Thread类还是实现Runnable接口,run()方法都是没有返回值的,而且如果在线程体中有异常需要抛出也是很难实现的。
在JDK1.5之后实现多线程还可以使用第三种方式,那就是实现Callable接口,重写call()方法。
实现Callable接口Callable接口从JDK1.5开始,与Runnable实现多线程相比,Callable支持泛型,call()方法可以有返回值,而且还支持泛型的返回值,比run()方法更强大的一点是还可以抛出异常。
Callable接口中的方法call()需要借助 FutureTask类来获取结果,使用Callable接口实现多线程所需要的类与接口之间的关系如下图所示
任务管理器 FutureTask 是 RunnableFuture 接口的实现类,而 RunnableFuture 接口又继承了Future接口和Runnable接口,所以任务管理器类 FutureTask 也是Runnable接口的实现类。通过创建任务管理器类的对象将Callable接口的实现类传入,从而实现多线程,通过实现Callable接口实现多线程
//创建一个类实现Callable接口
import java.util.concurrent.Callable;
public class RandomCallable implements Callable {
@Override
public String call() throws Exception {
//创建一个长度为5的String类型的数组
String []array={"apple","banana","orange","grape","pear"};
int Random=(int)(Math.random()*4)+1;//产生一个1~4之间的随机数
return array[Random];//根据产生的随机数从而随机获得数组中的元素
}
}
//测试类
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建任务
RandomCallable rc=new RandomCallable();
//创建任务管理器,将任务提交给任务管理器
FutureTaskft=new FutureTask<>(rc);
//创建Thread类
Thread t=new Thread(ft);//FutureTask是Runnable接口的实现类,用Runnable接口的实现类创建Thread类
System.out.println("任务是否已完成:"+ft.isDone());
//启动线程
t.start();
//获取返回值结果
System.out.println(ft.get());
System.out.println("任务是否已完成:"+ft.isDone());
}
}
测试结果
在start()方法之前,任务是没有完成的,因为还没有启动线程,所以isDone()的结果为false,当使用get()方法获取到结果后,说明任务已经完成了,因为只有结果出来任务才能结束,否则无论get()方法后有多少句代码都不会执行。
线程的生命周期一个线程类使用new关键字创建完对象,在堆内存开辟内存空间,这个线程就处于新生状态,所以说处于新生状态的线程有自己的内存空间。
使用start()方法开始启动线程,那么线程处于就绪状态,处于就绪状态的线程具备了运行的所有条件,但是还没有得到CPU的调度,通常被称为“万事俱备,只欠CPU调度”。就绪状态的线程具备了运行资格,不具备运行条件。
当系统选定一个等待执行的线程后,这个线程将由就绪状态进入执行状态,被称为运行状态,运行状态的线程执行自己的run方法中的代码,直到等待某资源而阻塞或完成任务而死亡。如果在给定的时间片内没有执行结束,就会被系统换下来回到就绪状态。
处于运行状态的线程在调用了join()、sleep()、wait()或者等待I/O等时,线程则处于阻塞状态。当引起阻塞的原因消除时,线程就会转入就绪状态,重新到就绪队列中排队等待,被系统选中后从原来停下的位置开始继续执行。
一个线程的run()方法执行完毕或者抛出未捕获的异常,再或者调用stop()方法,那么线程将处于死亡状态,线程的生命周期如下图
Thread类提供了N多 *** 作线程的方法
例一:线程的常用方法一 例二:自定义线程的默认名称 例三:线程是否处于活跃状态运行结果
对运行结果的分析
使用new创建线程类的对象后,线程还未处于活动状态,所以isAlive()的结果为false。当调用start()方法启动线程后,线程处于就绪状态,处于就绪状态的线程处于活动状态,isAlive()的结果为true。
在运行效果图中可以看到“主线程结束,my2线程是否处于活动状态”的结果为true或者为false,也就是说my2线程是否处于活动状态,取决于主线程与my2线程谁先执行结束。
如果主线程先结束,再打印输出时my2线程还没有结束,所以依然处于活动状态,如果my2线程先结束,主线程还没有结束,当打印输出时my2线程就处于非活动状态。
例四:线程的强制执行测试结果
当主线程中i>2时,代理线程t调用join方法强制执行,主线程处于阻塞状态;当代理线程t执行结束时,主线程才能继续执行。
我们也可以通过调用sleep()方法让线程进入休眠状态,休眠状态的线程本身进入阻塞状态
例五:线程的休眠执行过程
Java多线程休眠
在线程类的run方法中使用了Thread.sleep(500)线程休眠0.5秒,所以在程序启动运行0.5秒后才开始进行输出,也就是说每次执行到Thread.sleep(500)时,线程都将进入阻塞状态,0.5秒后进入就绪状态,然后再次等待CPU调度执行。
例六:线程的礼让yield()方法使当前线程暂停一次,允许其它线程执行,yield()的线程不会进入阻塞状态,而是直接进入就绪状态。让了其他线程后,下一个就是自己占据CPU的资源了。如果没有其它等待的线程,则yield()的线程会马上恢复执行。
运行结果
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)