jmm存储三大特性
可见性,有序性,原子性
volatile 解决了可见性和有序性
package com.thread.study.volatiles;
/**
* @ClassName
* @Description TODO
* @Author ZQS
* @Date 2020/9/8 0008 16:22
* @Version 1.0
**/
public class Singleton {
private static Singleton singleton;
private Singleton(){
}
public static Singleton getInstance(){
if(singleton==null) {
synchronized (Singleton.class) {
if(singleton==null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
上述代码,(用了双重检测)看似加锁线程安全了,但是还存在可见性问题。当启动 2个线程(A和B),
当A线程创建了一个对象,还没有写到主存区域时,B线程读的对象还是null
,所以另外又创建了一个对象(虽然这种情况出现概率很低)
volatile解决方法
一个线程里面修改了共享变量,会强制使其它在使用当前共享变量的线程中的共享变量失效,强制从共享内存从新读取
在创建对象时可能有这几步骤(执行步骤是乱的)
1.在堆中分配一块内存 new Singleton并且赋予一个地址值 0x001
2.将地址值0x001给singleton
3.初始化这个类
当A线程执行了1.2 步骤后B线程 在外面得到这个对象,但是对象还没有初始化
volatile解决方法
volatile指定了顺序,1->3->2(大致这样) 防止指令重排序
对于原子性只能通过加锁
是指一个操作是不可中断的。即使是多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。
所以只能加锁,防止其他线程同时操作
问题:
主要是使用了重入锁(锁套锁)
线程之间互相等待对方释放锁,就是死锁
解决方法:编号解锁
private MyLock myLock1 = new MyLock(1);
private MyLock myLock2 = new MyLock(2);
public void test1(){
//一个地方 A在前B在后 另外一个地方B在前 A在后
//现在给锁编号 那么永远小的在前
if(myLock1.getI()<myLock2.getI()){
synchronized (myLock1){
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (myLock2){
}
}
}else {
synchronized (myLock2){
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (myLock1){
}
}
}
}
syn(A){判断有没有锁}
00 空闲,01 使用 { 外面 }
假如为01 ,锁在使用,其他线程只能等待
锁使用完后,状态会改为00,所有线程都可以争取
默认存储对象的hashcode,分代年龄,锁类型,锁标志位等信息,锁标记
指向class的指针,主要是new对象,要找到对象所对应的class的地址
对象锁多的重要参数
每个对象都有一个Moniter(监视器)对象: 管程 (查看对象的状态)
{ 当对这个对象反编译的1时候 会出现下面几个重要参数 }
1、entryList:当前抢占这个对象锁定的线程集合
2、owner : 持有moniter线程
3、_count : 持有count+1,释放就count-1
4、waitset : d调用wait方法 (存放等待的线程,在这个线程启动时就在等待的位置继续运行)
概念引用
总结
当竞争的线程运行的时间比较短时可以用自旋锁
jdk1.6优化:
自适应自旋锁:自旋次数不固定,由上一次自旋时间与拥有状态决定,很少获得锁,就减少自旋时间,很多跟多锁就增加自旋时间
锁消除: jvm会自动去掉没有共享资源线程安全的代码加的锁
eg: 使用 Stringbuffer
锁粗化: 循环一百次stringbuffer会自动加大范围,避免反复加锁解锁
无锁------>偏向锁(偏向某一个线程)------>轻量级锁((交替执行)---->重量级锁(竞争非常激烈)
锁膨胀: 主要是锁从轻到重的过程
锁降级: 主要是锁从重到轻的过程
偏向锁:总是同一个线程多次获得锁,会改变mark work进入偏向模式,下次获取该锁的时候不需要做同步操作,只需要检查markword的锁标记位及id,就可以减去获得锁的很多操作,进入cas(适用于锁竞争不激烈)
轻量级锁:线程交替执行同步块,cas将对象mark word 01指针指向lock record(锁记录,markword的拷贝),并将lock record的owner指针指向obj的mark word 00
重量级锁:直接互斥,进入阻塞状态
更新失败:1.已经获得2.多个线程竞争进入重量级
解锁:替换主存的markword,因为请求了才会失败已经进入重量级锁,失败唤醒被挂起的线程
·
·
lock():获得锁,如果锁被占用则等待。
lockInterruptibly():获得锁,但优先响应中断。(留着)
tryLock():尝试获得锁,如果成功,则返回true;失败返回false。此方法不等待,立即返回。
tryLock(long time,TimeUnit):在上面的方法上加上时间
unlock():释放锁
Condition newCondition()
getHoldCount():当前线程调用 lock() 方法的次数
getQueueLength():当前正在等待获取 Lock 锁的线程的估计数
getWaitQueueLength(Condition condition):当前正在等待状态的线程的估计数,需要传入 Condition 对象
hasWaiters(Condition condition):查询是否有线程正在等待与 Lock 锁有关的 Condition 条件
hasQueuedThread(Thread thread):查询指定的线程是否正在等待获取 Lock 锁
hasQueuedThreads():查询是否有线程正在等待获取此锁定
isFair():判断当前 Lock 锁是不是公平锁
isHeldByCurrentThread():查询当前线程是否保持此锁定
isLocked():查询此锁定是否由任意线程保持
tryLock():线程尝试获取锁,如果获取成功,则返回 true,如果获取失败,则返回 false
tryLock(long timeout,TimeUnit unit):线程如果在指定等待时间内获得了锁,就返回true,否则返回 false
lockInterruptibly():如果当前线程未被中断,则获取该锁定,如果已经被中断则出现异常
公平锁:即等待时间越久的越先获取许可(锁)
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
·
·
写的时候和lock一样(悲观锁)
读的时候是一起读的,数据共享(乐观锁)
悲观锁和乐观锁—》引用 JavaGuide大佬
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockTest implements Runnable{
private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
@Override
public void run() {
//select * from product where id=1 for update
//写锁达到的效果 跟lock一样的
// ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();
//writeLock.lock();
ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
readLock.lock();
System.out.println(Thread.currentThread().getName()+"获得锁");
try {
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName()+"xxxxxxx");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
readLock.unlock();
}
}
public static void main(String[] args) {
ReadWriteLockTest readWriteLockTest = new ReadWriteLockTest();
new Thread(readWriteLockTest).start();
new Thread(readWriteLockTest).start();
}
}
信号量,Semaphore可以控同时访问的线程个数,可用做限流
常见方法:
public Semaphore(int permits) {
//参数permits表示许可数目,即同时可以允许多少线程进行访问
sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) {
//这个多了一个参数fair表示是否是公平的,即等待时间越久的越先获取许可
sync = (fair)? new FairSync(permits) : new NonfairSync(permits);
}
public void acquire() throws InterruptedException {
} //获取一个许可
public void acquire(int permits) throws InterruptedException {
} //获取permits个许可
public void release() {
} //释放一个许可
public void release(int permits) {
} //释放permits个许可
这4个方法都会被阻塞,如果想立即得到执行结果,可以使用下面几个方法:
public boolean tryAcquire() {
}; //尝试获取一个许可,若获取成功,则立即返回true,若获取失败,则立即返回false
public boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException {
}; //尝试获取一个许可,若在指定的时间内获取成功,则立即返回true,否则则立即返回false
public boolean tryAcquire(int permits) {
}; //尝试获取permits个许可,若获取成功,则立即返回true,若获取失败,则立即返回false
public boolean tryAcquire(int permits, long timeout, TimeUnit unit) throws InterruptedException {
}; //尝试获取permits个许可,若在指定的时间内获取成功,则立即返回true,否则则立即返回false
·
·
使用案例
package com.thread.study.semaphore;
import java.util.concurrent.Semaphore;
/**
* @ClassName
* @Description TODO
* @Author ZQS
* @Date 2020/9/8 0008 20:15
* @Version 1.0
**/
public class SemaphoreTest implements Runnable{
Semaphore semaphore = new Semaphore(5);
@Override
public void run() {
if(semaphore.tryAcquire()) {
try {
//能获得许可 获得执行的资格
//令牌桶
//semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "获得执行的资格");
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}
else {
System.out.println("没有获得许可》》》");
}
}
public static void main(String[] args) {
SemaphoreTest semaphoreTest = new SemaphoreTest();
for (int i = 0; i < 20; i++) {
new Thread(semaphoreTest).start();
}
}
}
规定多少个线程同时完成才走下一步
引用 蜗牛爱上星星大佬博客