Java多线程|多线程基础梳理

目录

1 进程与线程,并行与并发

(1) 进程与线程

(2)并发与并行

2 创建线程的两种方式

(1)继承Thread类

(2)实现Runnable接口

(3)选用建议

3 线程生命周期的五种状态

4 线程基本操作

(1)线程间通信,等待/唤醒机制 wait()与notify()

(2) wait()与sleep()区别

(3) 停止线程

(4) 其他方法

5 一些关键字含义及用途

(1)Volatile 

(2)非原子的 64 位操作

(3)Final关键字


==========================【导读】[开始]========================== 

        工作中实践到了多线程与高并发应用,也踩了一些沉重的坑。
万丈高楼起于垒土,学习与总结+工作实践不可相离。分三部分总结这块知识。
知识体系详见第一张思维导图。本篇主题“多线程基础知识”。

==========================【导读】[结束]========================== 

1 进程与线程,并行与并发

(1) 进程与线程

(1)进程是系统进行资源分配和调度的一个独立单位,线程是进程的一个实体,
是 CPU 调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。
(2)线程自己基本不拥有系统资源,只拥有一些在运行中必不可少的资源(如程
序计数器,一组寄存器和栈等),线程可与同属于一个进程的其他线程共享进程所拥
有的全部资源。通常一个进程可以包含若干个线程,它们可以利用进程所拥有的资源。

(2)并发与并行

并发,同一时刻只有一个线程运行。
并行,同一时刻有多个线程运行。

2 创建线程的两种方式

(1)继承Thread类

1 自定义类继承Thread,并复写父类中的run(),将线程运行的代码放到run方法体中。
2 创建子类对象的同时线程也被创建。
3 调用线程的start方法,开启线程。

(2)实现Runnable接口

1 自定义类实现 Runnable 接口,并覆盖接口中的 run(),将线程运行的方法放到 run 方法体中。
2 创建实现 Runnable 接口的子类对象,把它作为参数传递给 Thread 类的构造函数,创建一个 Thread 对象。
3 调用线程的 start 方法,开启线程。

(3)选用建议

(1)使用 Runnable 创建线程,解决了 extends Thread 的单继承的局限性。
(2)使用 Runnable 创建线程,可以进行数据资源的共享。
    所以一般选用实现 Runnable 接口,来创建线程。

3 线程生命周期的五种状态

1 新建(NEW)
当创建Thread类的一个实例(对象)时,此线程进入新建状态(未被启动)。
例如:Thread  t1=new Thread();
2 就绪(RUNNABLE)
线程已经被启动,正在等待被分配给CPU时间片,
也就是说此时线程正在就绪队列中排队等候得到CPU资源。例如:t1.start();
3 运行(RUNNING)
线程获得CPU资源正在执行任务(run()方法),此时除非此线程自动放弃CPU资源
或者有优先级更高的线程进入,线程将一直运行到结束。
4 死亡(DEAD)
当线程执行完毕或被其它线程杀死,线程就进入死亡状态,这时线程不可能再进入就绪状态等待执行。
自然终止:正常运行run()方法后终止。
异常终止:调用stop()方法让一个线程终止运行。
5 堵塞(BLOCKED)
由于某种原因导致正在运行的线程让出CPU并暂停自己的执行,即进入堵塞状态。
正在睡眠:用sleep(long t) 方法可使线程进入睡眠方式。一个睡眠着的线程在指定的时间过去可进入就绪状态。
正在等待:调用wait()方法。(调用motify()方法回到就绪状态)
被另一个线程所阻塞:调用suspend()方法。(调用resume()方法恢复)

    线程生命状态变迁图示:

4 线程基本操作

(1)线程间通信,等待/唤醒机制 wait()与notify()

  • (1)notify(): 唤醒对象监视器上的单个线程,也是把单个线程从阻塞态转变为可运行态(抢到 CPU 执行权,才变成 Running)。
  • (2)notifyAll(): 唤醒对象监视器上的所有线程,把线程从阻塞态转变成可运行态 Runnable,到底哪个线程执行,取决于哪个线程能够抢到 CPU 的执行权。
  • (3)wait():在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。是把当前线程从运行态转变为阻塞态。
  • (4)等待,唤醒必须写在同步代码块中,也就是说等待和唤醒的都是同步的线程。
  • (5)这些操作需要对象监视器的支持,因为锁(对象作为锁)是任意的,所以这些方法被定义在了 Object 中。

(2) wait()与sleep()区别

(1)被定义的位置,以及是否需要传入参数的区别。
 wait() 被定义在 Object 中,有函数重载形式,可以有毫秒参数,也可以没有。
sleep() 被定义在 Thread 类中,是静态方法。必须传入毫秒参数。
(2)使用时,二者放置的位置不同。
wait() 必须写在同步代码块(同步函数)中,也就是说需要有对象监视器(同步锁)的支持。
sleep() 可以写在任意地方,具体让那个线程休眠,取决于那个线程执行了该行代码。
(3)作用机制不同。
wait() 释放了 cpu 的执行权,同时还释放了锁。
sleep() 释放了 cpu 的执行权,但是不释放锁。

(3) 停止线程

(1)stop() 该方法已经被标注为过时方法  不建议使用。
(2)run()代码体执行结束,线程停止。
(3)interrupt(): 如果,线程的当前状态处于非阻塞状态,那么仅仅是线程的中断标志被修改为true而已;
如果线程的当前状态处于阻塞状态,那么在将中断标志设置为true后,且是wait、sleep以及jion三个方法
引起的阻塞,那么会将线程的中断标志重新设置为false,并抛出一个InterruptedException;

(4) 其他方法

(1)join():等待该线程执行结束。让该线程执行结束,才允许其它线程抢夺cpu的执行权。
(2)yield(): 当前线程释放cpu的执行权。(注意:但并不是说接下来一定执行其它线程。只能让同优先级的其它线程有执行机会。
当然当前线程和其它线程可以再次抢夺cpu的执行权。可能当前线程又抢到了执行权,它又接着执行。)
(3)线程对象调用toString()可以打印出一些和该线程相关的一些信息。

5 一些关键字含义及用途

(1)Volatile 

加锁机制,既保证可见性又保证原子性。而 Volatile 只保证可见性,禁止指令重排序,对读操作线程安全。

(2)非原子的 64 位操作

Java 内存模型要求,变量的读取操作和写人操作都必须是原子操作,但对于非 volatile 类型
的 long 和 double 变量,JVM 允许将64位的读操作或写操作分解为两个32位的操作。当读取一个非volatile
类型的long变量时,如果对该变量的读操作和写操作在不同的线程中执行,那么很可能会读取到某个值的
高32位和另一个值的低32位日。因此,即使不考虑失效数据问题,在多线程程序中使用共享且可变的long
和double等类型的变量也是不安全的,除非用关键字volatile来声明它们,或者用锁保护起来。

(3)Final关键字

Java 语言规范和 Java 内存模型中都没有给出不可变性的正式定义,但不可变性并不等于将对象中所有的域
都声明为 final 类型,即使对象中所有的域都是 final 类型的,这个对象也仍然是可变的,因为在 final 类型的域中
可以保存对可变对象的引用。在 Java 内存模型中,final 域还有着特殊的语义。final 域能确保初始化过程的安全性,
从而可以不受限制地访问不可变对象,并在共享这些对象时无须同步。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不甩锅的码农

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值