多线程
思维导图看天下:
1. 概述
并行与并发
并行 :指两个或多个事件在同一时刻发生(同时发生)
并发 :指两个或多个事件在同一个时间段内发生。(交替执行)
线程与进程
进程:是指一个内存中运行的程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程
记忆:进程的英文为Process,Process也为过程,所以进程可以大概理解为程序执行的过程。
(进程也是程序的一次执行过程,是系统运行程序的基本单位; 系统运行一个程序即是一个进程从创建、运行到消亡的过程)
线程:进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。【java默认有两个线程:main、GC】
进程与线程的区别:
- 进程:有独立的内存空间,进程中的数据存放空间(堆空间和栈空间)是独立的,至少有一个线程。
- 线程:堆空间是共享的,栈空间是独立的,线程消耗的资源比进程小的多
2. 线程创建的五种方式
推荐使用Runnable接口的方式,因为Java是单继承的,所以使用Thread有OPP单继承局限性
2.1 背景介绍
线程类
Java使用 java.lang.Thread 类代表线程,所有的线程对象都必须是Thread类或其子类的实例
每个线程的作用是完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码
Java使用线程执行体来代表这段程序流。
2.2 ① 继承Thread类
2.2.1 线程实现
1)实现步骤
- 继承Thread类的子类,并重写该类的run()方法(该run()方法的方法体就代表了线程需要完成的任务,因此run()方法称为线程执行体)
- 创建Thread子类的实例,即创建了线程对象
- 调用线程对象的start()方法来启动该线程
2)实现案例
自定义线程类:
主函数:
public static void main(String[] args) { MyThread myThread = new MyThread("MyThread"); myThread.start(); for (int i = 0;i<1000;i++){ System.out.println("main"+i); } }
执行结果:
3)执行过程分析
过程:程序启动运行main时候,java虚拟机启动一个进程,主线程main在main()调用时候被创建
随着调用Mt类的对象的start方法,另外一个新的线程也启动了 ,这样,整个应用就在多线程下运行。
运行时序图:
内存结构:
4)调用start和run方法的区别
2.2.2 构造方法
- public Thread()
分配一个新的线程对象。 - public Thread(String name)
分配一个指定名字的新的线程对象 - public Thread(Runnable target)
分配一个带有指定目标新的线程对象 - public Thread(Runnable target,String name)
分配一个带有指定目标新的线程对象并指定名字
2.2.3 常用方法
- public String getName() :获取当前线程名称。
- public void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法
- public void run() :此线程要执行的任务在此处定义代码。
- public static void sleep(long millis) :使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
- public static Thread currentThread() :返回对当前正在执行的线程对象的引用。
1)获取线程名称
-
可以使用Thread类中的方法getName,
String getName() 返回该线程的名称。 -
可以先获取当前正在执行的线程,再调用getName方法获取线程名称
static Thread currentThread() 返回对当前正在执行的线程对象的引用
//1.可以使用Thread类中的方法getName String name = getName(); System.out.println(name);//创建时, 指定了名称,获取的就是指定的名称 //如果没有指定名称,获取的就是Thread-0 //2.可以先获取当前正在执行的线程 Thread currentThread = Thread.currentThread(); System.out.println(currentThread);//Thread[Thread-0,5,main] String name2 = currentThread.getName(); System.out.println(name2);//Thread-0
2)设置线程名称
- 方法一:可以使用Thread类中的方法setName
void setName(String name) 改变线程名称,使之与参数 name 相同。
MyThread myThread = new MyThread(); myThread.setName("myThreadName"); myThread.start();
- 方法二:添加一个带参构造方法,参数传递线程的名称;调用父类的带参构造方法,把名字传递给父类,让父亲给儿子起名字
Thread(String name) 分配新的 Thread 对象。
public class MyThread extends Thread{ //定义指定线程名称的构造方法 public MyThread(String name) { super(name); }
3)线程休眠
public static void sleep(long millis)
使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)睡醒了,继续执行
/*程序在执行第二秒时, 会暂停2秒,2秒后,继续执行后面程序*/ for (int i = 1; i <=60; i++) { System.out.println(i); /*让程序睡眠1秒钟 1秒=1000毫秒*/ try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } }
2.2.4 Thread构造方法底层原理——静态代理
只针对有参且参是Runnable类的构造方法:public Thread(Runnable target)
由于Thread和target顶层都是Runnable接口,所以Thread是使用了静态代理的方式代理参数target。
2.3 ② 实现Runnable接口
优势:
- 避免单继承的局限性
一个类继承了Thread类就不能继承其他的类
一个类实现了Runnable接口,还可以继续继承别的类,实现其他的接口 - 增强了程序的扩展性,降低程序的耦合度
使用Runnable接口把设置线程任务和开启线程相分离
实现类当中,重写run方法,设置线程任务
创建Thread类对象,调用 start方法,开启新线程
如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享
2.3.1 实现步骤
1.创建一个RunnableImpl类实现Runnable接口
2.重写Runnable接口中的run方法,设置线程任务
3.创建Runnable接口的实现类RunnableImpl的对象t
4.创建Thread类对象,构造方法中传递Runnable接口的实现类RunnableImpl的对象t
5.调用Thread类中的start方法,开启新的线程,执行run方法
示例:
//实现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); } } } public static void main(String[] args) { //3.创建Runnable接口的实现类对象 RunnableImpl r = new RunnableImpl(); //4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象 Thread t = new Thread(r);//打印20次i //5.调用Thread类中的start方法,开启新的线程,执行run方法 t.start(); //【一般16-18行简写为:new Thread(r,"线程名").start();】 //主线程开启新线程之后继续执行的代码 for (int i = 0; i <20; i++) { System.out.println(Thread.currentThread().getName()+"===>"+i); } }
2.3.2 构造方法
- Thread(Runnable target) 分配新的 Thread对象
- Thread(Runnable target, String name) 分配新的 Thread对象【推荐该方法,因为可以自定义线程名】
2.4 ③ 实现Callable接口
十分重要,但本篇只简单介绍了一下,请去看下一篇JUC
2.4.1 实现步骤
- 实现Callable接口,需要返回值类型
- 重写call方法,需要抛出异常
- 创建目标对象
- 创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1); //1为开辟的线程池中线程的数量
- 提交执行Future result1 = ser.submit(t1); //线程
- 获取结果:boolean r1 = resut1.get() //指定线程的返回结果
- 关闭服务:ser.shutdownNow()
代码:
public class MyCallableImpl implements Callable<Boolean> { @Override public Boolean call() throws Exception { PictureCatch t = new PictureCatch(); t.test(url,name); System.out.println("下载了文件名:"+name); return true; } String url; //网址 String name; //保存的文件名 MyCallableImpl(String url,String name){ this.url=url; this.name=name; } public static v