Java多线程,博客学习笔记


参考 https://mp.weixin.qq.com/s/5bu547-EsgX0BMQwh68XKQ

进程与线程

进程

进程:程序的一次运行。

每个进程之间是独立的。操作系统在分配资源(例如:内存)时,是以进程为单位。

两个进程之间进行切换,通信(交换数据)等操作时,成本比较高。

并发:多个进程同时运行
高并发,多个任务处理功能,但是不要求同时进行。

线程

线程:进程中的其中一条执行路径。

同一个进程中的多个线程之间是可以共享部分内存(方法区、堆),每个线程的有些内存又是独立(虚拟机栈、本地方法栈、程序计数器)。

因为线程之间可能使用共享内存,那么在数据交换成本上就比较低。而且线程之间的切换,对于CPU和操作系统来说,成本比较低。

线程是CPU调度的最小单位。

并行:多个线程同时运行。
并行,要求同时进行。针对CPU多核,甚至多个CPU,同时运行多个线程任务。

多线程

CPU:一个CPU同一个时间只能够运行一个线程的任务。
如何实现多个线程同时运行的呢?
因为CPU是非常快,这个速度远远高于内存、硬盘、人的大脑反应的速度。
那么CPU会在多个线程之间,快速的切换,人是感觉不到。

多线程是指一个进程在执行过程中可以产生多个更小的程序单元,这些更小的单元称为线程,这些线程可以同时存在,同时运行,一个进程可能包含多个同时执行的线程。进程与线程的区别如图所示: (图在https://mp.weixin.qq.com/s/5bu547-EsgX0BMQwh68XKQ)

Java 中线程实现方式

Java的程序入口是main,其实也是main线程,主线程。线程是进程的其中一条执行路径,即一个进程至少有一个线程。那么main线程就是Java程序进程的第一个线程了。

在 Java 中实现多线程有两种手段,一种是继承 Thread 类,另一种就是实现 Runnable 接口。

实现Runnable接口

实现接口,并且重写run方法。在main方法中,实例化这个实现接口的对象,再实例化Thread对象,之后对这些Thread对象调用start方法。这个start()方法只有Thread类中才有,说明我们要借用Thread类的对象。

继承Thread类

继承Thread类,并且重写run方法。在main方法中,实例化继承Thread类的对象,之后对这些对象调用start方法。

从程序可以看出,现在的两个线程对象是交错运行的,哪个线程对象抢到了 CPU 资源,哪个线程就可以运行,所以程序每次的运行结果肯定是不一样的,在线程启动虽然调用的是 start() 方法,但实际上调用的却是 run() 方法定义的主体。

实际上 Thread 类和 Runnable 接口之间在使用上也是有区别的,如果一个类继承 Thread类,则不适合于多个线程共享资源,而实现了 Runnable 接口,就可以方便的实现资源的共享。(我的理解,因为继承接口的类,实例化一次便是一个任务,而这个任务被分配给多个实例化的Thread对象,才实现了多线程。如果直接继承Thread类,每实例化一个继承Thread类的对象,同时也开启了一个新的实例,但是如果先实例化一个对象,再分配给多个实例化的Thread对象,两者是不是就相同了)
见 https://blog.csdn.net/weixin_45928582/article/details/106941780

线程的状态变化(生命周期)

任何线程一般具有5种状态,即创建,就绪,运行,阻塞,死亡。

创建

在程序中用构造方法创建了一个线程对象后,新的线程对象便处于新建状态,此时它已经有了相应的内存空间和其他资源,但还处于不可运行状态。新建一个线程对象可采用Thread 类的构造方法来实现,例如 “Thread thread=new Thread()”。

就绪

新建线程对象后,调用该线程的 start() 方法就可以启动线程。当线程启动时,线程进入就绪状态。此时,线程将进入线程队列排队,等待 CPU 服务,这表明它已经具备了运行条件。

运行

当就绪状态被调用并获得处理器资源时,线程就进入了运行状态。此时,自动调用该线程对象的 run() 方法。run() 方法定义该线程的操作和功能。

阻塞

一个正在执行的线程在某些特殊情况下,如被人为挂起或需要执行耗时的输入/输出操作,会让 CPU 暂时中止自己的执行,进入阻塞状态。在可执行状态下,如果调用sleep(),wait(),join()(调用join方法的线程加塞,阻塞其他线程)(suspend()已过时) 等方法,线程都将进入阻塞状态,发生阻塞时线程不能进入排队队列,只有当引起阻塞的原因被消除后,线程才可以转入就绪状态。

从阻塞回到就绪状态:

  • 1 sleep()时间到,sleep()被打断interrupt()
  • 2 notify()
  • 3 加塞的线程结束
  • 4 占用锁的线程释放锁
  • 5 resume()已过时

死亡

线程调用 stop() 方法时或 run() 方法执行结束后,即处于死亡状态。处于死亡状态的线程不具有继续运行的能力。

在这里插入图片描述

在此提出一个问题,Java 程序每次运行至少启动几个线程?
(答案在https://mp.weixin.qq.com/s/5bu547-EsgX0BMQwh68XKQ)

线程的操作方法

线程的强制运行,join

在线程操作中,可以使用 join() 方法让一个线程强制运行,线程强制运行期间,其他线程无法运行,必须等待此线程完成之后才可以继续执行。

线程的休眠,sleep

中断线程,interrupt

后台线程,setDaemon

在线程类 MyThread 中,尽管 run() 方法中是死循环的方式,但是程序依然可以执行完,因为方法中死循环的线程操作已经设置成后台运行。

线程的优先级,setPriority

从程序的运行结果中可以观察到,线程将根据其优先级的大小来决定哪个线程会先运行,但是需要注意并非优先级越高就一定会先执行,哪个线程先执行将由 CPU 的调度决定。

线程的礼让,yield

将一个线程的操作暂时让给其他线程执行
程序最后的输出不一定和作者的输出一样,执行yield方法的时候会有很多种情况。

同步以及死锁(线程安全问题)

多个线程要操作同一个资源时就有可能出现资源同步问题。

当多个线程使用“共享数据”时,就会有线程安全问题。
当一个线程修改了“共享数据”,就会影响其他线程。

如何解决?:加锁
形式一:同步代码块
形式二:同步方法

同步代码块,synchronized(同步对象)

这里的同步对象,又称锁对象
锁对象,又称为监视器对象,同一时刻,某一段代码,只允许一个线程运行,这个锁就记录谁现在在运行,其他线程进不来。

锁对象的选择:

  • 1 可以是任意类型的对象
  • 2 这几个线程要使用同一个锁对象
    有时候使用synchronized (this) ,是不合适的,因为这个this,对于多个个线程来说不一定是同一个,详见https://blog.csdn.net/qq_40473204/article/details/107575951

同步方法,synchronized 方法返回值 方法名称

死锁,

所谓死锁,就是两个线程都在等待对方先完成,造成程序的停滞,一般程序的死锁都是在程序运行时出现的。

博客代码思路:
在死锁类里面创建static的zs和ls对象,设置flag表示锁。
实例化两个死锁类表示对于zs,ls的控制。一个flag为false,一个为true。
run方法中,如果flag为true,执行zs同步代码块,这里面还有一个需要ls的同步代码块,如果flag为false,则为执行ls同步代码块,这里面还有一个需要zs的同步代码块。

涉及到生产者消费者问题,详见
https://blog.csdn.net/qq_40473204/article/details/107575951

volatile关键字解析

https://www.cnblogs.com/dolphin0520/p/3920373.html
疑问:double check为什么要加volatile,是为了避免无序写入吗?

猜你喜欢

转载自blog.csdn.net/qq_40473204/article/details/107904059
今日推荐