《疯狂Java讲义(第4版)》-----第16章【多线程】(线程的创建及生命周期)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/ccnuacmhdu/article/details/84938588
  • 线程的独立运行的,他并不知道进程是否还有其他线程存在
  • 当操作系统创建一个进程时,必须为该进程分配独立的内存空间,并分配大量的相关资源;但创建一个线程则简单得多,因此使用多线程来实现并发比使用多进程实现并发的性能要高得多
  • 多线程是非常有用的,一个浏览器必须能同时下载多个图片;一个Web服务器必须能同时响应多个用户请求;Java虚拟机本身就在后台提供了一个超级线程来进行垃圾回收;GUI应用也需要启动单独的线程从主机环境收集用户界面事件……

线程的创建和启动

Java使用Thread类代表线程,所有线程对象必须是Tread类或其子类的实例。

继承Tread类创建线程类

在这里插入图片描述
在这里插入图片描述

使用步骤:
1)继承Thread类,创建其子类,实现Thread类的run方法(该方法是Thread类实现的Runnable接口中的方法),该run方法就是新建线程对象的执行体(主线程的执行体是main方法);
2)创建Thread类子类的实例(线程对象);
3)调用线程对象的start()方法启动线程(调用run方法)。

import java.lang.Thread;

public class MyThread extends Thread{
	private int i;
	
	public void run(){
		for(; i < 4; i++){
			System.out.println(getName()+" "+i);
		}
	}

	public static void main(String[] args){
		for(int i = 0; i < 8; i++){
			System.out.println(Thread.currentThread().getName()+" "+i);
			if(i == 4){
				new MyThread().start();
				new MyThread().start();
			}
		}
		
	}
}

运行结果如下,每次运行会出现不同的结果,i==4之后三个线程(主线程main方法和两个其他线程)交互运行

在这里插入图片描述

缺点:无法共享线程类的实例变量,因为上面程序每次都是新建一个线程对象

注意:启动线程调用的是start方法!如果直接调用run方法,run方法就变成普通的方法了,整个程序就是单线程的程序了,如下:
在这里插入图片描述

实现Runnable接口创建线程类

使用步骤
1)写一个Runnable接口的实现类,并实现其run方法;
2)调用Thread的构造器,传入Runnable接口的实现类,创建Thread的对象实例开启线程。

在这里插入图片描述

import java.lang.Thread;

public class MyThread implements Runnable{

	private int i;
	public void run(){
		for(; i < 4; i++){
			System.out.println(Thread.currentThread().getName()+" "+i);
		}
	}

	public static void main(String[] args){
		for(int i = 0; i < 8; i++){
			System.out.println(Thread.currentThread().getName()+" "+i);
			if(i == 4){
				MyThread mt = new MyThread();
				new Thread(mt, "线程1").start();
				new Thread(mt, "线程2").start();
			}
		}
		
	}
}

从下图可知,线程1和线程2是共享一个变量!
在这里插入图片描述

1.Runnable是函数式接口
2.可以通过传入同一个Runnable对象,实现共享线程对象的实例变量

使用Callable接口和FutrueTask类创建进程

这种方式和Runnable方式类似对比,Callable相当于Runnable,FutureTask传入Runnable对象再传入Thread建立线程对象,call方法相当于run方法。只不过call方法可以有返回值,可以抛异常,比run要强大一些。

在这里插入图片描述
在这里插入图片描述

import java.lang.Thread;
import java.util.concurrent.*;

public class MyThread implements Callable<Integer>{

	private int i;
	public Integer call(){
		for(; i < 4; i++){
			System.out.println(Thread.currentThread().getName()+" "+i);
		}
		return i;
	}

	public static void main(String[] args){
		FutureTask<Integer> task = new FutureTask<Integer>(new MyThread());
		for(int i = 0; i < 8; i++){
			System.out.println(Thread.currentThread().getName()+" "+i);
			
			if(i == 4){
				
				new Thread(task, "有返回值的线程").start();
			}
		}
		try{
			//获取线程返回值
			System.out.println("子线程返回值:"+task.get());
		}catch(Exception e){
			e.printStackTrace();
		}
		
	}
}

在这里插入图片描述

线程的生命周期

新建(New)

new一个线程对象,该线程就进入新建状态。JVM为其分配内存,初始化成员变量的值。

就绪(Runnable)

线程对象调用了start方法后,该线程进入就绪状态。JVM为其创建方法调用栈和程序计数器,至于何时开始运行,取决于JVM的线程调度器的调度。

运行(Running)

就绪状态的线程获得了CPU,开始执行run方法,线程处于运行状态。

阻塞(Blocked)

遇到下面情况,线程会进入阻塞状态:

  • 线程调用sleep方法主动放弃所占用的资源;
  • 线程调用了一个阻塞式IO方法,该方法返回之前;
  • 线程试图获得一个同步监视器,但该同步监视器正被其他线程所持有;
  • 线程在等待某个通知(notify);
  • 程序调用了线程的suspend方法将该线程挂起(此方法容易导致死锁,建议尽量不要用)。

遇到下面情况,线程会解除阻塞:

  • 调用sleep方法的线程经过了指定时间
  • 线程调用的阻塞IO方法已经返回
  • 线程成功获得了试图获得的同步监视器
  • 处于挂起的线程被调用了resume方法

死亡(Dead)

三种死亡方式:

  • run方法或call方法正常运行结束
  • 线程抛出未捕获的Exception或Error
  • 直接调用该线程的stop方法(该方法容易导致死锁,尽量不要用)
import java.lang.Thread;

public class MyThread extends Thread{
	private int i;
	
	public void run(){
		for(; i < 4; i++){
			System.out.println(getName()+" "+i);
		}
	}

	public static void main(String[] args){
		MyThread thread = new MyThread();
		for(int i = 0; i < 8; i++){
			System.out.println(Thread.currentThread().getName()+" "+i);
			if(i == 4){
				thread.start();
				System.out.println(thread.isAlive());
			}
			
		}
		if(!thread.isAlive()){
			thread.start();
		}
		
	}
}

在这里插入图片描述

  1. 判断线程死活:isAlive,新建、死亡状态返回false,其他三种状态返回true
  2. 以上实验,抛出了异常,因为上面试图让已经死亡的线程重新启动!start只能被新建状态的线程调用,且只能调用一次,否则抛异常。
  3. 一旦子线程启动之后,就和主线程有相同的地位,当自身没执行结束的时候,不会因为主线程执行结束而结束。

猜你喜欢

转载自blog.csdn.net/ccnuacmhdu/article/details/84938588
今日推荐