java并发编程

可以点进去看源码

synchronized

同步的,可重入(计数器),非公平

用在方法和代码块上是对象锁。用在静态方法上是类锁(类的字节码文件对象,Class锁)

  • 代码块是括号里的对象?

获取了类锁的线程和获取了对象锁的线程是不冲突的!(作用于线程)

每个对象内部都有一把,只有抢到那把锁的线程,才被允许进入对应的代码块执行相应的代码。

随便使⽤⼀个对象作为锁)在书上称之为–>客户端锁,这是不建议使⽤的。 书上想要实现的功能是:给ArrayList添加⼀个 putIfAbsent() ,这需要是线程安全的

话说谁知道你这是哪本书啊艹 为什么不建议啊话说,哦是因为,,艹辣鸡java3y,什么装饰器模式

  • 话说操作系统的锁是锁代码区域的,,这个对象锁呢?

    被锁的方法/代码块执行完会释放锁,若是出现异常也会释放(不会因异常导致死锁)

原理

JVM规范:代码块同步是使用monitorenter 和monitorexit指令实现的,而方法同步是使用另外一种方式实现的,细节在JVM规范里并没有 详细说明。但是,方法的同步同样可以使用这两个指令来实现。

任何对象都有 一个monitor与之关联,当且一个monitor被持有后,它将处于锁定状态。线程执行到monitorenter 指令时,将会尝试获取对象所对应的monitor的所有权,即尝试获得对象的锁

相当于划分临界区?

monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到结束处和异常处,

历史:1.6之前被称为重量级锁,之后为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁

Lock显示锁

1.5.之后出现,有很多子类。

  • Lock⽅式来获取锁⽀持中断、超时不获取、是⾮阻塞的
  • 提⾼了语义化:哪⾥加锁,哪⾥解锁都得写出来
  • Lock显式锁可以给我们带来很好的灵活性,但同时我们必须⼿动释放锁
  • ⽀持Condition条件对象 允许多个读线程同时访问共享资源

更加灵活(有许多Synchronized没有的特性,如重入、读写锁)但1.6之后Synchronized有了很多优化

优化操作:适应⾃旋锁,锁消除,锁粗化,轻量级锁,偏向锁

现在两者性能差不多,但Lock还要手动释放。

(Synchronized直接代码块画的范围)

所以用哪个都行,该利用特性的时候记得Lock就行

Lock包下三抽象类

AbstractOwnableSynchronizer

AbstractQueuedLongSynchronizer

AbstractQueuedSynchronize:AQS

通过看注释:

依赖于先进先出的等待队列,依靠单个原子int值表示状态(获取中or释放)

子类一般使用内部类来实现需要同步的工作

提供两种模式:独占,共享

在LOCK包中的相关锁(常⽤的有ReentrantLock、 ReadWriteLock)都是基于AQS来构建

⼀般我们叫AQS为同步器

通过CAS原子操作修改state的值,用volatile实现可见性。

  • 这里CAS怎么实现?

好家伙还自己实现队列,,

acquire方法使用了模板设计模式

过程:acquire(int)尝试获取资源,如果获取失败,将线程插⼊等待队列。插⼊等待队列后, acquire(int)并没有放弃获取资源,⽽是根据前置节点状态状态判断是否应该继续获取资源,如果 前置节点是头结点,继续尝试获取资源,如果前置节点是SIGNAL状态,就中断当前线程,否则继 续尝试获取资源。直到当前线程被park()或者获取到资源,acquire(int)结束

两个子类

ReentrantLock

关键字:阻塞队列的实现原理,独占模式

互斥,构造方式支持用参数设置为“相对公平”,比sy灵活,使⽤时最标准⽤法是在try之前调⽤lock⽅法,在finally代码块释放锁

class X {
 	private final ReentrantLock lock = new ReentrantLock();
	// ...
	public void m() {
 		lock.lock(); // block until condition holds
 		try {
 			// ... method body
 		} finally {
 			lock.unlock()
 		}
 	}
}

三个内部类

  • Sync
  • NonfairSync
  • FairSync

都是AQS的子类(AQS相当于框架)

非公平:尝试获取锁,失败了就用acquire。acquire()里的tryAcquire之前说子类实现的,应该就是这里?

  • 回头去看看Cas怎么实现的啊喂,还有OS里的

在ReentrantLock锁上使⽤的是state来表示同步状态(也可以表示重⼊的次数),

除了上面例子中的使用lock.lock()方法外,还有

  • lock.lockInterruptibly()
    • 一个线程获取了锁之后,是不会被interrupt()方法中断的。因为调用interrupt()方法不能中断正在运行过程中的线程,只能中断阻塞过程中的线程。
      而当通过lockInterruptibly()方法获取某个锁时,如果不能获取到,只有进行等待的情况下,是可以响应中断的。
      而用synchronized修饰的话,当一个线程处于等待某个锁的状态,是无法被中断的,只有一直等待下去。

####ReentrantReadWriteLock

默认非公平

也可以公平,等待时间最长的进程将获取读写锁

这公不公平说法谁提出来的,,OS怎么不提这个

不是互斥锁而是读写锁:

  • 读取数据的时候,可以多个线程同时进⼊到到临界区
  • 写数据的时候,⽆论是读线程还是写线程都是互斥的
  • 那读的时候能不能开始写?打开文件的时候怎么知道是读还是写?

实现了ReadWriteLock接口

该接口维护一对锁(读锁和写锁)

理论上并发性更好(毕竟可以同时读了),但是修改并发增加的化,有吧开销花在互斥上了

以一个集合为例,如果初始化后常查询,少修改,那么适合

当尝试公平获取写锁时,应该处于无锁状态

写锁可以获取读锁,读锁不能获取写锁(什么意思?线程获取?写到一半想读了?)

写锁可以降级成读锁,读锁不能升级为写锁

两锁都支持在获取时中断

好家伙,这和中断和os是类似道理不?那个好像是硬件中断指令让cpu住手,,不过OS好像没讲主动让出?只讲了正常退出?

内部类

ReentrantReadWriteLock⽐ReentrantLock锁多了两个内部类(都是Lock实现)来维护读锁和写锁,但是 主体还是使⽤Syn:

  • WriteLock
  • ReadLock

高16位表示读,低16位表示写

调⽤syn的 acquire(1) 获取写锁,这里有丶像os,if(加上之后 > MAX_COUNT),throw Error

调⽤的是 acquireShared(int arg) ⽅法获取读锁,

公平锁

公平锁理解起来⾮常简单: 线程将按照它们发出请求的顺序来获取锁 ⾮公平锁就是: 线程发出请求的时可以“插队”获取锁 Lock和synchronize都是默认使⽤⾮公平锁的。如果不是必要的情况下,不要使⽤公平锁 公平锁会来带⼀些性能的消耗

线程安全:

ThreadLocal:空间换时间

共享变量在每个线程都有一个副本,每个线程操作的都是自己的副本,对另外的线程没有影响。

JDK官方的保证线程安全的集合

run()和start()⽅法区别: run() :仅仅是封装被线程执⾏的代码,直接调⽤是普通⽅法 start() :⾸先启动了线程,然后再由jvm去调⽤该线程的run()⽅法。

jvm虚拟机的启动是单线程的还是多线程的? 是多线程的。不仅仅是启动main线程,还⾄少会启动垃圾回收线程的,不然谁帮你回收不⽤的内 存~

既然有两种⽅式实现多线程,我们使⽤哪⼀种???

⼀般我们使⽤实现Runnable接⼝ 可以避免java中的单继承的限制 可以将并发运⾏任务和运⾏机制解耦

Callable就是Runnable的扩展。 Runnable没有返回值,不能抛出受检查的异常,⽽Callable可以

使⽤守护线程的时候要注意的地⽅

  1. 在线程启动前设置为守护线程,⽅法是 setDaemon(boolean on)
  2. 使⽤守护线程不要访问共享资源(数据库、⽂件等),因为它可能会在任何时候就挂掉了。
  3. 守护线程中产⽣的新线程也是守护线程
  • 共享资源?
  • 线程的优先级是⾼度依赖于操作系统的,Windows和Linux就有所区别(Linux下优先级可能就被忽略 了)?为什么被忽略?优先级怎么实现的?

如果有线程组,优先级不会超过线程组

servlet线程数据全在栈上,指的是doPost方法的参数之类的?

各种锁总结对比

sy

重入锁:同一线程可以进了又进

重入读写锁:更细粒度的控制(可以具体控制读和写的不同区别,不是统一加锁了)

线程池

JDK:Executor提供了⼀种将“任务提交”与“任务执⾏”分离开来的机制(解耦

Semaphore:同步工具类

Atomic

count++不是原子的

CAS

来源维基百科:

⽐较并交换(compare and swap, CAS),是原⼦操作的⼀种,可⽤于在多线程编程中实现不被打断 的数据交换操作,从⽽避免多线程同时改写某⼀数据时由于执⾏顺序不确定性以及中断的不可预 知性产⽣的数据不⼀致问题。 该操作通过将内存中的值与指定数据进⾏⽐较,当数值⼀样时将内 存中的数据替换为新的值

  • 原子操作都有哪些?这个看起来不用硬件?OS怎么没讲?

失败的线程并不会被挂起,⽽是被告知这次竞争中失 败,并可以再次尝试(或者什么都不做)

毕竟挂起的话可能只能等os唤醒,,但CAS原来的操作不是占用资源,没唤醒这一说吧??话说学OS都是拿硬件资源为例,,要自己主动重试的只见过自选

真的好像乐观锁,,

Atomic包⾥的类基本都是使⽤Unsafe实现的包装类。 Unsafe⾥边有⼏个我们喜欢的⽅ 法(CAS):

⽽Unsafe底层实际上是调⽤C代 码,C代码调⽤汇编,最后⽣成出⼀条CPU指令cmpxchg,完成操作。这也就为啥CAS是原⼦性的,因 为它是⼀条CPU指令,不会被打断

ABA问题

ABC三个线程,一个值,A改了,B又改回去,结果C发现跟自己预期值一样

  • 话说其他线程怎么更新预期值来着?好像只说了预期值不一样就自己呆着去,,

要解决ABA的问题,我们可以使⽤JDK给我们提供的AtomicStampedReference和 AtomicMarkableReference类

简单来说就是在给为这个对象提供了⼀个版本,并且这个版本如果被修改了,是⾃动更新的。 原理⼤概就是:维护了⼀个Pair对象,Pair对象存储我们的对象引⽤和⼀个stamp值。每次CAS⽐较的是 两个Pair对象

啊好吧,,到这里才是乐观锁

ThreadLocal

ThreadLocal提供了线程的局部变量,每个线程都可以通过 set() 和 get() 来对这个局部变量进⾏操作,但不会和其他线程的局部变量进⾏冲突,实现了线程的数据隔离

最典型的是管理数据库的Connection:数据库连接池的连接交由ThreadLocal来进⾏管理ThreadLocal能够实现当前线程的操作都是⽤同⼀个Connection,保证了事务

  1. 每个Thread维护着⼀个ThreadLocalMap的引⽤
  2. ThreadLocalMap是ThreadLocal的内部类,⽤Entry来进⾏存储
  3. 调⽤ThreadLocal的set()⽅法时,实际上就是往ThreadLocalMap设置值,key是ThreadLocal对 象,值是传递进来的对象
  4. 调⽤ThreadLocal的get()⽅法时,实际上就是往ThreadLocalMap获取值,key是ThreadLocal对象 5. ThreadLocal本身并不存储值,它只是作为⼀个key来让线程从ThreadLocalMap获取value。

ThreadLocal要避免内存泄漏,及时remove。

。JAVA中Volatile的作用:强制每次都直接读内存,阻止重排序,确保voltile类型的值一旦被写入缓存必定会被立即更新到主存

补充

Thread.sleep() 本线程在未来***毫秒内不参与cpu竞争,操作系统不会在这段时间内重新计算该线程的优先级

  • 都是优先级队列算法?linux?win?java怎么在那里实现sleep?
  • 都是啥时候计算优先级?一个线程结束后?
    • 当进程执行完毕或者自己主动挂起后,操作系统就会重新计算一 次所有进程的总优先级,
  • 会立即让出cpu吧
  • 。Unix系统使用的是时间片算法,而Windows则属于抢占式的。为什么说win抢占式是主动让出?
  • sleep是挂起么?挂起是不参与cpu竞争的意思吗?

你可能感兴趣的:(java基础,java,jvm,开发语言)