synchronized
如果某一个资源被多个线程共享,为了避免因为资源抢占导致资源数据错乱,我们需要对线程进行同步,那么synchronized就是实现线程同步的关键字,可以说在并发控制中是必不可少的部分
特性
- 原子性:如同事务要么全部执行,要么回滚最初,保证数据的准确性
- 可见性:多个线程需要访问一个资源的时候,具有该资源的状态、属性等都具有访问权限
- 有序性:在持锁的代码逻辑,具有先后顺序执行
- 可重入性:一个线程拥有了锁然而还可以重复申请锁
锁的是什么?
synchronized
可以修复方法、成员函数、静态方法、同时还有代码块,但是归根结底是锁什么?
- Class类
- new实体对象
private int num = 0;
@Test
public void testMain() throws InterruptedException {
Thread thread1 = new Thread(new MyRunnable());
Thread thread2 = new Thread(new MyRunnable());
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(num);
}
class MyRunnable implements Runnable {
//锁的对象
private synchronized void add() {
num++;
}
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
add();
}
}
}
// System.out.println(num); 1875
如果想要执行结果是 20000;怎么改? 方法很多抓锁的本质锁谁,优化谁
锁对象
- 既然锁对象,那么就在多线程
Thread
执行同一个对象,搞定!
MyRunnable syMyRunnable= new MyRunnable();
Thread thread1 = new Thread(syMyRunnable);
Thread thread2 = new Thread(syMyRunnable);
// System.out.println(num); 20000
- 锁对象,也可以在代码块里锁一个具体的对象,
private int num = 0;
private Object object = new Object();
@Test
public void testMain() throws InterruptedException {
Thread thread1 = new Thread(new MyRunnable());
Thread thread2 = new Thread(new MyRunnable());
//....
}
private void add() {
//锁代码块中的 唯一对象Object
synchronized (object) {
num++;
}
}
// System.out.println(num); 20000
锁类
如果想通过锁类,那么就保证Class为static静态
即可
private static int num = 0; //静态变量
@Test
public void testMain() throws InterruptedException {
Thread thread1 = new Thread(new MyRunnable());
Thread thread2 = new Thread(new MyRunnable());
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(num);
}
/**
*静态类
*/
static class MyRunnable implements Runnable {
//静态函数
private synchronized static void add() {
num++;
}
}
// System.out.println(num); 20000
可以总结:
- 静态方法锁class类
- 非静态方法锁对象
- 当锁类的情况下,会把这个Class类下的所有变量、方法以及引用对象都所锁住,造成资源浪费
- 一般可锁不被引用的对象
Object
- synchronized是悲观锁
- 优点:使用简单、不用手动去管理
- 弊端:太重、锁的属性对象太多、容易造成死锁
CAS
是英文单词Compare And Swap的缩写,翻译过来就是比较并替换
CAS机制当中使用了3个基本操作数:内存地址原值V,旧的预期值A,要修改的新值B
原理操作是:比较 A 与 V 是否相等, 如果比较相等,将 B 写入 V。(交换) 返回操作是否成功。
当多个线程同时对某个资源进行CAS操作,只能有一个线程操作成功,但是并不会阻塞其他线程,其他线程只会收到操作失败的信号。
lock
public interface Lock {
/**
* 获取锁
*/
void lock();
/**
* 如果当前线程未被中断,则获取锁,可以响应中断
*/
void lockInterruptibly() throws InterruptedException;
/**
* 仅在调用时锁为空闲状态才获取该锁,可以响应中断
*/
boolean tryLock();
/**
* 如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁
*/
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
/**
* 释放锁
*/
void unlock();
/**
* 返回绑定到此 Lock 实例的新 Condition
* return:Condition接口实例 具有唤醒阻塞的线程
*/
Condition newCondition();
}
获取锁
- lock()
- tryLock()
- tryLock(long time, TimeUnit unit)
- lockInterruptibly()
lock()
lock()
就是用来获取锁,如果当前锁已被其他线程获取,则进入等待,采用lock()
必须主动去释放锁,并且在发生异常时,不会自动释放,因此在使用时会try{}
,并且将释放操作放到finally
try{
//处理任务
}catch(Exception ex){
}finally{
//释放锁
lock.unlock();
}
tryLock() && tryLock(long time, TimeUnit unit)
- tryLock() 方法具有返回值,用来尝试获取锁,如果获取成功返回
True
,如果获取失败说明此时锁已经被其他线程所占用,则返回false
,也就是说这个方法无论如何都会立即返回,而并不是在那里自旋。 - tryLock(long time, TimeUnit unit) 也是具有返回值,区别是拿不到锁会等待一定的时间,在时间内还拿不到锁,就返回
false
,同时响应中断,如果一开始拿到锁或者是在等待时间范围类拿到锁就直接返回True
if(lock.tryLock()) {
try{
//处理任务
}catch(Exception ex){
}finally{
//释放锁
lock.unlock();
}
}else {
//如果不能获取锁,则直接做其他事情
}
lockInterruptibly()
该方法比较特殊,就是当lockInterruptibly()
去获取锁的时候,如果线程正在等待取锁,则这个线程能够响应中断,立即中断线程的等待状态,例如:当俩个线程同时通过lock.lockinterruptibly
想获取某个锁时,此时Thread-A获取到了锁,而Thread-B只能等待,那么对线程B调用Thread-B.Lockinterruptibly
就可以使得Thread-B中断等待过程。
public void method() throws InterruptedException {
lock.lockInterruptibly();
try {
//.....
}
finally {
lock.unlock();
}
}
释放锁
- unlock()
lock.unlock();
合适的try{}
地方进行释放
绑定用于占用
- newCondition()
// 只有一个锁
Lock lock = new ReentrantLock();
//condition 进行通信管理状态
Condition condition = lock.newCondition();
Condition
对于synchronized
锁,线程之间的通信方式是wait
和notify
。对于Lock
接口线程间就是通过Condition
方式通信的。它比wait/notify更强大的是,它支持等待过程中不响应中断、多个等待队列和在指定时间苏醒。
//reentrantlock对应一个AQS阻塞队列 ,其内部有个 abstract static class Sync extends AbstractQueuedSynchronizer {}
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void conditionWait() throws InterruptedException {
lock.lock();
try {
// 释放cpu的执行资格和执行权,同时释放锁
condition.await();
} finally {
//抛异常时进行释放锁
lock.unlock();
}
}
public void conditionSignal() throws InterruptedException {
lock.lock();
try {
//唤醒
condition.signal();
} finally {
//抛异常时进行释放锁
lock.unlock();
}
}
Atomic
JDK为了防止一些基本数据以及数组对象,分别在java.util.concurrent
包专门处理线程并发安全的工具类,并且包含多个原子操作,
- 标量类:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
- 数组类:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
- 更新器类:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
- 复合变量类:AtomicMarkableReference,AtomicStampedReference
其内部实现不是简单使用synchronized
,而是更加高效的 CAS + volatie
,避免大开销,提升执行效率,其中CAS
比较通过直接内存指针操作获取unsafe.getAndAddInt(**this**, valueOffset, 1)
, Unsafe.class
是大量native
操作本地方法的类,应用层一般都是通过反射机制操作,而JDK
是可以直接使用,其操作不当容易操作各种问题,官方不建议普通开发者进行使用。
volatile
Java语言提供了一种消弱的同步机制,即volatile变量
,用来确保将变量的更新操作通知到其他线程,当把变量声明为volatile
类型后,编译器与运行的时候都会注意到这个变量是共享的,因此不会将变量上的操作与其他内存操作一起排序。
- 在访问
volatile
变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile
变量是一种比synchronized
关键字更为轻量级的同步机制
当一个变量定义了volatile之后具有俩中特性:
- 此变量在多线程中具有可见性(顶部的特性中有说明)
- 禁止指向重新序优化,也就不会和其他内存操作一起排序。俗称内存屏障
常见作用多线程中:“一写多读”,只有唯一的线程(Thread-0)对事件的增删改,其他线程只读取用
总结
- 乐观锁:CAS,基准为有对象属性可能会被锁定
- 悲观锁:synchronized,基准为当前代码一定都会被锁定
- 无锁:轻量级无锁资源,单线程独享
- 偏向锁:总是由同一个线程多次获得,例如
main-Thread
,那么此时的锁为偏向锁 - 轻量级锁:由偏向锁升级而来,当有第二个线程也申请该锁的资源,那么一前一后交替执行同步代码
- 重量级锁:当同一个时间,多个线程同时竞争资源锁,此时锁就会升级重量级锁,导致开销大执行较重