多线程高级进阶

多线程

一、volatile

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;
    }
}

1、可见性问题:

上述代码,(用了双重检测)看似加锁线程安全了,但是还存在可见性问题。当启动 2个线程(A和B),
当A线程创建了一个对象,还没有写到主存区域时,B线程读的对象还是null
,所以另外又创建了一个对象(虽然这种情况出现概率很低)

volatile解决方法

一个线程里面修改了共享变量,会强制使其它在使用当前共享变量的线程中的共享变量失效,强制从共享内存从新读取

2、有序性问题:

在创建对象时可能有这几步骤(执行步骤是乱的)

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){
     

                }
            }
        }

    }

三、对象在内存中的布局(关于是否有锁)

3.1、对象头

syn(A){判断有没有锁}
00 空闲,01 使用 { 外面 }

假如为01 ,锁在使用,其他线程只能等待

锁使用完后,状态会改为00,所有线程都可以争取

Mark Word :

默认存储对象的hashcode,分代年龄,锁类型,锁标志位等信息,锁标记

class metadata Address:

指向class的指针,主要是new对象,要找到对象所对应的class的地址

对象锁多的重要参数

​每个对象都有一个Moniter(监视器)对象: 管程  (查看对象的状态)
	{ 当对这个对象反编译的1时候 会出现下面几个重要参数 }

1、​entryList:当前抢占这个对象锁定的线程集合

2、owner : 持有moniter线程

3、​_count : 持有count+1,释放就count-1

4、waitset : d调用wait方法  (存放等待的线程,在这个线程启动时就在等待的位置继续运行)



四、自旋锁

2.1、自旋概念:

概念引用

总结

当竞争的线程运行的时间比较短时可以用自旋锁

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锁

  • lock():获得锁,如果锁被占用则等待。

  • lockInterruptibly():获得锁,但优先响应中断。(留着)

  • tryLock():尝试获得锁,如果成功,则返回true;失败返回false。此方法不等待,立即返回。

  • tryLock(long time,TimeUnit):在上面的方法上加上时间

  • unlock():释放锁

  • Condition newCondition()

6.1、ReentrantLoc(实现Lock锁)

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();
    }

·

·

6.2、读写锁 ReadWriteLock(实现Lock锁)

写的时候和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(信号量)

信号量,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();
        }
    }
}

八、CountDownLatch

规定多少个线程同时完成才走下一步

引用 蜗牛爱上星星大佬博客

你可能感兴趣的:(java基础,并发编程,java,多线程)