java锁
java锁是什么?
- 多线程同步操作的实现需要给对象一个互斥体,这个互斥体就可以叫做锁
锁的实现方式
- java中锁的实现方式有两种,synchronized关键字和并发包中的锁类
死锁
- 线程之间相互等待着对方释放资源,而自己的资源又不释放给别人,这种情况就是死锁
重入锁
- 一个线程在拥有了当前资源的锁之后,可以再次拿到锁而不被阻塞
自旋锁
- 线程在没有获得锁时不会被挂起,而是一直执行一个空循环,默认10次
- 自旋锁的目的是减少线程被挂起的次数,线程的挂起和释放都是耗费资源的
- 自旋锁不适合时间长的并发情况,因为自旋时间过长还是会被挂起
自适应自旋锁
- 是对自旋锁的一种优化,当一个线程自旋获得锁之后,那么下次自旋的次数就会增加,相反假如没有获得锁,下次会减少自旋次数
锁消除
- 在编译期间利用“逃逸分析技术”对不存在竞争却加了锁的代码进行锁失效
- 这样就减少了锁的请求和释放操作
tips
- 逃逸分析技术:会将确定不会逃逸的对象放在栈内存中,所以说,并不是所有的对象都放在堆内存中
锁偏向
- 线程再次进入时,如果线程id与Theadid的值相等,那么该线程不会再重复获得锁
锁粗化
- 在编译期间将相邻的同步代码块合并成一个大同步代码块
类锁
- synchronized作用在静态方法上,占用的资源是类级别的,相当于在类上加锁,这个时候只要是这个类产生的对象在调用这个方法的时候都会产生互斥,即该类的对象都共享一把锁
对象锁
- synchronized作用在非静态方法上,给对象加锁,这个时候两个或者两个以上的线程执行这个对象的同步方法时就会产生互斥
tips
- synchronized修饰符要求执行方法要获得对象的锁才能执行
- 这个线程同时访问同一对象的同步方法A(对象锁)时会发生竞争,访问不同对象的A是不会发生竞争
- 如果是类锁都会发生竞争
synchronized实现原理
- 有一个临界区:被同步保护的代码区域,在字节码中有monitorenter申请锁和monitorexit释放锁指令
锁的作用
- 保证同一时刻同一竞争资源只有一个线程在访问
锁的优化策略
- 锁消除、锁偏向、自适应自旋锁、锁粗化
线程同步的方式
- synchronized修饰
- volatile实现同步(只能保证可见性,不能保证原子性)
- 使用Lock
- 使用原子类
- 使用局部变量ThreadLocal
- 使用容器类
voatile
-
是java提供的一种轻量级的同步锁,java提供了两种内在的同步机制:synchronzed同步代码块或方法,voatile同步变量
-
voatile更轻量级,他不会引起线程上下文的切换和调度,但voatile变量同步性较差
并发编程的三个基本概念
原子性
- 一个操作或者多个操作要么全部执行,要么都不执行
- 原子性拒绝多线程操作的,无论是单核还是多核,同一时刻只能有一个线程对她进行操作
- 在整个操作过程中不会被线程调度器中断的操作,都可以认为是原子性的,例如a=1是原子性操作,a++,a+=1都不是原子性操作
java中原子性操作包括
- 基本数据类型的读取和赋值,且赋值必须是值赋值给变量,变量之间的赋值不是原子性操作
- 所有引用reference的赋值操作
- java.concurrent.Atomic包中的所有类的一切操作
可见性
- 当一个线程修改了共享变量的值时,其他线程能够立即得知
- 当一个变量被volatile修饰后,表示线程工作内存无效,一个线程修改这个变量时会立即更新到主内存中。其他线程读取这个变量时会直接从主内存中读取
- synchronized和lock都可以保证可见性,都能保证同一时刻只有一个线程获得锁,然后执行同步代码,并且在释放锁之前会将修改的变量更新到主内存中
有序性
- 程序的执行顺序按照代码的先后顺序执行
锁的互斥和可见性
- 锁提供了两种主要特性:互斥和可见性
- 互斥:一次只允许一个线程持有某个特定的锁,一次只有一个线程访问该共享数据
tips
- 共享变量储存在主内存中,每个线程都有一个私有的工作内存,工作内存中保存了主内存中共享变量的副本拷贝,线程对变量的所有操作都是在工作内存中进行的。