2021-09-12

本文详细介绍了Java多线程的基本概念、线程同步与异步的区别、调度机制、优先级设置、多线程创建方式、线程池、通信方法、生命周期管理以及线程安全问题的解决方案。涵盖了线程API、线程池工具和关键术语,是理解Java并发编程的实用指南。
摘要由CSDN通过智能技术生成

**

Java 多线程——学习总结

首先区分以下关联的概念:
程序:是为完成特定任务,用某种语言编写的一组指令的集合,即指一段静态的代码,静态对象。
进程(process):是程序的一次执行过程,或是正在运行的一个程序,是一个动态的过程,有它自身的产生,存在和消亡的过程。-------生命周期
每个进程都有自己的地址空间,资源如,内存,I/O,CPU,同一个进程里的 线程共享本进程里的地址空间,享有独立性。
它是资源分配的基本单位,运行调度的基本单位,系统中并发执行的单位。因此,计算机可以同时运行多个进程。
在这里插入图片描述

线程(thread):进程可进一步细化为线程,是一个程序内部的不同执行路径。
每个进程中至少包含一个线程,而这些线程都在共享进程的资源空间等,当线程发生变化的时候只会引起CPU执行的过程发生变化,不会改变进程所拥有的资源。在进程中的多个线程也可并发。
它是进程中执行运算的最小单位,亦是执行处理机调度的基本单位。
在这里插入图片描述
同步:排队执行 , 效率低但是安全。同步就是一直等,等到你完成给出回复为止。
异步:同时执行 , 效率高但是数据不安全。异步就是先将所有声明的事情干到底,然后等回复。
一句话:同步就是我通知你,你必须给我反馈,异步就是我忙着呢,通知到了,不等你了。
那么,针对线程来说就是:
线程同步:就是多个线程同时访问同一资源,必须等一个线程访问结束,才能访问其它资源,比较浪费时间,效率低
线程异步:访问资源时在空闲等待时可以同时访问其他资源,实现多线程机制

并发:指两个或多个事件在同一个时间段内发生。一个处理器同时(时间段内)处理多个任务。比如一个人同时吃三个馒头。
并行:指两个或多个事件在同一时刻发生(同时发生)。多个处理器或者是多核的处理器同时处理多个不同的任务。比如三个人同时吃三个馒头。
二者区别:并发的关键是你有处理多个任务的能力,不一定要同时。
并行的关键是你有同时处理多个任务的能力。
并发是轮流处理多个任务,并行是同时处理多个任务。

总之:
单个CPU一次只能运行一个任务。任一时刻,CPU总是运行一个进程,其他进程处于非运行状态。
一个进程的内存空间是共享的,每个线程都可以使用这些共享内存。
有时候,一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。需要使用"互斥锁"(Mutual exclusion,缩写 Mutex),防止多个线程同时读写此块内存区域。
同样,某些内存区域,只能供给固定数目的线程使用。则使用"信号量"(Semaphore)的方式,用来保证多个线程不会互相冲突。
不难看出,Mutex是Semaphore的一种特殊情况(n=1时)。
因此,系统程序对他们的使用可归结为三点:
(1)以多进程形式,允许多个任务同时运行;
(2)以多线程形式,允许单个任务分成不同的部分运行;
(3)提供协调机制,一方面防止进程之间和线程之间产生冲突,另一方面允许进程之间和线程之间共享资源。

线程的相关API

//获取当前线程的名字
Thread.currentThread().getName()

1.start(): 1.启动当前线程2.调用线程中的run方法
2.run(): 通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
3.currentThread():静态方法,返回执行当前代码的线程
4.getName():获取当前线程的名字
5.setName():设置当前线程的名字
6.yield():主动释放当前线程的执行权
7.join():在线程中插入执行另一个线程,该线程被阻塞,直到插入执行的线程完全执行完毕以后,该线程才继续执行下去
8.stop():过时方法。当执行此方法时,强制结束当前线程。
9.sleep(long millitime):线程休眠一段时间
10.isAlive():判断当前线程是否存活

判断是否是多线程

public class Sample{
		public void method1(String str){
			System.out.println(str);
		}
	public void method2(String str){
		method1(str);
	}	
	public static void main(String[] args){
		Sample s = new Sample();
		s.method2("hello");
	}
}

以上其实质就是主线程在执行方法2和方法1这一条路径,所以就是一个线程

线程的调度

调度策略:
时间片:线程的调度采用时间片轮转的方式,即分时调度
抢占式:高优先级的线程抢占CPU,若级别相同则会随机选择一个(线程随机性),
Java的调度方法:
1.对于同优先级的线程组成先进先出队列(先到先服务),使用时间片策略
2.对高优先级,使用优先调度的抢占式策略

Java使用的为抢占式调度。CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核芯而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是 在同一时刻运行。 其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,因为CPU的使用率更高。

线程的优先级
等级:
MAX_PRIORITY:10
MIN_PRIORITY:1
NORM_PRIORITY:5

方法:
getPriority():返回线程优先级
setPriority(int newPriority):改变线程的优先级

注意!:高优先级的线程要抢占低优先级的线程的cpu的执行权。但是仅是从概率上来说的,高优先级的线程更有可能被执行。并不意味着只有高优先级的线程执行完以后,低优先级的线程才执行。

多线程的创建方式

方式1:继承于Thread类
1.创建一个集成于Thread类的子类 (通过ctrl+o(override)输入run查找run方法)
2.重写Thread类的run()方法
3.创建Thread子类的对象
4.通过此对象调用start()方法
start是开启线程,再调用方法(即默认开启一次线程,调用一次run方法,可以同时执行几件事

** 方式2:实现Runable接口方式**

1.创建一个实现了Runable接口的类
2.实现类去实现Runnable中的抽象方法:run()
3.创建实现类的对象
4.将此对象作为参数传递到Thread类中的构造器中,创建Thread类的对象
5.通过Thread类的对象调用start()

比较创建线程的两种方式:
开发中,优先选择实现Runable接口的方式
原因
1:实现的方式没有类的单继承性的局限性
2:实现的方式更适合用来处理多个线程有共享数据的情况
联系:Thread也是实现自Runable,两种方式都需要重写run()方法,将线程要执行的逻辑声明在run中

方式3:实现callable接口方式
与使用runnable方式相比,callable功能更强大些:
runnable重写的run方法不如callaalbe的call方法强大,call方法可以有返回值
方法可以抛出异常
支持泛型的返回值
比runable多一个FutureTask类,用来接收call方法的返回值。

  • 适用于需要从线程中接收返回值的形式
    callable实现新建线程的步骤:
  • 1.创建一个实现callable的实现类
  • 2.实现call方法,将此线程需要执行的操作声明在call()中
  • 3.创建callable实现类的对象
  • 4.将callable接口实现类的对象作为传递到FutureTask的构造器中,创建FutureTask的对象
  • 5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start方法启动(通过FutureTask的对象调用方法get获取线程中的call的返回值)

方式4: 使用线程池的方式:
背景:经常创建和销毁,使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路:提前创建好多个线程,放入线程池之,使用时直接获取,使用完放回池中。可以避免频繁创建销毁,实现重复利用。类似生活中的公共交通工具。(数据库连接池)
好处:提高响应速度(减少了创建新线程的时间)
降低资源消耗(重复利用线程池中线程,不需要每次都创建)
便于线程管理
corePoolSize:核心池的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止
。。。。。。

JDK 5.0 起提供了线程池相关API:
ExecutorService 和 Executors
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor.
void execute(Runnable coommand):执行任务/命令,没有返回值,一般用来执行Runnable
Futuresubmit(Callable task):执行任务,有返回值,一般又来执行Callable
void shutdown():关闭连接池。

Executors 工具类,线程池的工具类,用于创建并返回不同类型的线程池
Executors.newCachedThreadPool() 创建一个可根据需要创建新线程的线程池
Executors.newFixedThreadPool(n) 创建一个可重用固定线程数的线程池
Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
Executors.newScheduledThreadPool(n) 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。

线程通信方法:

wait()/ notify()/ notifayAll():此三个方法定义在Object类中的,因为这三个方法需要用到锁,而锁是任意对象都能充当的,所以这三个方法定义在Object类中。

由于wait,notify,以及notifyAll都涉及到与锁相关的操作
wait(在进入锁住的区域以后阻塞等待,释放锁让别的线程先进来操作)---- Obj.wait 进入Obj这个锁住的区域的线程把锁交出来原地等待通知
notify(由于有很多锁住的区域,所以需要将区域用锁来标识,也涉及到锁) ----- Obj.notify 新线程进入Obj这个区域进行操作并唤醒wait的线程

有点类似于我要拉粑粑,我先进了厕所关了门,但是发现厕所有牌子写着不能用,于是我把厕所锁给了别人,别人进来拉粑粑还是修厕所不得而知,直到有人通知我厕所好了我再接着用。

所以wait,notify需要使用在有锁的地方,也就是需要用synchronize关键字来标识的区域,即使用在同步代码块或者同步方法中,且为了保证wait和notify的区域是同一个锁住的区域,需要用锁来标识,也就是锁要相同的对象来充当

sleep和wait的异同
相同点:一旦执行方法以后,都会使得当前的进程进入阻塞状态
不同点:
1.两个方法声明的位置不同,Thread类中声明sleep,Object类中声明wait。
2.调用的要求不同,sleep可以在任何需要的场景下调用,wait必须使用在同步代码块或者同步方法中
3.关于是否释放同步监视器,如果两个方法都使用在同步代码块或同步方法中,sleep不会释放,wait会释放

线程生命周期
在这里插入图片描述

线程的同步:

在同步代码块中,只能存在一个线程。
线程的安全问题:
什么是线程安全问题呢?
线程安全问题是指,多个线程对同一个共享数据进行操作时,线程没来得及更新共享数据,从而导致另外线程没得到最新的数据,从而产生线程安全问题。
方式一:同步代码块
使用同步监视器(锁)
Synchronized(同步监视器){
//需要被同步的代码
}
说明:

操作共享数据的代码(所有线程共享的数据的操作的代码)(视作卫生间区域(所有人共享的厕所)),即为需要共享的代码(同步代码块,在同步代码块中,相当于是一个单线程,效率低)
共享数据:多个线程共同操作的数据,比如公共厕所就类比共享数据
同步监视器(俗称:锁):任何一个的对象都可以充当锁。(但是为了可读性一般设置英文成lock)当锁住以后只能有一个线程能进去(要求:多个线程必须要共用同一把锁,比如火车上的厕所,同一个标志表示有人)
Runable天生共享锁,而Thread中需要用static对象或者this关键字或者当前类(window。class)来充当唯一锁

方式二:同步方法
使用同步方法,对方法进行synchronized关键字修饰
将同步代码块提取出来成为一个方法,用synchronized关键字修饰此方法。
对于runnable接口实现多线程,只需要将同步方法用synchronized修饰
而对于继承自Thread方式,需要将同步方法用static和synchronized修饰,因为对象不唯一(锁不唯一)

总结:1.同步方法仍然涉及到同步监视器,只是不需要我们显示的声明。
2.非静态的同步方法,同步监视器是this
静态的同步方法,同步监视器是当前类本身。继承自Thread。class

总结:Synchronized与lock的异同?
相同:二者都可以解决线程安全问题
不同:synchronized机制在执行完相应的代码逻辑以后,自动的释放同步监视器
lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())(同时以为着lock的方式更为灵活)

优先使用顺序:
LOCK-》同步代码块-》同步方法

判断线程是否有安全问题,以及如何解决:
1.先判断是否多线程
2.再判断是否有共享数据
3.是否并发的对共享数据进行操作
4.选择上述三种方法解决线程安全问题

————————————————
版权声明:本文为转发 CSDN博主「weixin_44797490」的原创文章。
原文链接:https://blog.csdn.net/weixin_44797490/article/details/91006241

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值