1.锁是干什么用的
锁一般来说用作资源控制,限制资源访问,防止在并发环境下造成数据错误
2.重入锁
重入锁也叫作递归锁,指的是同一个线程外层函数获取到一把锁后,内层函数同样具有这把锁的控制权限
synchronized和ReentrantLock就是重入锁对应的实现
synchronized重量级的锁
ReentrantLock轻量级的锁 lock()代表加入锁 unlock()代表释放锁
不可重入锁:说明当没有释放该锁时。其他线程获取该锁会进行等待
public class MyLock { //标识锁是否可用 如果值为 true代表有线程正在使用该 锁 ,如果为false代表没有人使用锁 private boolean isLocked=false; //获取锁:加锁 public synchronized void lock() throws InterruptedException { //判断当前该锁是否正在使用 while (isLocked){ wait(); } //当前没有人使用情况 下就占用该锁 isLocked=true; } //释放锁 public synchronized void unLock(){ //将当前锁资源释放 isLocked=false; //唤起正在等待使用锁的线程 notify(); } }
public class MyLockTest {
MyLock myLock=new MyLock(); //A业务 public void print() throws InterruptedException { //获取一把锁 myLock.lock(); System.out.println("print业务方法"); doAdd(); //释放锁 myLock.unLock(); } //B业务方法 public void doAdd() throws InterruptedException { //获取一把锁 myLock.lock(); System.out.println("aoAdd方法"); //释放锁 myLock.unLock(); } public static void main(String[] args) throws InterruptedException { MyLockTest myLockTest=new MyLockTest(); myLockTest.print(); }
}
控制台结果:
当前效果就造成了死锁
synchronized可重入性:如果当前A持有一把锁,在A业务内部调用B,那么B也同样拥有这把锁的使用权限
编写测试代码:
MyLock myLock=new MyLock(); //A业务 public synchronized void print() throws InterruptedException { System.out.println("print业务方法"); doAdd(); } //B业务方法 public synchronized void doAdd() throws InterruptedException { System.out.println("aoAdd方法"); } public static void main(String[] args) throws InterruptedException { MyLockTest myLockTest=new MyLockTest(); myLockTest.print(); }
控制台结果:
ReentrantLock同样具有可重入性
编写测试代码:
public class MyLockTest { //创建锁对象 Lock lock=new ReentrantLock(); //A业务 public void print() throws InterruptedException { //获取了一把锁 lock.lock(); System.out.println("print业务方法"); doAdd(); //释放锁 lock.unlock(); } //B业务方法 public void doAdd() throws InterruptedException { //获取一把锁 lock.lock(); System.out.println("aoAdd方法"); //释放锁 lock.unlock(); } public static void main(String[] args) throws InterruptedException { MyLockTest myLockTest=new MyLockTest(); myLockTest.print(); } }
控制台结果:
3. 读写锁
并发线程下,所有线程都执行读的操作,会不会有问题
并发线程下,部分读部分写会不会有问题 会发生写冲突
并发线程下,所有线程都执行写会不会有问题 会发生写冲突
编写测试代码:
//创建一个集合 static Map<String,String> map=new HashMap<String,String>(); //创建一个读写锁 static ReentrantReadWriteLock lock=new ReentrantReadWriteLock(); //获取读锁 static Lock readLock=lock.readLock(); //获取写锁 static Lock writeLock=lock.writeLock(); //写操作 public Object put(String key,String value){ writeLock.lock(); try { System.out.println("Write正在执行写操作~"); Thread.sleep(100); String put = map.put(key, value); System.out.println("Write写操作执行完毕~"); return put; } catch (InterruptedException e) { e.printStackTrace(); }finally { writeLock.unlock(); } return null; } //写操作 public Object get(String key){ readLock.lock(); try { System.out.println("Read正在执行读操作~"); Thread.sleep(100); String value = map.get(key); System.out.println("Read读操作执行完毕~"); return value; } catch (InterruptedException e) { e.printStackTrace(); }finally { readLock.unlock(); } return null; } public static void main(String[] args) { ReadWriteLock lock=new ReadWriteLock(); for (int i = 0; i < 10; i++) { int finalI = i; new Thread(()->{ try { //写操作 lock.put(finalI +"","value"+finalI); //读操作 System.out.println(lock.get(finalI+"")); } catch (Exception e) { e.printStackTrace(); } }).start(); } }
控制台 结果:
我们可以看出是当所有写操作都执行完毕后才开始执行读操作
4. 乐观锁
总认为不会发生并发问题,每一次取数据时总认为其他线程不会对该数据先进性更改,但是在更新时会判断其他线程在这之前有
没有对该数据进行修改,
数据库当中常用方案:版本号控制
5.悲观锁
总是假设最坏的情况,每次取数据时,都会认为其他线程会对该数据进行修改,所以会进行加锁
其他线程访问的时候会阻塞等待,例如在数据库当中可以使用行锁,表锁以及读写锁等方式实现
6. CAS无锁模式
6.1 什么 是CAS
CAS:Compare and Swap,即比较再交换。
jdk5增加了并发包java.util.concurrent.*,其下面的类使用CAS算法实现了区别于synchronouse同步锁的一种乐观锁。
JDK 5之前Java语言是靠synchronized关键字保证同步的,这是一种独占锁,也是是悲观锁。
6.2 Java内存模型:JMM(Java Memory Model)
在内存模型当中定义了一个主内存,所有声明的实例变量都存在于主内存当中,主内存的数据会共享给所有线程,每一个线程有一块工作内存,工作内存当中主内存数据的副本
当更新数据时,会将工作内存中的数据同步到主内存当中
6.2 CAS无锁机制
本身无锁,采用乐观锁的思想,在数据操作时对比数据是否一致,如果一致代表之前没有线程操作该数据,那么就会更新数据,如果不一致代表有县城更新则重试
CAS当中包含三个参数CAS(V,E,N),V标识要更新的变量,E标识预期值,N标识新值
运行过程:
1.线程访问时,先会将主内存中的数据同步到线程的工作内存当中
2.假设线程A和线程B都有对数据进行更改,那么假如线程A先获取到执行权限
3.线程A先会对比工作内存当中的数据和主内存当中的数据是否一致,如果一致(V==E)则进行更新,不一致则刷新数据,重新循环判断
4.这时更新完毕后,线程B也要进行数据更新,主内存数据和工作内存数据做对比,如果一致则进行更新,不一致则将主内存数据重新更新到工作内存,然后循环再次对比两个内存中的数据
直到一致为止
CAS无锁机制存在一个问题
ABA问题,如果将原来A的值改为了B,然后又改回了A,虽然最终结果没有发生改变,但是在过程中是对该数据进行了修改操作
解决该问题:在Java中并发包下有一个原子类:AtomicStampedReference,在该类当中通过版本控制判断值到底是否被修改
解释:如果对值进行了更改则版本号+1,那么在CAS当中不仅仅对比变量的值,还要对比版本号,如果值和版本号都相等则代表没有被修改,如果有一方不相等代表进行过更改
那么就从主内存中重新刷新数据到工作内存然后循环对比,直到成功为止~
7.保证线程安全的三个方面:
1.原子性:保证同一时刻该资源只能有一个线程访问修改,其他线程阻塞等待,例如Atomic包,锁
2.可见性:一个线程对于主内存的数据操作对于其他线程是可见的
3.有序性:一个线程观察其他线程中指令执行顺序,由于指令重排序存在,观察结果一般杂乱无序
原子性: 互斥访问,Atomic包,CAS算法,Synchronized,Lock
可见性:synchronized,volatile
顺序性:happends-before
8. 原子类
编写测试代码:
//定义一个原子类对象 private AtomicInteger atomicInteger=new AtomicInteger(); public void getCount(){ //+1再返回 System.out.println(atomicInteger.incrementAndGet()); } public static void main(String[] args) { AtomicTest atomicTest=new AtomicTest(); for (int i=1;i<=2;i++){ new Thread(()->{ for (int j=1;j<=100;j++){ atomicTest.getCount(); } }).start(); } }
控制台效果:
8.AQS:全成AbstractQueueSynchronizer,抽象队列同步器,这个类在java.util.concurrent.locks包下
它是一个底层同步工具类,比如CountDownLatch,Sammphore,ReentrantLock,ReentrantReadWriteLock等等都是基于AQS
底层三个内容:
1.state(用于计数器)
2.线程标记(哪一个线程加的锁)
3.阻塞队列(用于存放阻塞线程)