【异常】-【多线程】

概念

一、并发:指两个或多个事件在同一时间段内发生 比如 在一个时间段中 前一半时间执行任务1 后一半时间执行任务2 两个任务交替执行
二、并行:指两个或多个事件在同一时刻发生(同时发生) 比如 在一个时间段中 同时执行任务1和任务2
三、内存:所有的应用程序都需要进入到内存中执行 临时存储RAM
四、硬盘:永久存储ROM
五、点击应用程序执行 硬盘中的程序就会进入内存 占用一些内存执行 进入到内存的程序叫进程,在任务管理器中点击结束进程 就把进程从内存中清除
六、线程调度分为分时调度和抢占式调度

  1. 分时调度:所有线程轮流使用CPU的使用权 平均分配每个线程占用CPU的时间
  2. 抢占式调度:优先让优先级高的线程使用CPU 如果线程的优先级相同 那么随机选择一个(这称为线程随机) java使用的就是抢占式调度

七、主线程:执行主(main)方法的线程
八、单线程程序:java程序中只有一个线程,执行从main方法开始 从上到下依次执行

public class Concept {
	public static void main(String[] args) {
		Person p1 = new Person("小强");
		p1.run();
		Person p2 = new Person("旺财");
		p2.run();
	}
}

程序从主方法开始 从上到下依次执行以上四行代码 是单线程程序,JVM执行main方法 main方法会进入到栈内存 JVM会找操作系统开辟一条main方法通向CPU的执行路径,CPU就可以通过这个路径来执行main方法 而这个路径有一个名字 叫main(主)线程

异常

一、异常:程序在执行过程中 出现的非正常的情况 最终会导致JVM的非正常停止
二、在java等面向对象的编程语言 异常本身是一个类 产生异常就是创建异常对象并抛出了一个异常对象 java处理异常的方式是中断处理
三、异常指的并不是语法错误 语法错了 编译不通过 不会产生字节码文件 根本不能运行
四、异常机制其实是帮助我们找到程序中的问题
五、异常的根类是java.lang.Throwable,其下有两个子类:java.lang.Errorjava.lang.Exception
六、Eroor错误 相当于程序得了一个无法治愈的毛病(非典 艾滋等) 必须修改源代码程序才能继续执行
七、Exception编译期异常 进行编译(写代码)java程序出现的问题 是由于使用不当导致的
RuntimeException:运行期异常 java程序运行过程中出现的问题
八、异常就相当于程序得了一个小毛病(感冒发烧) 把异常处理掉 程序可以继续执行(吃点药 打点点滴 继续革命工作)

import java.sql.Date;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import javax.xml.crypto.Data;

public class ThrowableClass { 
	public static void main(String[] args){
		//Exception编译期异常
		SimpleDateFormat sdfDateFormat = new SimpleDateFormat("yyyy-MM-dd");
		//如果不写try catch会报错 这就是编译异常
		try {
			Date date = (Date) sdfDateFormat.parse("1999-09-09");
		} catch (ParseException e) {
			e.printStackTrace();
		}
		
		//RuntimeException:运行期异常
		int[] arr = {1,2,3};
		//System.out.println(arr[3]);会报错 索引越界异常 这就是在程序运行期间出现异常
		//解决此方法:
		try {
			//可能会出现异常的代码
			System.out.println(arr[3]);
		} catch (Exception e) {
			//异常的处理逻辑
			System.out.println(e);
		}
		
		/* Eroor错误
		 * OutOfMemoryError:java heap space
		 * 内存溢出的错误 创建的数组太大了 超出了给JVM分配的内存
		 */
		//int[] arr2 = new int[1024*1024*1024];报错 内存溢出的错误 创建的数组太大了 超出了给JVM分配的内存
		//必须修改代码 创建的数组小一点 否则程序没法运行
		int[] arr2 = new int[1024*1024];

		System.out.println("后续代码");
	}
}

throw关键字

一、作用:可以使用throw关键字在指定的方法中抛出指定的异常
二、使用格式:throw new xxxException("异常产生的原因");
三、注意:

  1. throw关键字必须写在方法的内部
  2. throw关键字后边new的对象必须是ExceptionException的子类对象
  3. throw关键字抛出指定的异常对象 我们就必须处理这个异常对象
    (1)throw关键字后边创建的是RuntimeExceptionRuntimeException的子类对象 我们可以不处理 默认交给JVM处理(打印异常对象)
    (2)throw关键字后边创建的是编译异常(写代码的时候报错) 我们就必须处理这个异常 要么throw 要么try...catch
public class Throw {
	public static void main(String[] args) {
		int[] arr = null;
		int e = getElement(arr, 0);
		System.out.println(e);
	}
	
	/*
	 * 定义一个方法 获取数组指定索引处的元素
	 * 参数:
	 * 		int[] arr
	 * 		int index
	 * 以后工作中 我们首先必须对方法传递过来的参数进行合法性校验
	 * 如果参数不合法 那么我们就必须使用抛出异常的方式 告诉方法的调用者 传递的参数有问题
	 * 注意:NullPointerException是一个运行期异常 我们不用处理 默认交给JVM处理
	 * ArrayIndexOutOfBoundsException是一个运行期异常 我们不用处理 默认交给JVM处理
	 */
	private static int getElement(int[] arr, int index) {
		/*
		 * 我们可以对传递过来的参数数组进行合法性校验 如果数组arr的值是null
		 * 那么我们就抛出空指针异常 告知方法的调用者“传递的数组的值是null”
		 */
		if (arr == null) {
			throw new NullPointerException("传递的数组的值是null");
		}
		
		/*
		 * 我们可以对传递过来的参数index进行合法性校验 如果index的范围不在数组的索引范围内
		 * 那么我们就抛出数组索引越界异常 告知方法的调用者“传递的索引超出了数组的使用范围”
		 */
		if (index < 0 || index > arr.length - 1) {
			throw new ArrayIndexOutOfBoundsException("传递的索引超出了数组的使用范围");
		}
		int ele = arr[index];
		return ele;
	}
}

throws关键字

一、throws关键字:异常处理的第一种方式 交给别人处理
二、作用:当方法内部抛出异常对象时 我们就必须处理这个异常对象
三、可以使用throws关键字处理异常对象 会把异常对象声明抛出给方法的调用者处理(自己不处理 给别人处理) 最终交给JVM处理 JVM采用中断处理
四、使用格式:在方法声明时使用

修饰符 返回值类型 方法名(参数列表) throws AAAException,BBBException...{//抛出了什么异常就写什么异常 抛出了几个异常就写几个异常
	throw new AAAException("产生原因");
	throw new BBBException("产生原因");
	...
}

【注】:

  1. throws关键字必须写在方法声明处
  2. throws关键字后边声明的异常必须是Exception或者是Exception的子类
  3. 方法内部如果抛出了多个异常对象 那么throws后边必须也声明多个异常 如果抛出的多个异常对象有子父类关系 那么直接声明父类异常即可
  4. 调用了一个声明抛出异常的方法 我们就必须处理声明的异常:要么继续使用throws声明抛出 交给方法的调用者处理 最终交给JVM 要么try...catch自己处理异常
  5. 使用throws声明抛出的弊端:因为JVM采取中断处理 所以抛出异常后面的代码就不执行了 我们可以使用try...catch来解决这个问题
import java.io.FileNotFoundException;
import java.io.IOException;
import javax.imageio.IIOException;

public class Throws {
	/*FileNotFoundException extends IOException extends Exception
	 * 所以可以省略FileNotFoundException异常对象不声明 
	 * 由于所有异常都是Exception的子类 所以也可以直接声明Exception
	 */
	public static void main(String[] args) throws /*FileNotFoundException,*/IOException {
		readFile("d:\\a.tx");
	}
	
	/*
	 * 定义一个方法 对传递的文件路径进行合法性判断
	 * 如果路径不是“c:\\a.txt” 那么我们就抛出文件找不到异常对象 告知方法的调用者
	 * 【注】:
	 * FileNotFoundException是编译异常 抛出了编译异常 就必须处理这个异常
	 * 可以使用throws继续声明抛出FileNotFoundException这个异常对象 让方法的调用者处理
	 */
	public static void readFile(String filename) throws /*FileNotFoundException,*/IOException{
		if (!filename.equals("c:\\a.txt")) {
			throw new FileNotFoundException("传递的文件路径不是c:\\a.txt");
		}
		//抛出了上面这个异常 下面的代码就不执行了 在主函数中写readFile("d:\\a.tx");
		//也不会抛出"文件的后缀名不对!"异常
		
		/*
		 * 如果传递的路径不是.txt结尾
		 * 那么我们就抛出IO异常对象 告知方法的调用者 文件的后缀名不对
		 */
		if (!filename.endsWith(".txt")) {
			throw new IOException("文件的后缀名不对!");
		}
		
		System.out.println("路径没有问题 读取文件");
	}
}

try…catch

一、try...catch:异常处理的第二种方式 自己处理
二、格式:

try{
	可能产生异常的代码
}catch(定义一个异常的变量 用来接收try中抛出的异常对象){
	异常的处理逻辑 产生异常对象之后 怎么处理异常对象
	一般在工作中 会把异常的信息记录到一个日志中
}
...
catch(异常类名 变量名){//可以有多个catch
	异常的处理逻辑
}

【注】:

  1. try中可能会抛出多个异常对象 那么就可以使用多个catch来处理这些异常对象
  2. 如果try中产生了异常 那么就会执行catch中的异常处理逻辑 执行完毕catch中的异常处理逻辑 继续执行try...catch之后的代码,如果try中没有产生异常 那么就不会执行catch中异常的处理逻辑 执行完try中的代码 继续执行try...catch之后的代码
  3. try...catch的弊端:当程序运行到try中的readFile("c:\\a.tx");时发现出现了这个异常,就把这个异常抛给catch处理 而tryreadFile("c:\\a.tx");的后续代码System.out.println("资源释放!");不会被执行,为了解决这个问题 我们可以使用finally代码块
import java.io.IOException;
public class TryCatch {
	public static void main(String[] args) {
		try {
			readFile("c:\\a.tx");
			System.out.println("资源释放!");
		} catch (Exception e) {//try中抛出什么异常对象(或者看方法中抛出的是什么异常) catch就定义什么异常变量 用来接收这个异常对象
			//异常的处理逻辑 产生异常对象之后 怎么处理异常对象
			//System.out.println("catch - 传递的文件后缀不是.txt");//为演示Throwable类中定义了3个异常处理的方法 先将这行代码注释
			
			/*
			 * Throwable类中定义了3个异常处理的方法:
			 * String getMessage():返回此throwable的简短描述
			 * String toString():返回此throwable的详细信息字符串
			 * void printStackTrace():JVM打印异常对象 默认此方法打印的异常信息最全面
			 */
			//System.out.println(e.getMessage());//文件的后缀名不对!
			//System.out.println(e.toString());//java.io.IOException: 文件的后缀名不对! 重写了Object类的toString
			//System.out.println(e);//java.io.IOException: 文件的后缀名不对!
			
			/*
			 * java.io.IOException: 文件的后缀名不对!
				at TryCatch.readFile(TryCatch.java:62)
				at TryCatch.main(TryCatch.java:27)
			 */
			e.printStackTrace();
		}
		System.out.println("后续代码");
	}
	
	/*
	 * 定义一个方法 对传递的文件路径进行合法性判断
	 * 如果路径不是“c:\\a.txt” 那么我们就抛出文件找不到异常对象 告知方法的调用者
	 * 【注】:
	 * FileNotFoundException是编译异常 抛出了编译异常 就必须处理这个异常
	 * 可以使用throws继续声明抛出FileNotFoundException这个异常对象 让方法的调用者处理
	 */
	public static void readFile(String filename) throws /*FileNotFoundException,*/Exception{
		/*
		 * 如果传递的路径不是.txt结尾
		 * 那么我们就抛出IO异常对象 告知方法的调用者 文件的后缀名不对
		 */
		if (!filename.endsWith(".txt")) {
			throw new IOException("文件的后缀名不对!");
		}
		
		System.out.println("路径没有问题 读取文件");
	}
}

finally代码块

一、格式:

try{
	可能产生异常的代码
}catch(定义一个异常的变量 用来接收try中抛出的异常对象){
	异常的处理逻辑 产生异常对象之后 怎么处理异常对象
	一般在工作中 会把异常的信息记录到一个日志中
}
...
catch(异常类名 变量名){//可以有多个catch
	异常的处理逻辑
}finally{
	无论是否出现异常都会执行
}

【注】:

  1. finally不能单独使用 必须和try一起使用
  2. finally一般用于资源释放(资源回收) 无论程序是否出现异常 最后都要资源释放(IO)
import java.io.FileNotFoundException;
import java.io.IOException;

public class CatchFinally {
	public static void main(String[] args) {
		try {
			readFile("d:\\a.tx");
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally {
			System.out.println("资源释放!");
		}
	}
	/*
	 * 定义一个方法 对传递的文件路径进行合法性判断
	 * 如果路径不是“c:\\a.txt” 那么我们就抛出文件找不到异常对象 告知方法的调用者
	 * 【注】:
	 * FileNotFoundException是编译异常 抛出了编译异常 就必须处理这个异常
	 * 可以使用throws继续声明抛出FileNotFoundException这个异常对象 让方法的调用者处理
	 */
	public static void readFile(String filename) throws /*FileNotFoundException,*/IOException{
		/*
		 * 如果传递的路径不是.txt结尾
		 * 那么我们就抛出IO异常对象 告知方法的调用者 文件的后缀名不对
		 */
		if (!filename.endsWith(".txt")) {
			throw new IOException("文件的后缀名不对!");
		}
		
		System.out.println("路径没有问题 读取文件");
	}
}

Exception

异常的产生过程解析 分析异常是怎么产生的 如何处理异常

public class ExceptionClass {
	public static void main(String[] args) {
		int[] arr = {1,2,3};//创建int类型的数组 并赋值
		int e = getElement(arr,3);//ArrayIndexOutOfBoundsException报错
		System.out.println(e);
	}

	/*
	 * 定义一个方法 获取数组指定索引处的元素
	 * 参数:
	 * 		int[] arr
	 * 		int index
	 */
	private static int getElement(int[] arr, int index) {
		int ele = arr[index];
		return ele;
	}
}
  1. 当程序执行到int e = getElement(arr,3);这条语句时 会调用getElement方法 并将参数arr,3传递给该方法
  2. 程序会访问数组3索引 而数组是没有3索引的 这时 JVM就会检测出程序会出现异常 此时 JVM会做两件事:
    (1)JVM根据异常产生的原因创建一个异常对象 这个异常对象包含了异常产生的(内容 原因 位置):new ArrayIndexOutOfBoundsException("3");
    (2)在getElement方法中 没有异常的处理逻辑(try...catch) 那么JVM就会把异常对象抛出给getElement方法的,调用者main来处理这个异常
  3. main方法接收到了这个异常对象new ArrayIndexOutOfBoundsException("3");main方法也没有异常的处理逻辑,继续把异常对象new ArrayIndexOutOfBoundsException("3");,抛给main方法的调用者JVM处理
  4. JVM接收到异常对象做了两件事:
    (1)把异常对象(内容 原因 位置)以红色的字体打印在控制台
    (2)JVM会终止当前正在执行的java程序 即中断处理

异常的注意事项

多个异常使用捕获又该如何处理:

  1. 多个异常分别处理
  2. 多个异常一次捕获 多次处理
  3. 多个异常一次捕获 一次处理
import java.util.List;
public class Exception2 {
	public static void main(String[] args) {
	//多个异常分别处理
//	try {
//		int[] arr = {1,2,3};
//		System.out.println(arr[3]);
//	} catch (ArrayIndexOutOfBoundsException e) {
//		System.out.println(e);
//	}
//	try {
//		List<Integer> list = List.of(1,2,3);
//		System.out.println(list.get(3));
//	} catch (IndexOutOfBoundsException e) {
//		System.out.println(e);
//	}
		
	//多个异常一次捕获 多次处理
//	try {
//		int[] arr = {1,2,3};
//		System.out.println(arr[3]);
//		List<Integer> list = List.of(1,2,3);
//		System.out.println(list.get(3));
//	} catch (ArrayIndexOutOfBoundsException e) {
//		System.out.println(e);
//	}catch (IndexOutOfBoundsException e) {
//		System.out.println(e);
//	}
	
	/*
	 * 一个try多个catch注意事项:
	 * catch里边定义定义的异常变量 如果有子父类关系 那么子类的异常变量必须写在上边 否则就会报错
	 * ArrayIndexOutOfBoundsException extends IndexOutOfBoundsException
	 * 如果将上述代码改为以下代码 会报错:
	 * try {
		int[] arr = {1,2,3};
		System.out.println(arr[3]);
		List<Integer> list = List.of(1,2,3);
		System.out.println(list.get(3));
		} catch (IndexOutOfBoundsException e) {
			System.out.println(e);
		}catch (ArrayIndexOutOfBoundsException e) {
			System.out.println(e);
		}
	 * 报错原因:
	 * try中可能会产生的异常对象:
	 * new ArrayIndexOutOfBoundsException("3");
	 * new IndexOutOfBoundsException("3");
	 * try中如果出现了异常对象 会把异常对象抛出给catch处理 抛出的异常对象会从上到下依次赋值给catch中定义的异常变量
	 * 如果按照上面的代码来执行的话 执行过程如下:
	 * ArrayIndexOutOfBoundsException e = new ArrayIndexOutOfBoundsException("3");
	 * IndexOutOfBoundsException e = new IndexOutOfBoundsException("3");
	 * 如果按照下面的代码来执行的话 执行过程如下:
	 * IndexOutOfBoundsException e = new ArrayIndexOutOfBoundsException("3");//多态
	 * IndexOutOfBoundsException e = new IndexOutOfBoundsException("3");
	 * 因为ArrayIndexOutOfBoundsException extends IndexOutOfBoundsException
	 * 所以IndexOutOfBoundsException可以接收IndexOutOfBoundsException("3");
	 * 也可以接收ArrayIndexOutOfBoundsException("3");
	 */
	
	//多个异常一次捕获 一次处理
	try {
		int[] arr = {1,2,3};
		System.out.println(arr[3]);
		List<Integer> list = List.of(1,2,3);
		System.out.println(list.get(3));
		} catch (IndexOutOfBoundsException e) {//catch定义的变量类型可以接收所有的异常
			System.out.println(e);
		}
	
	//运行时异常被抛出可以不处理 即不捕获也不声明抛出
	//默认给虚拟机处理 终止程序 什么时候不抛出运行时异常了 再继续执行程序
	}
}

如果finally中有return语句 永远返回finally中的结果 避免出现这种情况 即避免在finally中写return

public class Exception3 {
	public static void main(String[] args) {
		int a = getA();
		System.out.println(a);
	}

	public static int getA() {
		int a = 10;
		try {
			return a;
		} catch (Exception e) {
			System.out.println(e);
		}finally {
			//一定会执行的代码
			a = 100;
			return a;
		}
	}
}

自定义异常类

一、自定义异常类:java提供的异常类不够我们使用 需要自己定义一些异常类
二、格式:

public class XXXException extends Exception(RuntimeException){
	添加一个空参数的构造方法
	添加一个带异常信息的构造方法
}

【注】:

  1. 自定义异常类一般都是以Exception结尾 说明该类是一个异常类
  2. 自定义异常类 必须继承Exception或者RuntimeException
    (1)继承Exception:那么自定义的异常类就是一个编译器异常 如果方法内部抛出了编译器异常 就必须处理这个异常 要么try...catch 要么throws
    (2)继承RuntimeException:那么自定义的异常类就是一个运行期异常 无需处理 交给虚拟机处理 虚拟机中断处理
public class RegisterException extends Exception{
	//添加一个空参数的构造方法
	public RegisterException() {
		super();
	}
	/*
	 * 添加一个带异常信息的构造方法
	 * 查看源码发现 所有的异常类都会有一个带异常信息的构造方法 方法内部会调用父类带异常信息的构造方法 让父类来处理这个异常信息
	 */
	public RegisterException(String message) {
		super(message);
	}
}

子父类的异常

一、如果父类抛出了多个异常 子类重写父类方法时 抛出和父类相同的异常 或者是父类异常的子类或者不抛出异常
二、父类方法没有抛出异常 子类重写父类该方法时也不可以抛出异常 此时子类产生该异常 只能捕获处理 不能声明抛出

【注】:父类异常是什么样 子类异常就是什么样

public class Fu {
	public void show01() throws NullPointerException,ClassCastException{}
	public void show02() throws IndexOutOfBoundsException{}
	public void show03() throws IndexOutOfBoundsException{}
	public void show04() {}
}

class Zi extends Fu{
	//如果父类抛出了多个异常 子类重写父类方法时 抛出和父类相同的异常
	public void show01() throws NullPointerException,ClassCastException{}
	//子类重写父类方法时 抛出父类异常的子类
	//ArrayIndexOutOfBoundsException是IndexOutOfBoundsException的子类
	public void show02() throws ArrayIndexOutOfBoundsException{}
	//子类重写父类方法时 不抛出异常
	//不管父类抛出什么异常 子类不抛出任何异常
	public void show03() {}
	//父类方法没有抛出异常 子类重写父类该方法时也不可以抛出异常
	//public void show04() {}//为了演示后续代码而将其注释掉
	//父类方法没有抛出异常 如果子类产生该异常 只能try...catch捕获处理 不能throws声明抛出
	public void show04() {
		try {
			throw new Exception("编译器异常");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

Objects类中的静态方法

一、Objects类中的静态方法:public static <T> T requireNonNull(T obj):查看指定引用对象不是null
二、源码:

public static <T> T requireNonNull(T obj){
	if(obj == null)//如果这个对象是null
	throw new NullPointerException();//抛出异常
	return obj;//如果这个对象不是null
}
import java.lang.reflect.Method;
public class Objects {
	public static void main(String[] args) {
		method(null);
	}

	public static void method(Object object) {
		//对传递过来的参数进行合法性判断 判断是否为null
//		if (object == null) {
//			throw new NullPointerException("传递的对象的值是null");
//		}
		//对上面的代码进行简化
		//Objects.requireNonNull(object);
		Objects.requireNonNull(object,"传递的对象的值是null");//这个方法是上一个方法的重载方法 
		//效果和简化前的代码一致
	}
}

多线程

创建多线程程序的方式

一、创建多线程程序的第一种方式:创建Thread类的子类

  1. java.util.Thread类:是描述线程的类 我们想要实现多线程程序 就必须继承Thread类
  2. 实现步骤:
    (1)创建一个Thread类的子类
    (2)在Thread类的子类中重写Thread类中的run方法 设置线程任务(开启线程要做什么?)
    (3)在main方法中创建Thread类的子类对象
    (4)在main方法中调用Thread类中的方法start方法 开启新的线程 执行run方法
    void start():使该线程开始执行 java虚拟机调用该线程的run方法
    结果是两个线程并发的运行 当前线程(main线程)和另一个线程(创建的新线程 执行其run方法)
    多次启动一个线程是非法的 特别是当线程已经结束执行后 不能再重新启动
    java程序属于抢占式调度 那个线程的优先级高 哪个线程优先执行 同一个优先级随机选择一个执行
public class ThreadClass {
	public static void main(String[] args) {
		//3.创建Thread类的子类对象
		MyThread mt = new MyThread();
		//4.调用Thread类中的方法start方法 开启新的线程 执行run方法
		mt.start();
		for (int i = 0; i < 20; i++) {
			System.out.println("main:" + i);
		}
	}
}

public class MyThread extends Thread{//1. 创建一个Thread类的子类
	//2.在Thread类的子类中重写Thread类中的run方法 设置线程任务(开启线程要做什么?)
	@Override
	public void run() {
		for (int i = 0; i < 20; i++) {
			System.out.println("run:" + i);
		}
	}
}
  1. 这个多线程程序的执行过程:
    (1)JVM执行main方法 找操作系统开辟一条main方法通向淳朴的路径 这个路径叫main(主)线程 CPU通过这个线程,这个路径可以执行main方法
    (2)然后执行MyThread mt = new MyThread();因为创建了一个对象 所以有开辟了一条通向CPU的路径,这条路径通过mt.start();执行run方法 对于CPU而言 此时就有了两条执行的路径 CPU就有了选择的权利 CPU想执行哪条路径就执行哪条路径 我们控制不了CPU 于是就有了程序的随机打印结果 反过来说 两个线程 一个main线程 一个新线程 一起抢夺CPU的执行权(也叫执行时间)谁抢到了谁执行对应的代码
  2. 多线程的好处:多个线程之间互不影响 因为他们在不同的栈空间

二、创建多线程程序的第二种方式:实现Runnable接口:java.lang.Runnable

  1. Runnable接口应该由那些打算通过某一线程执行其实例的类来实现 类必须定义一个成为run的无参数方法
  2. java.lang.Thread类的构造方法:
    (1)Thread(Runnable target):分配新的Thread对象
    (2)Thread(Runnable target,String name):分配新的Thread对象
  3. 实现步骤:
    (1)创建一个Runnable接口的实现类
    (2)在实现类中重写Runnable接口的run方法 设置线程任务
    (3)创建一个Runnable接口的实现类对象
    (4)创建Thread类对象 构造方法中传递Runnable接口的实现类对象
    (5)调用Thread类中的start方法 开启新的线程执行run方法
  4. 实现Runnable接口创建多线程程序的好处:
    (1)避免了单继承的局限性:一个类只能继承一个类(一个人只能有一个亲爹) 类继承了Thread类就不能继承其他的类,实现了Runnable接口 还可以继承其他的类 实现其他的接口
    (2)增强了程序的扩展性 降低了程序的耦合性(解耦),实现Runnable接口的方式 把设置线程任务和开启新线程进行了分离(解耦),实现类中 重写了run方法:用来设置线程任务,创建Thread类对象 调用start方法:用来开启新线程
public class RunnableClass {
	public static void main(String[] args) {
		//3.创建一个Runnable接口的实现类对象
		RunnableImpl run = new RunnableImpl();
		//4.创建Thread类对象 构造方法中传递Runnable接口的实现类对象
		Thread thread = new Thread(run);
		//5.调用Thread类中的start方法 开启新的线程执行run方法
		thread.start();
		for (int i = 0; i < 20; i++) {
			System.out.println(Thread.currentThread().getName() + "-->" + i);
		}
	}
}

//1.创建一个Runnable接口的实现类
public class RunnableImpl implements Runnable{
	//2.在实现类中重写Runnable接口的run方法 设置线程任务
	@Override
	public void run() {
		for (int i = 0; i < 20; i++) {
			System.out.println(Thread.currentThread().getName() + "-->" + i);
		}
	}
}

设置线程的名称

一、使用Thread类中的方法setName(名字):void setName(String name):改变线程名称 使之与参数name相同
二、创建一个带参数的构造方法 参数传递线程的名称 调用父类的带参构造方法 把线程名称传递给父类 让父类(Thread)给子线程起一个名字:Thread(String name):分配新的Thread对象

public class ThreadMethod2 {
	public static void main(String[] args) {
		new Method2MyThread("旺财").start();//开启多线程
		Method2MyThread mt = new Method2MyThread();
		mt.setName("小强");
		mt.start();
	}
}

public class Method2MyThread extends Thread{
	public Method2MyThread() {}
	public Method2MyThread(String name) {
		super(name);//把线程名称传递给父类 让父类(Thread)给子线程起一个名字
	}

	@Override
	public void run() {
		//获取线程名称
		System.out.println(Thread.currentThread().getName());
	}
}

获取线程的名称

一、使用Thread类中的方法getName()String getName():返回该线程的名称
二、线程的名称:

  1. 主线程:main
  2. 新线程:Thread-0Thread-1
    三、可以先获取到当前正在执行的线程 使用线程中的方法getName()获取线程的名称:static Thread currentThread():返回对当前正在执行的线程对象的引用
public class ThreadMethod1 {
	public static void main(String[] args) {
//		//3.创建Thread类的子类对象
//		MethodMyThread mt = new MethodMyThread();
//		//4.调用Thread类中的方法start方法 开启新的线程 执行run方法
//		mt.start();
//		new MethodMyThread().start();
//		new MethodMyThread().start();
		
		//可以使用链式编程直接获取名称 上面的所有代码可以优化成一行代码 为演示现将他们全部注释掉
		System.out.println(Thread.currentThread().getName());
		/*
		 * 获取主线程的名称 不能直接通过getName方法获取 因为ThreadMethod1类没有继承Thread类 没有getName方法
		 * 必须先获取当前正在执行的线程 再使用getName()获取
		 */
	}
}

//1.创建一个Thread类的子类
public class MethodMyThread extends Thread{
	//2.在Thread类的子类中重写Thread类中的run方法 设置线程任务(开启线程要做什么?)
	@Override
	public void run() {
		//获取线程名称
//		String nameString = getName();
//		System.out.println(nameString);
		//为演示另一个获取线程名称的代码而将上述代码注释
		
		Thread thread = Thread.currentThread();//获取当前正在执行的线程的名称
		System.out.println(thread);
		
		String nameString = thread.getName();//使用当前正在执行的线程中的方法getName()获取当前线程的名称
		System.out.println(nameString);		
	}
}

暂停线程

public static void sleep(long millis):使用当前正在执行的线程以指定的毫秒数暂停(暂时停止运行),毫秒数结束后 线程继续执行

public class ThreadMethod3 {
	public static void main(String[] args) {
		//模拟秒表
		for (int i = 0; i <= 60; i++) {
			System.out.println(i);
			//使用Thread类的sleep方法让程序睡眠一秒钟
			try {
				Thread.sleep(1000);//暂停1000毫秒 也就是一秒
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
}

唤醒的方法

一、void notify():唤醒在此对象监视器上等待的单个线程
二、void notifyAll():唤醒在此对象监视器上等待的所有线程

public class Notify {
	public static void main(String[] args) {
		//创建锁对象 保证唯一
		Object object = new Object();
		//创建一个顾客线程(消费者)
		new Thread() {
			@Override
			public void run() {
				//保证等待和唤醒只有一个在执行 需要使用同步技术
				synchronized (object) {
					System.out.println("顾客1告知老板要的包子的种类和数量");
				}
				try {
					object.wait();
				} catch (Exception e) {
					e.printStackTrace();
				}
				//唤醒之后执行的代码
				System.out.println("包子做好了 顾客1开吃!");
				System.out.println("=============================");
			}
		}.start();
		//创建一个顾客线程(消费者)
		new Thread() {
			@Override
			public void run() {
				//保证等待和唤醒只有一个在执行 需要使用同步技术
				synchronized (object) {
					System.out.println("顾客2告知老板要的包子的种类和数量");
				}
				try {
					object.wait();
				} catch (Exception e) {
					e.printStackTrace();
				}
				//唤醒之后执行的代码
				System.out.println("包子做好了 顾客2开吃!");
				System.out.println("=============================");
			}
		}.start();
				
		// 创建一个老板线程(生产者)
		new Thread() {
			@Override
			public void run() {
				//一直做包子
				//花5秒做包子
				try {
					Thread.sleep(5000);
				} catch (Exception e) {
					e.printStackTrace();
				}
							
				//保证等待和唤醒只有一个在执行 需要使用同步技术
				synchronized (object) {
					System.out.println("老板5秒之后做好包子 告知顾客 可以吃包子了");
					//做好包子之后 调用notify方法 唤醒顾客吃包子
					//object.notify();//依次唤醒顾客1 顾客2
					object.notifyAll();//一次性唤醒顾客1 顾客2
				}
			}
		}.start();
	}
}

模拟卖票案例

一、多线程访问了共享的数据 会产生线程安全问题
二、模拟卖票案例:创建三个线程 同时开启 对共享的票进行出售

  1. 运行程序后出现了线程安全问题:卖票出现了重复的票和不存在的票
    (1)出现了重复的票:t0t1t2同时执行到了打印输出正在卖第+“ticket”张票 这时ticket还没有执行到--
    (2)出现不存在的票:假设t0线程抢到了CPU的执行权 进入到run方法中执行 执行到if语句 就失去了CPU的执行权,然后t2线程抢到了CPU的执行权 进入到run方法中执行 执行到if语句 就失去了CPU的执行权,然后t1线程抢到了CPU的执行权 进入到run方法中执行 执行到if语句 就失去了CPU的执行权,然后t2线程又抢到了CPU的执行权 继续执行卖票 输出打印正在卖第+“ticke”张票 假设此时ticket=1, 那么ticket--后,ticket=0 继续判断ticket>0不成立 不再执行,然后t1线程又抢到了CPU的执行权 继续执行卖票 输出打印正在卖第0张票 那么ticket--ticket=-1 继续判断ticket>0不成立 不再执行。然后t0线程又抢到了CPU的执行权 继续执行卖票 输出打印正在卖第-1张票 那么ticket--ticket=-2 继续判断ticket>0不成立 不再执行

注意:线程安全问题是不能产生的 我们可以让一个线程在访问共享数据时 无论是否失去了CPU执行权 让其他的线程只能等待 等待当前线程卖完票 其他线程再进行卖票 一定要保证使用一个线程在卖票

  1. 为了解决这个问题 有三种方法:
    (1)同步代码块
    (2)同步方法
    (3)锁机制
public class Ticket {
	public static void main(String[] args) {
		//创建Runnable接口的实现类对象
		TicketRunnableImpl runnableImpl = new TicketRunnableImpl();
		System.out.println("run:" + runnableImpl);//run:TicketRunnableImpl@15db9742
		//创建thread类对象 构造方法中传递Runnable接口的实现类对象
		Thread t0 = new Thread(runnableImpl);
		Thread t1 = new Thread(runnableImpl);
		Thread t2 = new Thread(runnableImpl);
		//调用start方法开启多线程
		t0.start();
		t1.start();
		t2.start();
	}
}

实现卖票案例

一、解决线程安全问题的一种方案:使用同步代码块

  1. 格式:
synchronized(锁对象){
	可能会出现线程安全问题的代码(访问了共享数据的代码)
}

注意:

  1. 通过代码块中的锁对象 可以使用任意的对象
  2. 但是必须保证多个线程使用的锁对象是同一个
  3. 锁对象作用:把同步代码块锁住 只让一个线程在同步代码块中执行

二、解决线程安全问题的二种方案:使用同步方法

  1. 使用步骤:
    (1)把访问了共享数据的代码抽取出来 放到一个方法中
    (2)在方法上添加synchronized修饰符
    (3)格式:定义方法的格式
修饰符 synchronized 返回值类型 方法名(参数列表){
	可能会出现线程安全问题的代码(访问了共享数据的代码)
}

三、解决线程安全问题的二种方案:使用lock

  1. java.util.concurrent.locks.lock接口
  2. lock实现提供了比使用synchronized方法和语句可获得的更广泛的锁定操作
  3. lock接口中的方法:
    (1)void lock()获取锁
    (2)void unlock()释放锁
  4. 使用步骤:
    (1)在成员位置创建一个ReentrantLock对象
    (2)在可能会出现安全问题的代码前调用Lock接口中的方法Lock获取锁
    (3)在可能会出现安全问题的代码后电泳Lock接口中的方法unLock释放锁
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TicketRunnableImpl implements Runnable{
	//定义一个多线程共享的票源
	//private int ticket = 100;//同步代码块和同步方法使用这个
	private static int ticket = 100;//静态方法时使用这个
	
	//1.在成员位置创建一个ReentrantLock对象
	Lock l = new ReentrantLock();
	
	//创建一个锁对象
	Object object = new Object();
	
	//设置线程任务:卖票
	@Override
	public void run() {
		System.out.println("this:" + this);//this:TicketRunnableImpl@15db9742
		//使用死循环 让卖票操作重复执行
		while(true) {
//			//先判断票是否存在
//			if (ticket > 0) {
//				//票存在 卖票 ticket--
//				System.out.println(Thread.currentThread().getName() + "-->" +"正在卖第" + ticket + "张票");
//				ticket--;
//			}
//		}
		//以上代码会导致线程安全问题 现做如下优化
		//同步代码块
			//可能会出现线程安全问题的代码(访问了共享数据的代码):ticket是共享数据 所有跟ticket有关的代码都是
//			synchronized (object) {
//				//先判断票是否存在
//				if (ticket > 0) {
//					//票存在 卖票 ticket--
//					System.out.println(Thread.currentThread().getName() + "-->" +"正在卖第" + ticket + "张票");
//					ticket--;
//				}
//			}
			//payTicket();//调用同步方法
			//payTicketStatic();//调用静态方法
			//2.在可能会出现安全问题的代码前调用Lock接口中的方法Lock获取锁
			l.lock();
			//先判断票是否存在
//			if (ticket > 0) {
//				//票存在 卖票 ticket--
//				System.out.println(Thread.currentThread().getName() + "-->" +"正在卖第" + ticket + "张票");
//				ticket--;
//			}
//			//3.在可能会出现安全问题的代码后电泳Lock接口中的方法unLock释放锁
//			l.unlock();
			//以上代码可以被优化
			if (ticket > 0) {
				try {
					//票存在 卖票 ticket--
					System.out.println(Thread.currentThread().getName() + "-->" +"正在卖第" + ticket + "张票");
				} catch (Exception e) {
					// TODO: handle exception
					e.printStackTrace();
				}finally {
					//3.在可能会出现安全问题的代码后电泳Lock接口中的方法unLock释放锁
					l.unlock();//无论程序是否异常 都会把锁释放
				}
			}
		}
	}
/*同步技术的原理:
 * 使用了一个锁对象 这个锁对象脚同步锁 也叫对象锁 也叫对象监视器
 * 3个线程一起抢夺CPU的执行权 谁抢到了谁执行run方法进行卖票
 * 假设t0抢到了CPU的执行权 执行run方法 遇到了synchronized代码块
 * 这时t0会检查synchronized代码块是否有锁对象 发现有 就会获取到锁对象(把锁对象拿走) 进入到同步中执行
 * 假设此时t1也抢到了CPU的执行权 执行run方法 遇到了synchronized代码块
 * 这时t1会检查synchronized代码块是否有锁对象 发现没有 t1就会进入阻塞状态 会一直等待t0线程归还锁对象
 * 一直到t0线程执行完同步中的代码 会把锁对象归还给同步代码块 t1才能获取到锁对象 进入到同步中执行
 * 总结:同步中的线程 没有执行完毕不会释放锁 同步外的线程没有锁进不去同步
 */
	
	//为演示第二种方法 现将上述代码注释
	/*定义一个同步方法
	 * 同步方法也会把方法内部的代码锁住 只让一个线程执行 同步方法的锁对象是谁?就是实现类对象 new RunnableImpl()
	 * 也就是this
	 */
//	public synchronized void payTicket() {
//		//先判断票是否存在
//		if (ticket > 0) {
//			//票存在 卖票 ticket--
//			System.out.println(Thread.currentThread().getName() + "-->" +"正在卖第" + ticket + "张票");
//			ticket--;
//		}
//	}
	
	//为演示另一种方法 现将上述代码注释
	/*
	 * 静态的同步方法
	 * 锁对象是谁?不能是this this是创建对象之后产生的 静态方法优先于对象 静态方法的锁对象是本类的class属性 也就是class文件对象(反射)
	 */
	public static synchronized void payTicketStatic() {
		//先判断票是否存在
		if (ticket > 0) {
			//票存在 卖票 ticket--
			System.out.println(Thread.currentThread().getName() + "-->" +"正在卖第" + ticket + "张票");
			ticket--;
		}
	}
}

卖包子案例

一、等待唤醒案例:线程之间的通信
二、实现卖包子案例:

  1. 创建一个顾客线程(消费者):告知老板要的包子的种类和数量 调用wait方法 放弃CPU的执行 进入到WAITING状态(无限等待)
  2. 创建一个老板线程(生产者):花了5秒做包子 做好包子之后 调用notify方法 唤醒顾客吃包子

注意:

  1. 顾客和老板线程必须使用同步代码块包裹起来 保证等待和唤醒只有一个在执行
  2. 同步使用的锁对象必须保证唯一
  3. 只有锁对象才能调用wait和notify方法

三、Objects类中的方法:

  1. void wait():在其他线程调用此对象的notify()方法或notifyAll()方法前 导致当前线程等待
  2. void notify():唤醒在此对象监视器上等待的单个线程 会继续执行wait方法之后的代码
public class WaitAndNotify {
	public static void main(String[] args) {
		//创建锁对象 保证唯一
		Object object = new Object();
		//创建一个顾客线程(消费者)
		new Thread() {
			@Override
			public void run() {
				//保证等待和唤醒只有一个在执行 需要使用同步技术
				synchronized (object) {
					System.out.println("告知老板要的包子的种类和数量");
				}
				try {
					object.wait();
				} catch (Exception e) {
					e.printStackTrace();
				}
				//唤醒之后执行的代码
				System.out.println("包子做好了 开吃!");
				System.out.println("=============================");
			}
		}.start();
		
		// 创建一个老板线程(生产者)
		new Thread() {
			@Override
			public void run() {
				//一直做包子
				//花5秒做包子
				try {
					Thread.sleep(5000);
				} catch (Exception e) {
					e.printStackTrace();
				}
					
				//保证等待和唤醒只有一个在执行 需要使用同步技术
				synchronized (object) {
					System.out.println("老板5秒之后做好包子 告知顾客 可以吃包子了");
					//做好包子之后 调用notify方法 唤醒顾客吃包子
					object.notify();
				}
			}
		}.start();
	}
}

四、进入到TimeWaiting(计时等待)有两种方式:

  1. 使用sleep(long m)方法 在毫秒值结束之后 线程睡醒进入到Runnable/Blocked状态
  2. 使用wait(long m)方法 wait方法如果在毫秒值结束之后 还没有被notify唤醒 就会自动醒来
public class WaitAndNotify2 {
	public static void main(String[] args) {
		//创建锁对象 保证唯一
		Object object = new Object();
		//创建一个顾客线程(消费者)
		new Thread() {
			@Override
			public void run() {
				//保证等待和唤醒只有一个在执行 需要使用同步技术
				synchronized (object) {
					System.out.println("告知老板要的包子的种类和数量");
				}
				try {
					object.wait(5000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
						
				//唤醒之后执行的代码
				System.out.println("包子做好了 开吃!");
				System.out.println("=============================");
			}
		}.start();
	}
}

线程间通信

一、线程间通信:多个线程在处理同一个资源 但是处理的动作(线程任务)却不相同,比如:线程1(包子铺)生产包子 线程2(吃货)吃包子 包子可以理解为同一资源 线程1和线程2处理的动作 一个是生产一个是消费 那么线程1和线程2之间就存在线程通信问题
二、用等待唤醒机制来保证线程间通信有效利用资源
三、等待与唤醒机制:线程之间的通信
四、重点:有效的利用资源(生产一个包子 吃一个包子 再生产一个包子 再吃一个包子……)
五、通信:对包子的状态进行判断

  1. 没有包子–》吃货线程唤醒包子铺线程–》吃货线程等待–》包子铺线程做包子–》做好包子–》修改包子的状态为有
  2. 有包子–》包子铺线程唤醒吃货线程–》包子铺线程等待–》吃货吃包子–》吃完包子–》修改包子的状态为没有
    六、等待唤醒中的方法:
  3. wait:线程不再活动 不再参与调度 进入wait set中 因此不会浪费CPU资源 也不会去竞争锁 这时的线程状态是waiting,他还要等着别的线程执行一个特别的动作——notify 也就是通知(notify)在这个对象上等待的线程从wait set中释放出来 重新进入到调度队列(ready queue)中
  4. notify:选取所通知对象的wait set中的一个线程释放 如:餐馆有空位后 等餐最久的顾客先入座
  5. notifyAll:释放所通知对象的wait set上的全部线程

【注】:哪怕只通知了一个等待的线程 被通知线程也不能立即恢复执行 因为他当初中断的地方是在同步块内 而此刻他已经不持有锁,所以他需要再次尝试去获取锁(很可能面临其他线程的竞争) 成功后才能在当初调用wait方法之后的地方恢复执行

七、总结:如果能获取锁 线程就从等待状态变成执行状态 否则 从wait set出来 又进入entry set 线程就从等待状态又变成阻塞状态
八、调用waitnotify方法需要注意的细节:

  1. wait方法和notify方法必须要由同一个锁对象调用 因为对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程
  2. wait方法与notify方法是属于Object类的方法的 因为锁对象可以是任意对象 而任意对象的所属类都是继承了Object类的对象
  3. wait方法与notify方法必须要在同步代码块或者同步函数中使用 因为必须通过锁对象调用者两个方法

九、等待唤醒机制需求分析:需要哪些类

  1. 资源类:包子类
    设置包子的属性:皮 馅
    包子的状态:有true 没有false
  2. 生产者(包子铺)类:是一个线程类 可以继承Thread
    设置线程任务(run):生产包子
    对包子的状态进行判断:
    true:有包子 包子铺调用wait方法进入等待状态
    false:没有包子 包子铺交替生产两种包子 有两种状态(i%2==0) 包子铺生产好了包子 修改包子的状态为true有 唤醒吃货线程 让吃货线程吃包子
  3. 消费者(吃货)类:是一个线程类 可以继承Thread
    设置线程任务(run):吃包子
    对包子的状态进行判断:
    false:没有包子 吃货线程调用wait方法进入等待状态
    true:有包子 吃货吃包子 吃货吃完包子 修改包子的状态为false没有 吃货唤醒包子铺线程 生产包子
  4. 测试类:
    包含main方法 程序执行的入口 启动程序
    创建包子对象:创建包子铺线程 开启 创建吃货线程 开启
public class WaitAndNotify {
	public static void main(String[] args) {
		//创建包子对象
		BaoZi bz = new BaoZi();
		//创建包子铺线程 开启 生产包子
		new BaoZiPu(bz).start();
		//创建吃货线程 开启 吃包子
		new ChiHuo(bz).start();
	}
}
public class BaoZi {
	String pi;//皮
	String xian;//馅
	//包子的状态:有true 没有false 设置初始值为false没有包子
	boolean flag = false;
}
/*
 * 包子铺和包子的线程关系:互斥关系 通信关系。必须同时同步技术保证两个线程只能有一个在执行
 * 锁对象必须保证唯一 可以使用包子对象作为锁对象
 * 包子铺类和吃货类就需要把包子对象作为参数传递进来:
 * 1.需要在成员位置创建一个包子变量
 * 2.使用带参数构造方法 为这个包子变量赋值
 */
public class BaoZiPu extends Thread{
	//1.需要在成员位置创建一个包子变量
	private BaoZi bz;
	// 2.使用带参数构造方法 为这个包子变量赋值
	public BaoZiPu(BaoZi bz) {
		this.bz = bz;
	}
	
	//设置线程任务run:生产包子
	@Override
	public void run() {
		int count = 0;
		//让包子铺一直生产包子
		while (true) {
			//必须同时同步技术保证两个线程只能有一个在执行
			synchronized (bz) {
				//对包子的状态进行判断
				if(bz.flag == true) {
					//包子铺调用wait方法进入等待状态
					try {
						bz.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			
				//被唤醒之后执行 包子铺交替生产两种包子
				if (count % 2 == 0) {
					//生产薄皮三鲜馅包子
					bz.pi = "薄皮";
					bz.xian = "三鲜馅";
				}else {
					//生产冰皮牛肉大葱馅包子
					bz.pi = "冰皮";
					bz.xian = "牛肉大葱馅";
				}
				count++;
				System.out.println("包子铺正在生产:" + bz.pi + bz.xian + "包子");
				//生产包子需要3秒
				try {
					Thread.sleep(3000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				//包子铺生产好了包子
				//修改包子的状态为true有
				bz.flag = true;
				//唤醒吃货线程 让吃货线程吃包子
				bz.notify();
				System.out.println("包子铺已经生产好了:" + bz.pi + bz.xian + "包子,吃货可以开始吃了");
			}
		}
	}
}
public class ChiHuo extends Thread{
	//1.需要在成员位置创建一个包子变量
	private BaoZi bz;
	// 2.使用带参数构造方法 为这个包子变量赋值
	public ChiHuo(BaoZi bz) {
		this.bz = bz;
	}
	
	//设置线程任务run:生产包子
	@Override
	public void run() {
		while (true) {
			//必须同时同步技术保证两个线程只能有一个在执行
			synchronized (bz) {
				//对包子的状态进行判断
				if(bz.flag == false) {
					//包子铺调用wait方法进入等待状态
					try {
						bz.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				//被唤醒之后执行的代码:吃包子
				System.out.println("吃货正在吃:" + bz.pi +bz.xian + "的包子");
				//吃货吃完包子修改包子的状态为false没有
				bz.flag = false;
				//吃货唤醒包子铺线程 生产包子
				bz.notify();
				System.out.println("吃货已经把" + bz.pi +bz.xian + "的包子吃完了,包子铺开始生产包子");
				System.out.println("==========================================================");
			}
		}
	}
}

线程池

一、java.util.concurrent.Executors:线程池的工厂类 用来生成线程池
二、Executors类中的静态方法:

  1. static ExecutorService newFixedThreadPool(int nThreads):创建一个可重用固定线程数的线程池
    (1)参数:int nThreads:创建线程池中包含的线程数量
    (2)返回值:ExecutorService接口 返回的是ExecutorService接口的实现类对象 我们可以使用ExecutorService接口接收(面向接口编程)
  2. java.util.concurrent.ExecutorService:线程池接口
  3. submit(Runnable task):提交一个Runnable任务用于执行 用来从线程池中获取线程 调用start方法 执行线程任务
  4. void shutdown():关闭/销毁线程池的方法

三、线程池的使用步骤:

  1. 使用线程池的工厂类Executors里面提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池
  2. 创建一个类 实现Runnable接口 重写run方法 设置线程任务
  3. 调用ExecutorService中的方法submit 传递线程任务(实现类) 开启线程 执行run方法
  4. 调用ExecutorService中的方法shutdown销毁线程池(不建议)
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPool {
	public static void main(String[] args) {
		// * 1.使用线程池的工厂类Executors里面提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池
		ExecutorService es = Executors.newFixedThreadPool(2);
		// * 3.调用ExecutorService中的方法submit 传递线程任务(实现类) 开启线程 执行run方法
		//我们刚才只创建了一个容量为2的线程池 而我们现在有三个任务 所以有一个任务就等待有任务归还给线程池 归还完了他再使用
		//线程池会一直开启 使用完了线程 会自动把线程归还给线程池 线程可以继续使用
		es.submit(new RunnableImpl());
		es.submit(new RunnableImpl());
		es.submit(new RunnableImpl());
		// * 4.调用ExecutorService中的方法shutdown销毁线程池(不建议)
		es.shutdown();
		//es.submit(new RunnableImpl());//错误!线程池已经被销毁了 不能再执行任务
	}
}
public class RunnableImpl implements Runnable{
	@Override
	public void run() {
		System.out.println(Thread.currentThread().getName() + "创建了一个新线程");
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值