1、线程基础、线程之间的共享和协作

基础概念

什么是进程和线程

进程是程序运行资源分配的最小单位
桌面双击启动一个程序,该程序运行了一个进程,该进程里有多个线程
这些线程共享该进程中的全部系统资源。资源包括:CPU、内存空间、 磁盘 IO 等。而进程和进程之间是相互独立的。
进程分为系统进程和用户进程。
线程是 CPU 调度的最小单位,必须依赖于进程而存在
线程本身拥有很少的资源,大部分都是共享进程所拥有的全部资源。

CPU 核心数和线程数的关系

多核心:也指单芯片多处理器。多个 CPU 同时并行地运行程序。
多线程: 让同一个处理器上的多个线程同步执行并共享处理器的执行资源(并发)。

image.png

CPU 时间片轮转机制(RR调度)

image.png

并行和并发

并发:指应用能够交替执行不同的任务。线程>核数的去执行
并行:指应用能够同时执行不同的任务。双核cpu,两线程分别去一个核执行

高并发编程的意义、好处和注意事项

(1)充分利用 CPU 的资源
(2)加快响应用户的时间
(3)可以使你的代码模块化,异步化,简单化
例如我们实现电商系统,下订单和给用户发送短信、邮件就可以进行拆分,
将给用户发送短信、邮件这两个步骤独立为单独的模块,并交给其他线程去执行。
这样既增加了异步的操作,提升了系统性能,又使程序模块化,清晰化和简单化。

多线程程序需要注意事项

(1)线程之间的安全性
在同一个进程里面的多线程是资源共享的,也就是都可以访问同一个内存地址当中的一个变量。

(2)线程之间的死锁
为了解决线程之间的安全性引入了 Java 的锁机制。而锁用的不好就容易死锁,因为不同的线程都在等待那些根本不可能被释放的锁,从而导致所有的工作都无法完成。

(3)线程太多了会将服务器资源耗尽形成死机当机
线程数太多有可能造成系统创建大量线程而导致消耗完系统内存以及 CPU的“过渡切换”,造成系统的死机。
解决方法:示例数据库连接池。
只要线程需要使用一个数据库连接,它就从池中取出一个,使用以后再将它返回池中。资源池也称为资源库。

Java 里的线程

精华就是这个图!!!!!
image.png

Java 程序天生就是多线程的

啥也不干就启动一个main(),java就会启动好多线程。main()主线程、和众多守护线程( 分发处理发送给 JVM 信号的线程、GC线程、内存 dump线程等)

启动

启动线程的方式有两种:
1、继承Thread, X extends Thread;然后 X.start
2、实现Runnable,X implements Runnable;然后交给 Thread 运行

Thread 和 Runnable 的区别:
Thread 才是 Java 里对线程的唯一抽象
Runnable 只是对任务(业务逻辑)的抽象
Thread 可以接受任意一个 Runnable 的实例并执行。

中止

自然终止
run 执行完成,或者抛出一个未处理的异常导致线程提前结束。

stop
暂停suspend()、恢复resume()和停止stop()。这三方法不建议使用。
原因:这些方法是强制执行的。suspend、stop的时候。线程占着资源(锁和其他资源等)直接睡眠,停止了,这样会导致死锁、内存泄露等等问题。

中断 interrupt()
安全的中止则是其他线程通过调用某个线程 A 的 interrupt()方法对其进行中断操作。
中断 interrupt()是一个中断标志位,只是通知一下线程应该中断了,但是不会对线程有任何影响,然后根据代码判断控制是否该结束线程释放资源。
因为只是一个标志,所以当调用interrupt()方法时,不会影响改变线程的生命周期,线程可以完全不理会继续执行,或者结束。
interrupt() 发起中断请求,将
boolean isInterrupted() 判断是否被中断
boolean Thread.interrupted() 判断当前线程是否被中断,不过 Thread.interrupted()会同时将中断标识位改写为 false

image.png

如果一个线程处于了阻塞状态(如线程调用了 thread.sleep、thread.join、
thread.wait 等),则在线程在检查中断标示时如果发现中断标示为 true,则会在
这些阻塞方法调用处抛出 InterruptedException 异常,并且在抛出异常后会立即
将线程的中断标示位清除,即重新设置为 false。

image.png

image.png

不建议自定义一个取消标志位来中止线程的运行。因为 run 方法里有阻塞调
用时会无法很快检测到取消标志,线程必须从阻塞调用返回后,才会检查这个取
消标志。这种情况下,使用中断会更好,因为,
一、一般的阻塞方法,如 sleep 等本身就支持中断的检查,
二、检查中断位的状态和检查取消标志位没什么区别,用中断位的状态还可
以避免声明取消标志位,减少资源的消耗。
注意:处于死锁状态的线程无法被中断!!!

重要方法

run()和 start()
Thread类是Java里对线程概念的抽象。
通过new Thread()其实只是 new 出一个 Thread 的实例,还没有操作系统中真正的线程挂起钩来。
只有执行了 start()方法后,才实现了真正意义上的启动线程。
image.png
image.png

start()方法不能重复调用,如果重复调用会抛出异常。

image.png

run() 方法是业务逻辑实现的地方,本质上和任意一个类的任意一个成员方
法并没有任何区别,可以重复执行,也可以被单独调用。
run()=普通方法

yield()
使当前线程让出 CPU 占有权,但让出的时间是不可设定的。也不会释放锁资源。让完之后该线程与其他线程一起竞争cpu使用权。
注意:并不是每个线程都需要这个锁的,而且执行 yield( )的线
程不一定就会持有锁,我们完全可以在释放锁后再调用 yield 方法。
所有执行 yield()的线程有可能在进入到就绪状态后会被操作系统再次选中
马上又被执行。

join()
把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行。
比如在线程 B 中调用了线程 A 的 Join()方法,直到线程 A 执行完毕后,才会继续
执行线程 B。(此处为常见面试考点)

线程的优先级
setPriority()设置优先级,1~10,默认5。但是不同系统有不同的优先级,所以不可靠。聊胜于无

守护线程
线程分为用户线程守护线程。自己启动的叫用户线程,后台为用户线程服务的是守护线程。
Daemon(守护)线程是一种支持型线程,因为它主要被用作程序中后台调度以及支持性工作。
当用户线程全死亡时,他的守护线程也会死亡。
所以try{}finally{}代码块,当线程为守护线程时,他的用户线程死亡,他被清除,不一定会执行finally{}代码块,所以这种情况下是不安全的。
可以通过调用 Thread.setDaemon(true)将线程设置为 Daemon 线程。

wait()和 notify/notifyAll()
wait、notify是Object里的方法。线程必须要获得该对象的对象级别锁,即只能在同步方法或同步块中调用 wait()方法、notify()系列方法— 所以是通过某对象(Obj)来让线程等待,更新的
必须放在同步方法里(synchronized)
wait()调用后会释放锁资源!!!—因为别人要更新不放就死锁了
notify()调用后不会释放锁资源—所以一般notify()放在代码的最后一行,全部run()都跑完了,才释放锁
notify()随机更新一个用到该对象的线程,notifyAll()通知所有线程
wait(long millis) —等待超时就抛一个异常

是指一个线程 A 调用了对象 O 的 wait()方法进入等待状态,而另一个线程 B
调用了对象 O 的 notify()或者 notifyAll()方法,线程 A 收到通知后从对象 O 的 wait()
方法返回,进而执行后续操作。
上述两个线程通过对象 O 来完成交互,而对象
上的 wait()和 notify/notifyAll()的关系就如同开关信号一样,用来完成等待方和通
知方之间的交互工作。

标准范式
image.png

生产/消费模式案例

image.png
image.png
image.png
image.png
image.png

连接池(生产/消费)模式的实现!

a.png

a (2).png

aa.png

线程生命周期总结!!!

image.png

线程间的共享和协作

synchronized 内置锁
Java 支持多个线程同时访问一个对象或者对象的成员变量,关键字
synchronized 可以修饰方法或者以同步块的形式来进行使用,它主要确保多个线
程在同一个时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量
访问的可见性和排他性(原子性),又称为内置锁机制

synchronized叫做对象锁,所以它锁的是一个对象,可以用在方法上或者代码块中。
但是也能用在static类(静态类) 上,叫他 类锁实质锁的是静态类的Class对象。 所以还是对象锁。

错误使用
比如锁Integer等对象时,失效。因为Integer等对象,实现i+1的时候,是将Integer->转成int,然后 int+1在new 一个Integer赋值进去的,所以是新的对象。

image.png

volatile,最轻量的同步机制
volatile关键词试用于一写多读。它保证了可见性不保证原子性
volatile关键词的作用是,一个线程里修改,能通知其他线程这个变量改变了。

ThreadLocal
ThreadLocal线程局部变量,在多线程试用的时候,每个线程都有自己的这个变量。为每个线程提供变量副本/线程隔离
ThreadLocal 为每个线程都提供了变量的副本,使得每个线程在某一时间訪问到的并非同一个对象,这样就隔离了多个线程对数据的数据共享。
ThreadLocal的使用,get、set、remove(删除并回收资源)

使用案例:
Spring 的事务就借助了 ThreadLocal 类。Spring 会从数据库连接池中获得一个
connection,然会把 connection 放进 ThreadLocal 中,也就和线程绑定了,事务需
要提交或者回滚,只要从 ThreadLocal 中拿到 connection 进行操作。

image.png

ThreadLocal的get方法
image.png
ThreadLocalMap方法
image.png

1.使用不当引发内存泄漏

image.png
看上图和上上图(ThreadLocalMap方法)得出
引用 ThreadLocal 的对象被回收了,但是 ThreadLocalMap
还持有 ThreadLocal 的引用,如果没有手动删除,ThreadLocal 的对象实例不会
被回收,导致 Entry 内存泄漏。
(remove手动删除,set、get方法可能会删除一些key为null的map)
由于 ThreadLocalMap 的生命周期跟
Thread 一样长,如果没有手动删除对应 key 就会导致内存泄漏。而不是因为弱引用,强引用必会发生内存泄露。所以取舍之间设计师选择了弱引用

使用线程池+ ThreadLocal 时要小心,因为这种情况下,线程是一直在不断的重复运行的,从而也就造成了 value 可能造成累积的情况。

2.错误使用 ThreadLocal 导致线程不安全
当设置为static静态时,全局共用一份变量,没法建变量副本。所以会导致ThreadLocal失效。大家都改的同一个变量导致线程不安全.

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享