多线程

多线程

参考与这位大佬

多线程与并发的关系:
并发是程序的一种允运行状态,多线程是解决这种状态的一种手段方法。

并发编程三要素(即并发编程常见的是三个问题):

  • 原子性: 原子性指的是一个或者多个操作要么执行成功,要么不执行
  • 可见性: 可见性指的是当多个线程共享一个变量时,当其中一个线程修改变量后,其它线程可立刻看到修改结果
  • 有序性: 有序性指的是程序执行的顺序按照代码的顺序执行

原子性问题:
经典例子: 银行转账问题。A 账户向 B 账户转账 1000 元。包括两个步骤,一 A 账户减去 1000 元,二 B 账户加上 1000 元。如果这个操作不具备原子性,则会造成账户余额之不正确的情况。比如 A 减去 1000 元成功了,但此时操作停止了,然后又从 B 取出 500 元,接着又给 B 从入 1000 元,这样就会导致 A 虽然 减去了 1000 元,但 B 却没有收到这 1000 元。
在并发编程中:

i = 0;
j == i;
i++;
i = j + 1;
只有第一个才保证了原子性。
在单线程下,我们可以认为整个操作都是原子性的,但在多线程下,java 只保证了基本数据类型的变量和赋值操作的原子性。

在多线程环境下可以通过 锁、synchriozed 来保证原子性。volatile 无法保证复合操作的原子性。

可见性问题:

//线程一
int i = 0;
i = 10;

//线程二
j = i;

对于上面的代码,变量 i 需要两个线程来操作。线程一先初始化 i,在给其赋值 10,过程是 cpu 现将其初识值加载到 cpu 高速缓存中,在给其赋值,如果在这时(高速缓存中 i 的值并未被曝存在内存中)线程二运行,线程二是直接从内存中获取,那么他获取将是 i 的初始值,也就是说 对于线程一操作的数据并没有被线程二可见。

对于可见性问题解决办法有两种,即用 voliate 修饰线程共享变量和加锁。

  • voliate: voliate 修饰的变量,系统可以保证其被线程修改的值立刻保存到内存中
  • 加锁: 即使用 sychronized 或 lock,它会保证变量每次只被一个线程得到,因此可以保证变量在被一个线程修改后保存到内存中才会被其它线程得到。

有序性问题:
有序性指的是代码的运行顺序必须和代码的实际顺序一致。出现有序性问题的原因是 jvm 在执行代码的时候会在保证程序运行结果不变的情况对执行指令进行优化,即有可能会改变代码的运行顺序。
有序性问题智只会出现在多线程环境里,单线程环境是允许发生指令重排现象的。

对于有序性问题解决办法是通过 vloiate 关键字来保证有序性或者使用 sychronized 和 lock 关键字。sychronized 和 lock 会保证在每个时刻内只有一个线程执行同步代码,自然就会保证有序性。另外,java 内存模型也具有一些先天的手段来保证有序性,一般称之为 happens-before 原则。

多线程的优点:

  • 充分利用资源: 即多线程可以达到充分利用多核 CPU 的目的。
  • 防止阻塞: 对于单线程程序可能会由于读取资源时发生阻塞,从而导致程序崩溃。但在多线程中,某一个线程死掉,其它线程认可正常运行。
  • 便与建模: 对于一个大型系统来说,如果使用单线程,那就必须考虑很多问题以防止崩溃,从使程序变得更加复杂。这时候就可以考虑使用多线程编程,可以把系统A的任务拆分成 B C D 等,拆分后我们只需要保证每个小任务稳定就可以。

线程的创建方式:

  • 继承 Thread 类
  • 实现 Runnable 接口
  • 实现 Callable 接口
  • 通过线程池创建

线程创建方式之间的区别:

  • 继承 Thread 类:

    • 优点: 编写简单,访问当前线程只需使用 this 即可
    • 缺点: 由于线程类继承了 thread 类,所以不能在继承其它类
  • 实现 Runnable 或 Callable 接口:

    • 优点: 线程类实现类 runnable 或 callable 接口,还可以继承其它类;这种方式下,多个线程可以共享一个目标对象,所以适合多个线程共同处理一个资源的情况,从而可以看成是 CPU、代码和资源形成的模型,更好的体现了 OOP 思想。
    • 缺点: 代码编写较为复杂,访问当前线程得用 thread.currentThread() 方法。
  • 实现 Runnable 或 Callable 接口的区别:

    • runnable 重写的方法是 run(),calable 重写的方法是 call()。
    • run() 不可以抛出异常,call() 可以抛出异常。
    • run() 没有返回值,call() 有返回值。call() 会返回一个 future 对象,通过 future 对象可以查看任务是否完成,了解任务的执行情况,获取任务执行的结果。

线程的五种状态:

  • 新建状态(New): 当线程对象创建后,线程进入新建状态。(Thread thred = new TestThread())。
  • 就绪状态(Runnable): 当线程对象调用 start() 方法时线程进入就绪状态,等待 cpu 调度。
  • 运行状态(Running): 当有空闲 cpu 资源调度处于就绪状态的线程时,此线程进入运行状态,也就是执行 run()/call() 方法中的内容。
  • 阻塞状态(Blocked): 当处于运行状态的线程由于某种原因,暂时放弃对 cpu 的使用权,则此线程进入阻塞状态。进入阻塞状态的线程等到阻塞状态结束后重新进入就绪状态,等待被 cpu 调度。
    • 等待阻塞: 运行状态的线程内调用 wait() 方法,使线程进入等待阻塞状态。
    • 同步阻塞: 运行状态的线程获取 sychronized 同步锁失败(此锁被其它线程占用),是线程进入同步阻塞状态。
    • 其它阻塞: 调用线程的 sleep()(线程等待方法)、join()、(让主线程等待子线程执行完成)、发出 I/O 请求使线程进入阻塞状态。当 sleep 状态超时、join() 等待结束或超时、I/O请求处理完毕后,线程将进入就绪状态等待 cpu 调度。
  • 死亡状态(Death): 线程运行结束或其它原因使线程退出 run() 方法,则线程进入死亡状态。

线程池:
线程池是指提前创建好多个线程,当有任务处理时,线程池里的线程去处理,处理完成后线程不会被销毁,而是继续等待下一个任务。当有需要频繁创建和销毁线程的业务场景时就可以考虑使用线程池,因为线程的创建和销毁是非常消耗系统资源的。
java 提供了 Executor 接口来创建线程池:

  • newCachedThreadPool: 创建一个缓存线程池
  • newFixedThredPool: 创建一个定长线程池,可以控制线程池线程最大并发数
  • newScheduledThreadPool: 创建一个定长线程池,可以执行周期性和定时任务
  • newSingleThreadPool: 创建一个单线程化的线程池,只能以唯一的工作线程执行任务

常用并发工具类及区别:
常用的并发工具类有 CountDownLatch、CyclicBarrier、Semaphore、Exchanger

  • CountDownLatch 简单说就是一个线程等待,当他所等待的线程全部执行完毕,且调用 countDown() 发出通知后,当前线程才可以执行。
  • CyclicBarrier 是所有线程都进行等待,当所有线程准备好进入 await() 方法后,所有线程同时开始执行。
  • CountDownLatch 的计数器只能使用一次,而 CyclicBarrier 的计数器可以使用 reset() 方法重置,所以 CyclicBarrier 更适合处理更复杂的业务场景。比如当计算错误时,可以重置计数器,让线程重新执行。
  • CyclicBarrier 还有其它常用的方法,如 getNumberWaiting() 方法可以用来获取 CyclicBarrier 阻塞的线程,isBroken() 可以判断阻塞线程是否被中断,如果被中断则返回 true,否则返回 false。

sychronized 和 voliate:

  • sychronized: sychronized 关键字是在多线程环境下控制线程同步的,即保证被 sychronized 修饰的代码段或方法在多线程环境下只能被一个线程执行。
  • voliate: voliate 关键字是用来保证可见性的,即当一个变量需要被多个线程同时操作时,voliate 会保证它的可见性,也就是当此变量被某个线程更新后会被立刻存入内存,从而使另一个操作此变量的线程从内存中获取到的是最新值。voliate 的另一个作用是结合 CAS(比较交换)保证了线程的原子性。

乐观锁与悲观锁:

  • 乐观锁: 乐观锁对多线程情况下的线程安全问题持乐观态度,即它认为竞争不总是会发生,因此它不需要持有锁,将 CAS(比较与替换)当成一个原子操作尝试去更新内存中的变量,如果失败则表示发生冲突,因此应用相应的重试逻辑。
  • 悲观锁: 悲观锁对多线程情况下的线程安全问题持悲观态度,即它认为竞争总是会发生,因此它必须持有一个锁,去更新内存中的变量(或这说没有锁就不能操作资源)。

事物总是相对的,如生与死,阴与阳。道言,两级生四象,四象生八卦,八卦即万物。又言,道生一,一生二,二生三,三生万物,是谓道也!

(卧槽。。。编不下去了。。。。)

@

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值