多线程与并发编程常见问题(未完待续)

导读:本篇文章讲解 多线程与并发编程常见问题(未完待续),希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

1. 进程和线程

引入:
引入进程是为了能使多道程序并发执行,提高资源利用率和吞吐量;
而引用线程是为了则是为了减小程序在并发时的开销,提高并发性能;

1.1. 何为进程 ?

进程是程序的⼀次执⾏过程,是系统运行程序的基本单位,是操作系统进行资源分配和调度的基本单位;
在Java中,启动main函数就是启动了一个JVM的进程;
⼀个进程在其执行的过程中可以产生多个线程;

【操作系统中】,进程实体 = PCB进程控制块 + 程序段 + 数据段
系统利用PCB来描述进程的状态,以此控制和管理进程;

1.2. 何为线程 ?

线程与进程相似,是比进程更小的执行单位,是CPU执行的最小单元;
⼀个进程在其执行的过程中可以产生多个线程

【在Java中】,Java进程中有多个线程,共享进程的堆和⽅法区资源,每个线程有⾃己私有的程序计数器、虚拟机栈和本地⽅法栈,所以系统在产⽣⼀个线程,或是在各个线程之间作切换⼯作时,负担要⽐进程⼩得多,也正因为如此,线程也被称为轻量级进程。

2. 为什么要使用多线程 ?

1.提高系统资源(CPU、内存)利用率,执行效率;
2.线程切换的开销远比进程小
3.如今高并发的需求量,需要多线程机制来满足需求;

3. 使用多线程可能带来什么问题 ?

并发编程的⽬的就是为了能提⾼程序的执⾏效率提⾼程序运⾏速度,但是并发编程并不总是能提⾼程序
运⾏速度的,⽽且并发编程可能会遇到很多问题,比如:内存泄漏上下文切换死锁还有受限于硬件和软件的资源闲置问题。

4. 线程的生命周期 ?

在这里插入图片描述

5. 什么是上下文切换 ?

概括来说就是:当前任务在执行完 CPU 时间⽚切换到另⼀个任务之前会先保存⾃⼰的状态,以便下次再切换回这个任务时,可以再加载这个任务的状态。
任务从保存到再加载的过程就是⼀次上下⽂切换。

进程:
①保存原数据到PCB
②对新进程数据的恢复

6. 死锁

指多个进程因竞争资源造成的一种互相等待的僵局,
程序在等待资源被释放,但资源无法被是否,若无外力作用将无法先前推进;

死锁的必要条件(其中一个不成立就不会死锁):
1.互斥条件:资源只能被一个线程占用
2.请求保持条件:【线程阻塞时】,对已获得的资源不释放(自己不放)
3.不剥夺条件:线程已获得的资源不能被抢走(不能被抢)
4.循环等待条件:线程形成头尾相接的循环等待资源的关系

如何避免死锁:
1.互斥条件:无法破坏,用锁就是为了要互斥(临界资
源需要互斥访问,一次仅允许一个进程使用的资源即临界资源)
2.请求保持条件:一次性申请所有资源
3.不剥夺条件: 若线程申请不到资源,则主动释放资源
4.循环等待条件: 多用户访问资源时,按照相同资源访问顺序进行处理,破坏循环等待条件;

例子中解决方法:一次性申请所有资源,让线程t1 先用完o1、o2,再开启线程t2 (线程2 先Thread.sleep(2000) );

7. 创建线程的三种方式

1.子类extends 继承 java.lang.Thread线程类,重写run() 方法,使用子类创建线程对象,最后调用start()
2.implements实现java.lang.Runnable接口,重写run()方法,建立Runnable的对象引用放入Thread的构造参数中,最后调用start()
3.编写类implements实现Callable接口,重写call方法,将其对象引用放入FutureTask构造参数中,再将FutureTask的对象引用放入Thread构造参数中,最后调用start() (FutureTask 实现了Runnable接口,可以作为Thread的参数输入!)

. 并发编程三要素 ?

1)原子性
指的是一个或者多个操作,要么全部执行并且在执行的过程中不被其他操作打断,要么就全部都不执行。
实现:Synchronized

2)可见性
指多个线程操作一个共享变量时,其中一个线程对变量进行修改后,其他线程可以立即看到修改的结果。
实现:volatile

3)有序性
有序性,即程序的执行顺序按照代码的先后顺序来执行。
实现:volatile(禁止指令重排)

. 说说yield()、join()、sleep() 方法和 wait() 方法区别和共同点 ?

yield() 会让线程从运行状态直接进入就绪状态,下一次继续抢占时间片,区别于阻塞
join() 使当前线程进入阻塞调用join()的线程执行直到结束,阻塞解除,当前线程才能回到就绪态继续抢占时间片;
sleep() 使当前正在执行的线程进入阻塞状态,时间结束后进入就绪态继续抢占时间片;

wait()和notify()方法不是线程当中的方法,是任何一个Java对象都有的方法(Object自带的)
wait():让【当前对象上】活动的线程进入等待状态,无期限等待,直到调用o.notify方法被唤醒为止;②并且释放掉t线程之前占有的o对象的锁
notify(): 唤醒正在o对象上等待的线程;
notifyAll():方法可以唤醒o对象上处于等待的所有线程 / 没有waitAll !

sleep()和wait()对比

  • sleep()是Thread类的方法,而wait()是Object类的方法;
  • 最大的区别在于sleep() 没有释放锁,而wait()方法会释放锁
  • 两者都可以暂停线程的执行;
  • wait()方法调用后,线程不会自己恢复,需要其他线程调用【同一个对象上】的notify()方法来唤醒;sleep() 到时间就会从阻塞回到就绪状态;

. 直接调用线程对象的run()方法和调用start()方法有什么区别?

start()方法的作用是启动分支线程,是在JVM中开辟一个新的栈空间,这段代码任务完成之后瞬间就结束了。
这段代码的任务只是为了开辟新的空间,只要空间开出来后,start()方法结束,线程进⼊了就绪状
态,线程启动成功。
启动成功的线程会自动调用run()方法;

如果把t.start()改为t.run() ,则并未开辟分支栈,就是普通的调用方法,还是在main主线程里运行;

. 说一说Synchronize同步锁 ?

Synchronized原理
作用:
1.synchronized关键字主要解决多个线程之间访问资源的同步性
2.已经能够保证在【读写数据时】对数据原子性可见性

Synchronized实现可见性
1.线程在【加锁时】,先清空线程的工作内存;
2.在【主内存中】拷贝最新变量的副本到工作内存;
3.执行完代码【开锁前】,将更改后的共享变量的值刷新到主内存中;
4.释放锁

synchronized关键字最主要的三种使用方式:
1.修饰代码块: 指定加锁对象,向上找到共享对象;
2.修饰实例方法: 对this当前对象实例加锁;
3.修饰静态方法:表示找类锁,类锁永远只有一把!而对象锁则是一个对象一把锁;

. 说一说volatile ?

volatile
它主要有两重个作用,一是保证多个线程对共享变量访问的可见性,二防止指令重排

. 说说 synchronized 关键字和 volatile 关键字的区别 ?

  1. volatile主要用于解决变量在多个线程之间的可见性volatile不保证数据的原子性;
  2. synchronized关键字解决的是多个线程之间访问资源的同步性synchronized保证数据原子性可见性;(volatile和synchronized都可以保证数据的可见性!)
  3. volatile关键字是线程同步的轻量级实现,所以volatile性能肯定⽐synchronized关键字要好。
  4. volatile关键字只能用于变量,而synchronized关键字可以修饰方法以及代码块,主要是对共享对象加锁;

. 谈谈 synchronized和ReentrantLock的区别 ? ※

ReentrantLock锁:ReentrantLock
ReentrantLock底层基于AQS实现,即使用volatile修饰的state属性和阻塞队列来实现线程的串行执行,从而达到线程安全性的目的;
ReentrantLock是可重入的互斥锁,虽然具有与synchronized相同功能,但是会比synchronized更加灵活(具有更多的方法),能解决synchronized关键字在一些并发场景下不适用的问题;

  1. 两者都是可重⼊锁
    “可重⼊锁”概念是:⾃⼰可以再次获取⾃⼰的内部锁。⽐如⼀个线程获得了某个对象的锁,此时这个对象锁还没有释放,当线程再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重⼊的话,就会造成死锁。
    同⼀个线程每次获取锁,锁的计数器都⾃增1,所以要等到锁的计数器下降为0时才能释放锁。
    重入锁的设计目的是为了解决死锁的问题;

  2. synchronized 依赖于 JVM 而 ReentrantLock 依赖于 jucAPI

  3. ReentrantLock可以指定是公平锁还是非公平锁。⽽synchronized只能是⾮公平锁。所谓的公平
    锁就是先等待的线程先获得锁。 ReentrantLock默认情况是⾮公平的,可以通过 ReentrantLock
    类的 ReentrantLock(boolean fair) 构造⽅法来制定是否是公平的。

  4. synchronized关键字与wait()和notify()/notifyAll()⽅法相结合可以实现等待/通知机制,
    ReentrantLock类当然也可以实现,但是需要借助于Condition接⼝与newCondition() ⽅法。
    Condition是JDK1.5之后才有的,它具有很好的灵活性,⽐如可以实现多路通知功能也就是在⼀
    个Lock对象中可以创建多个Condition实例(即对象监视器),线程对象可以注册在指定的
    Condition中,从⽽可以有选择性的进⾏线程通知,在调度线程上更加灵活。 在使⽤
    notify()/notifyAll()⽅法进⾏通知时,被通知的线程是由 JVM 选择的,⽤ReentrantLock类结
    合Condition实例可以实现“选择性通知” ,这个功能⾮常重要,⽽且是Condition接⼝默认提供
    的。⽽synchronized关键字就相当于整个Lock对象中只有⼀个Condition实例,所有的线程都注
    册在它⼀个身上。如果执⾏notifyAll()⽅法的话就会通知所有处于等待状态的线程这样会造成
    很⼤的效率问题,⽽Condition实例的signalAll()⽅法 只会唤醒注册在该Condition实例中的所
    有等待线程。

  5. 可中断:synchronized只能等待同步代码块执行结束,不可以中断,而reentrantlock可以调用线程的interrupt方法来中断等待,继续执行下面的代码。
    可以设置超时时间:调用lock.trylock(),如果没有设置等待时间的话,没获取到锁,将返回false
    可以设置为公平锁:公平锁其实是为了解决饥饿问题,当一个线程由于优先级太低的时候,就可能没有办法获取到时间片
    可以支持多个变量:类似于调用wait方法时,不满足条件的线程进入waitset队列等待CPU随机调度,支持多个变量表示支持多个类似自定义waitset,这样就可以指定对象来唤醒了。

. CAS

即Compare and Swap,是非阻塞的方式实现并发,是一种轻量级的乐观锁
CAS体现的是无锁并发,无阻塞并发; (无阻塞所以上下文切换少,效率高)

乐观锁思想:允许其他线程来修改数据,先核验再执行
悲观锁思想:不允许其他线程来修改数据;

CAS操作包含了三个操作数据:
主内存的共享变量(V)、预期原值(A)和新值(B) ;

实现:
先比较主内存中的共享变量预期原值的大小是否相同,相同即主内存中的变量还没有被其他线程所改变,则修改为新值;否则继续尝试;
如:
在这里插入图片描述
compareAndSet的compareAndSet会比较prev旧值和内存中的共享变量,如果相同则返回true,将旧值修改为新值;否则继续尝试;

CAS和volatile :
CAS需要volatile以保证内存中共享变量的值为最新,因为每次compareAndSet,会比较共享变量最新的结果;然后将最新的结果和pre旧值去比较,如果相等才进行更新;
如果共享变量没有用volatile修饰,则拿到的共享变量可能不是最新的,那么就可能出问题;

为什么无锁效率更高 ?
因为就算compareAndSet失败了,但是while(true)一直在高速运行没有停下来,上下文切换少,所以效率高;
而用Synchronized时,则会因为线程占有Owner则会在EntryList队列中阻塞等待,线程会发生上下文切换,成本高;

. ThreadLocal

ThreadLocal总结

. ThreadLocal与Synchronized的区别 ?

1.Synchronized用于线程间的数据共享,而恰恰相反,ThreadLocal用于线程间的数据隔离
2.Synchronized是利用锁的机制,以阻塞的方式 使变量或代码块在同一时该只能被一个线程访问;而ThreadLocal为每一个线程都提供了变量的副本;

. JMM

Java 内存模型是一种规范;

所有的变量都存储在主内存中;
每个线程都有一个私有的本地内存,本地内存中存储了该线程以读/写共享变量的拷贝副本。
线程对变量的所有操作都必须在本地内存中进行,而不能直接读写主内存。
不同的线程之间无法直接访问对方本地内存中的变量。

三大特性
即在【多线程读写共享数据时】(成员变量、数组)时,数据的原子性、可见性、有序性

原子性
由synchronized来实现;

可见性
比如说一个线程对某个变量进行修改,其他线程能够立刻看到,即为可见。
由volatile 、synchronized来实现;

有序性
代码的执行顺序应该上到下按序执行,但编译器有优化机制,会为了提升程序的性能和执行速度而调整执行顺序,单线程时结果看起来没什么变化,多线程下结果就可能出问题。
由volatile 来实现;

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/89230.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!