栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

java基础篇--线程

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

java基础篇--线程

文章目录

1、多线程的创建

1.1 创建方式1--Thread类1.2 创建方式2--实现Runnable接口1.3 创建方式3--实现Callable、FutureTask接口 2、线程安全问题

2.1 同步代码块2.2 同步方法2.3 Lock锁2.4 线程通信 3、线程池

1、多线程的创建

线程是一个程序内部的一条执行路径。

java线程的6种状态:

线程状态描述
NEW新建线程刚被创建,但未启动
Runnable可运行县城已经调用了start()等待CPU调度
Block阻塞线程在执行的时候未竞争到锁对象,进入阻塞状态
Waiting无限等待进入等待状态,需另一个线程唤醒
Timed Waiting计时等待调用含有超时参数的方法(sleep)进入计时等待状态
Teminated被终止正常运行结束或因没有捕获的异常终止了run方法而死亡

多线程是指从软硬上多条执行流程的技术。

1.1 创建方式1–Thread类
    继承Thread类实现重写run方法创建new新线程对象调用start方法启动线程(执行的还是run方法)

为什么不直接调用了run方法,而是调用start启动线程?

答:直接调用run方法会当成普通方法执行,此时相当于还是单线程执行;只有调用start方法才是启动一个新的线程执行,相当于告诉操作系统,这里有一个新的线程要分配CPU。

同时编程时不能把主线程任务放在子线程之前,这样主线程就一直是先跑完的,相当于一个单线程。

优点:编码简单;

缺点:线程类已经继承Thread类,无法再继承其他类,不利用扩展。

1.2 创建方式2–实现Runnable接口
    定义一个线程任务类实现Runnable接口,重写run()方法创建对象把对象交给Thread处理调用线程对象的start()方法启动线程
//第一种写法
class MyRunnable implements Runnable{
	@Override
	public void run(){
		//线程运行内容
	}
	
}
public class Thread{
	public static void main(String[] args){
		Runnable target = new MyRunnable();//创建任务类对象
		new Thread(target).start();//启动线程
		
		//第二种写法
		new Thread(()->{
			//线程运行内容
		})
	}
	
}

优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强。

缺点:编程多一层对象包装,如果线程有执行结果是不可以直接返回的。

1.3 创建方式3–实现Callable、FutureTask接口

前两种创建方式都存在一个问题:重写的run方法均不能直接返回结果,不适合需要返回线程执行结果的业务场景。

    得到任务对象

    定义类实现Callable接口,重写call方法,封装要做的事情;

    用FutureTask把Callable对象封装成线程任务对象

    把线程任务对象交给Thread处理

    调用Thread的start()方法启动线程,执行任务

    线程执行完毕,通过FutureTask的get方法获取任务执行结果

//应该申请线程任务执行完毕后的结果数据类型
class MyCallable implements Callable{
	@Override
	public void call() throws Exception{
		//线程运行内容
		return "线程执行结果";
	}
	
}
public class Thread{
	public static void main(String[] args){
		Callable call = new MyCallable();//创建任务类对象
		//把任务对象交给FutureTask对象 继承了Runnable接口
		FutureTask f = new FutureTask<>(call);
		
		new Thread(f1).start();//启动线程
	}
	
}

优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强;可以在线程执行完毕后去获取线程执行结果。

缺点:编码复杂。

2、线程安全问题

多个线程同时操作同一个共享资源的时候可能会出现业务安全问题,称为线程安全问题。

发生的原因:

    存在多线程并发同时访问共享资源存在修改共享资源

为了解决线程安全问题,线程同步

核心思想: 加锁,把共享资源加上锁,每次只能一个线程进入,访问完毕以后解锁,其他线程才能进来。

2.1 同步代码块

作用:把出线程安全问题的核心代码上锁

原理:每次只能一个线程进入,执行完毕后自动解锁,其他线程才可以进来执行。

synchronized(同步锁对象){
	操作共享资源的代码
}

锁对象的规范要求:

规范上:建议用共享资源作为锁对象

    对于实例方法建议使用this作为锁对象对于静态方法建议使用类名.class作为锁对象
2.2 同步方法

作用:把出现线程安全问题的核心方法上锁

原理:每次只能一个线程进入,执行完毕后自动解锁,其他线程才可以进来执行

格式:

修饰符 synchronized 返回值类型 方法名称(形参列表){
	操作共享资源的代码
}

同步方法底层原理:

    底层也是有隐式锁对象的,只是锁的范围是整个方法代码对于实例方法,默认用this作为锁对象,但代码要高度面向对象对于静态方法,默认使用类名.class作为锁对象

同步代码块好还是同步方法好?

同步代码块锁的范围更小,同步方法锁的范围更大(实际开发还是同步方法使用更多,使用方便)。

2.3 Lock锁

Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作。

获得Lock锁的实现类对象 -> ReentrantLock()

Lock的基本API :获得锁 lock();解锁unlock()

使用try-finally结构,在finally代码块进行解锁,保证代码块能够得到解锁。

2.4 线程通信

所谓线程通信就是线程间相互发送数据。

常见形式:通过共享一个数据的方式实现,根据共享数据的情况决定自己该怎么做,以及通知其他线程怎么做。

线程通信的前提:线程通信通常是在多个线程操作同一个共享资源的时候需要进行通信,且要保证线程安全。

三个常见方法:(使用当前同步锁对象进行调用)

方法名称说明
void wait()当前线程等待,直到另一个线程调用notify()或notifyAll()唤醒自己
void notify()唤醒正在等待对象监视器的单个线程
void notifyAll()唤醒正在等待对象监视器的所有线程
3、线程池

线程池是一个可以复用线程的技术。

不使用线程池的问题:

如果用户每发出一个请求,后台就创建一个新线程来处理,下次新任务来了又要创建新线程,而创建新线程的开销很大,这样会严重影响系统的性能。

线程池工作原理

线程池中的核心线程在处理请求,待完成的用户的请求位于任务队列,一旦处理的任务结束,就继续任务队列中的未完成的用户请求;核心线程都在处理请求且任务队列已满,会创建临时线程来帮忙处理。若临时线程数量达到规定上限、各线程皆忙且任务队列已满,则会根据设置的处理方式处理新发出的用户请求。(如拒绝)

如何得到线程池对象

方式一:使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象方式二:使用Executor(线程池的工具类)调用方法返回不同特点的线程池对象

核心线程即是线程池固有的线程,可以理解为正式员工;最大线程池包含核心线程+临时线程,临时线程可以理解为临时员工,当人手不够时会招收临时员工;任务队列即是店外等待的客户的队列;线程工厂可以理解为HR,招收临时员工,即根据情况创建临时线程。

线程池常见面试题:

临时线程什么时候创建

新任务提交时,发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程时,此时才会创建临时线程。

什么时候会开始拒绝任务

核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始拒绝。

Executors工具类基于线程池ExecutorService的实现类ThreadPoolExecutor 实现的线程池对象,可直接调用,但不适合做大型互联网场景的线程池方案,可能会造成资源耗尽的风险。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/727890.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号