2021.02.02 第一阶段 20

多线程

一、多线程相关的三组概念

(一)程序和进程
1、程序(program):一个固定的运行逻辑和数据的集合,是一个静态的状态,一般存储在硬盘中。
2、进程(process):一个正在运行的程序,是一个程序的一次运行,是一个动态的概念,一般存储在内存中。
例如:ctrl + shift + esc,打开任务管理器可以看到所有进程
(二)进程和线程
1、进程(process):一个正在执行的程序,有自己独立的资源分配,是一个独立的资源分配单位。资源指的是:例如Cpu、内存等
2、线程(thread):一条独立的执行路径。多线程,在执行某个程序的时候,该程序可以有多个子任务,每个线程都可以独立的完成其中一个任务。在各个子任务之间,没有什么依赖关系,可以单独执行。
3、进程和线程的关系:
进程是用于分配资源的单位
一个进程中,可以有多条线程;但是一个进程中,至少有一条线程
线程不会独立的分配资源,一个进程中的所有线程,共享同一个进程中的资源
(三)并行和并发
1、并行(parallel):多个任务(进程、线程)同时运行。在某一个确定的时刻,有多个任务正在同时执行。
条件:多CPU、多核心
2、并发(concurrent):多个任务(进程、线程)同时发起。在某一个确定的时候,只有一个任务在执行。
条件:单个CPU、单核心
(四)问题
1、CPU在多个任务间来回切换,效率是提高了,还是降低了?
答:对于整个系统而言,效率提升了,对于单个任务而言,效率降低了。因为计算机中,各个元件的运
行速率不同,CPU速率10-9秒,内存速率10-6秒,硬盘速率10-3,人类的反应速度100秒,当CPU
在执行一个任务的时候,这个任务需要其他的系统资源,CPU执行之后立马切换到下一个任务,上一个
任务的其他系统资源可能还在运行。等到CPU切换回来,这个任务的其他资源才刚刚执行完,CPU就又
可以继续执行下一个环节。CPU在多任务间来回切换,使得整个系统的效率都有提升。

二、多线程的实现方式

(一)概述
1、Java中Thread类型表示线程类型。在Java中,JVM允许多线程【并发】执行
2、常用的多线程实现方式有两种:
(1)继承方式:继承Thread类型
(2)实现方式:实现Runnable接口
(二)继承方式实现多线程
1、自定义一个类型,继承Thread类型,那么将来这个【自定义类型的对象,就是一个线程对象】,就表示一条线程。
2、实现步骤:
(1)定义一个类,继承Thread类,将这个类加入到线程的行列中
(2)重写**【Thread类中的】run**方法,在run方法中定义业务逻辑,此处定义的内容,就是线程将来要执行的任务
(3)创建自定义类型的对象,这个对象就表示【一个线程】
(4)调用start()方法启动线程
(三)实现方式实现多线程
1、实现Runnable接口:Runnable接口的实现类对象,表示一个具体的任务,将来创建一个线程对象只有,还要将实现类对象即这个任务对象添加到线程中,让线程执行。
2、实现步骤:
(1)自定义一个类型实现Runnable接口
(2)重写Runnable接口中的run方法
(3)创建自定义类型的对象,这个对象表示一个【任务对象】
(4)创建线程对象,将任务对象添加到线程中
(5)线程对象调用start()方法启动线程

public class Demo02_ImplWay {
	public static void main(String[] args) {
		//3.创建自定义类型的对象,【对象表示一个任务】
		MyTask mt = new MyTask();
		//4.创建线程对象,将任务添加到线程中,将来线程执行
		Thread t = new Thread(mt);
		//5.启动线程,线程执行任务
		t.start();
		for (int i = 1; i <= 1000; i++) {
		System.out.println(i + "=====main");
		}

	}
}
//1.自定义一个类,实现Runnable接口
class MyTask implements Runnable {
	//2.重写【Runnable接口中的run方法】
	@Override
	public void run() {
		//定义业务逻辑,将来线程就执行的是这里的业务逻辑
		for (int i = 1; i <= 1000; i++) {
			System.out.println(i + "=====MyTask");
		}
	}
}

(四)两种实现方式的比较

1、代码的复杂程度
(1)继承的方式比较简单
(2)实现的方式比较复杂
2、实现原理:
(1)继承的方式:调用start()方法启动线程,start()方法调用了start0()方法,start0()方法是native封装的,底层不是java代码实现。实际上start0()方法会直接调用run()方法,但是由于我们重写过run()方法,结合面向对象思想,会直接执行重写后的方法,执行的内容就是我们自己定义的业务逻辑。
(2)实现的方式:构造方法创建线程对象,其中构造方法将传入的Runnable接口的实现类对象,通过两个init方法,最终作为成员变量赋值给了当前创建的线程对象,线程对象就拥有了任务。线程对象调用start()方法启动线程,start()方法调用了start0()方法,start0()方法直接调用run()方法。此时,因为线程对象是Thread类型的对象,所以Thread类型中的run方法会执行:判断Runnable接口的对象是否为null,不为null就用实际传入的任务对象调用重写后的run方法,实际执行的就是我们重写后的run。
3、设计:Java中支持单继承,不支持多继承,支持多层继承
(1)继承的方式:如果自定义类型继承了Thread类,就无法继承其他类了,扩展性教差,耦合度较高
(2)实现的方式:Java中一个类在实现多个接口的同时,还能够再继承一个类,此时扩展性较好。并且,任务对象传入线程对象,将任务和线程拆分开,耦合度较低
4、灵活性:
(1)继承的方式:灵活性差,耦合度高,我们把业务逻辑定义在run方法中,将来自定义对象就表示线程,线程中的任务是不可变的
(2)实现的方式:我们向线程中提交什么任务,线程就执行什么任务,将任务和线程拆分开,耦合度较低,所以实现的方式更常用。
(五)匿名内部类对线程实现方式的简化
1、匿名内部类,在代码中实际上是一个对象
2、继承的方式:匿名内部类实现之后,匿名内部类代表的就是Thread的子类对象

//继承方式
Thread t = new Thread() {
	@Override
	public void run() {
	System.out.println("匿名内部类通过继承的方式实现多线程");
	}
};
t.start();

3、实现的方式:匿名内部类实现Runnable接口,是接口的一个实现类对选哪个,表示一个任务对象

//实现的方式
//表示一个任务对象
Runnable task = new Runnable() {
	@Override
	public void run() {
		System.out.println("匿名内部类通过实现的方式实现多线程");
	}
};
//创建线程对象
Thread t = new Thread(task);
t.start();

三、Thread类中常用的方法

(一)获取线程名称
1、getName() 获取线程名称
2、注意事项:
(1)线程的默认名称为:Thread-X,其中X表示从0开始线程的一个序号
(2)可以在创建线程之后调用该方法,也可以在线程的run方法中调用该方法
(3)Runnable接口的实现类中,没有getName()方法可以继承
(二)设置线程名称
1、**setName(String name)**为线程设置名称
2、Thread(String name) 创建线程时就为线程起名字
3、Thread(Runnable target, String name) 创建一个线程对象,为这个线程添加任务对象的同时为线程设置名称
(三)获取当前线程对象
1、static currentThread() 这个方法在哪个线程里被执行了,就能够获取到哪个线程对象
(四)线程休眠
1、static sleep(long millis) 该方法在哪个线程内被执行,就可以让哪个线程暂停执行,暂停的时长取决于参数中毫秒值的多少
2、值得注意的是:sleep方法会存在一个异常:InterruptedException中断异常,当线程休眠被打断的时候会出现此异常。如果是在重写的run方法中使用sleep方法,只能try…catch,因为被重写的run方法,没有声明异常,所以重写的方法也只能try…catch处理
(五)守护线程
1、守护线程(后台线程):能够保证其他线程正常运行的线程,守护线程可以为其他的线程运行提供良好的环境。
2、特点:
(1)任何一条线程创建出来之后,都是非守护线程,需要通过我们将其设定为守护线程
(2)守护线程死亡,非守护线程可以正常运行
(3)非守护线程死亡,守护线程就没有存在的意义了,片刻后也会消亡
3、方法:
(1)setDaemon(boolean on) 将线程设置为守护线程
(2)**isDaemon()**判断一个线程是否为守护线程,如果是就返回true
4、实际举例:垃圾回收线程就是守护线程,及时回收系统垃圾,释放资源,为了保障主线程正常运行。当主线程停止运行,那么垃圾回收线程片刻后也会停止运行。
(六)线程的优先级
1、通过某些方法,设定当前的线程优先级。优先级高的线程会在同一个执行周期内靠前执行,优先级低的线程会在同一个执行周期内靠后执行。
2、注意:优先级高的线程仅仅是在同一个执行周期内比较靠前执行,在靠前的时间执行的较多而已。优先级低的同理。
3、优先级:当我们创建一个线程的时候,默认的优先级都是5
(1)MAX_PRIORITY 线程的最高优先级:10
(2)MIN_PRIORITY 线程的最低优先级:1
(3)NORM_PRIORITY 线程的默认优先级:5
4、线程设定优先级的方法:setPriority(int newPriority)

四、多线程的线程安全问题

(一)问题描述
1、某段代码在执行的时候,操作数据还没有操作完毕,CPU就切换到其他的线程上,其他线程对当前数据做了修改,当CPU再切换回这条线程时,这条线程会接着上一次没有做完的操作继续执行,此时数据就有可能产生问题
2、产生原因:【没有保证操作数据代码执行的完整性、原子性】
3、希望:在代码操作的时候,对数据的操作要么不做,要么做完,在操作数据的过程中,不要让其他线程来操作数据产生干扰
(二)同步代码块
1、同步代码块:使用一种代码格式,达到让某段代码被A线程执行的时候,CPU不会切换到其他线程上去的目的。这样,就可以确保CPU在执行A线程的时候,数据不回被其他线程操作而产生干扰。

synchronized(同步锁对象) {
	需要保证操作完整性的代码
}
当前同步锁对象可以是任何一个对象
只要保证会互相干扰的线程上有同一个同步所对象,就能保证数据安全

3、原理:
当CPU想去执行同步代码块的时候,首先要获取同步锁对象,获取到之后,就可以执行同步代码块中的内容了;当CPU执行当前代码的时候,可以切换到其他线程上去。如果其他线程也需要获取相同的同步锁对象,而正在执行的线程没有释放同步锁对象,则其他线程获取不到同步锁,也就无法执行。必须要等到当前线程将代码执行完毕,结束同步代码块,释放同步锁对象,其他线程才能够再次获取同步锁对象,执行同步代码块的内容

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值