前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >线程的锁

线程的锁

作者头像
晚上没宵夜
发布2020-03-10 09:24:56
3750
发布2020-03-10 09:24:56
举报

1. 多线程可能出现的安全问题

代码语言:javascript
复制
public class Synchronizedtest implements Runnable {

    int i = 10;     //共享变量

    @Override
    public void run() {
        if( i == 10 ){
            System.out.println("i == 10");
            sys
            i++;
        }
    }
    
    public static void main(String[] args) throws InterruptedException {
        
        Synchronizedtest st = new Synchronizedtest();
        Thread t1 = new Thread(st,"线程1");
        Thread t2 = new Thread(st,"线程2");
        
        t1.start();
        // t1.sleep(1000);  第一次测试先注释掉,第二次测试打开,下面为两次测试结果
        t2.start();
    }
}
代码语言:javascript
复制
i == 10
i == 10
代码语言:javascript
复制
i == 10

问题分析:

  • i++ 这个操作是非原子性的,分为三步:
    • 读取 i 的值
    • 将读取的数值 +1
    • 将数值写回 i
  • 线程t1,读取了i 值为10,在把值写回 i (i++ = 11) 之前,线程t2就读取了 i 的值,此时t1并未修改 i 的值,所以 i 还是等于10
  • 因此二者判断 i 都是等于10,即都会输出内容
  • 第二次测试执行到t1.start()时先 “暂停” 1秒,t2线程还不开启,t1在1秒内绝对执行完之后才开启t2线程,这样 i 的值已经更新为11了,此时t2就不输出内容
  • 如果不把变量放在成员变量上,而是放在方法内,这样就不会共享了,因为方法是栈操作,独享空间

2. 解决方法

  • 不设置共享变量,放入方法体内成为栈的独享空间
  • 用final修饰基础变量,但引用变量用final修饰还是不行(指向不可变,但内容可变)
  • 加锁(内置锁,显示Lock锁),后面会有说明
  • 使用安全类
    • 原子性:Atomic包
    • 容器:ConcurrentHashMap
    • locks包

3. 准备知识点及关键字

  • 原子性:执行多个操作,其中一个操作没执行的话,全部操作也不执行;否则全部执行
  • 内存屏障:CPU有缓存,如果数据在缓存上,不能实时和内存发生信息交换,分在不同CPU执行的不同线程对同一个变量的缓存值不同
  • 有序性:代码的执行顺序按照代码的先后顺序执行,不用考虑重排序(指令执行前JVM会优化且重排序)
  • 可见性:一个线程对共享变量的修改,另一个线程能立刻看到
    • volatile:轻量级的同步机制,只修饰类变量和实例变量,仅保证可见性,不保证原子性,保证有序性,某变量被修改后所有进程知道该变量被修改,但如果重新赋值,这个还是非原子性分三步走(一旦完成写入操作,所有进程都会得到最新值)

4 锁

4.1 synchronized内置锁

  • 它是java的关键字,可以修饰方法,代码块,类
  • synchronized锁一次只能允许一个线程进入被锁住的代码块,java每个对象都有内置锁 / 监视器锁,synchronized就是使用对象的内置锁来锁定的
  • 保证锁内的原子性和可见性

4.1.1 方法锁

代码语言:javascript
复制
public class Synchronizedtest implements Runnable {
    
    //使用的是该类的锁
    @Override
    public synchronized void run() {
        for(int i = 0;i < 100;i++){
            System.out.println(Thread.currentThread().getName() + "------" + i);
        }
    }
    
    public static void main(String[] args) {
        
        Synchronizedtest st = new Synchronizedtest();
        Thread t1 = new Thread(st,"线程1");
        Thread t2 = new Thread(st,"线程2");
        
        t1.start();
        t2.start();
    }
}
代码语言:javascript
复制
线程1------96
线程1------97
线程1------98
线程1------99    //获得锁,执行完才释放,t2线程不能执行该方法
线程2------0
线程2------1
线程2------2
线程2------3
线程2------4

4.1.2 代码块锁

代码语言:javascript
复制
public void run() {

    //使用的也是该类的锁,打印结果是一致的
    //也可以用一个对象作为锁,客户端锁,但不推荐
    synchronized(this){
        for(int i = 0;i < 100;i++){
            System.out.println(Thread.currentThread().getName() + "------" + i);
        }
    }
}

4.1.3 静态锁

代码语言:javascript
复制
public class test {

    //静态方法属于类,获取到的锁是属于类锁(类的字节码文件对象)
    public static synchronized void test() {
    }
}

4.1.4 类锁与对象锁

二者不会冲突,即即可获得对象锁,也可获得类锁

代码语言:javascript
复制
public class testLock {

    //对象锁
    public synchronized void lockOne() throws InterruptedException {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
        }
    }
    
    //类锁
    public static synchronized void lockTwo() throws InterruptedException {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
        }
    }

    public static void main(String[] args) {
        testLock demo = new testLock();

       
        Thread t1 = new Thread(() -> {
            try {
                demo.lockOne();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        });
        Thread t2 = new Thread(() -> {
            try {
                lockTwo();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        });
        
        t1.start();
        t2.start();
    }
}
代码语言:javascript
复制
//两个线程都执行
Thread-1: 99
Thread-0: 35

4.1.5 内置锁的可重入性

代码语言:javascript
复制
public class Widget {

    // 锁住了
    public synchronized void doSomething() {
        System.out.println("Wigget--------------");
    }
}

public class LoggingWidget extends Widget {

    // 锁住了
    public synchronized void doSomething() {
        System.out.println("LoggingWidget------------");
        super.doSomething();
    }
}
  • 能运行,不会死锁
  • 线程运行LoggingWidget的dosomething()方法时,获得LoggingWidget的对象实例锁
  • 当调用super.doSomething();时,调用者还是LoggingWidget,再次获取LoggingWidget的对象实例锁,再次锁,即锁的重入
  • 上面的锁是在实例对象上的,不是类上的,锁都是同一个,但不是获得多把锁(每个锁有个关联对象和计数器,当某一线程请求锁成功后,JVM记下锁的持有线程,并且将计数器置为1;此时其它线程请求该锁,则必须等待;而如果同一个线程再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增;当线程退出同步代码块时,计数器会递减,如果计数器为0,则释放该锁)

4.2 Lock显式锁

4.2.1 AQS

在JUC并发包下有locks接口,里面有三个抽象类

其中,AbstractQueuedSynchronizer为AQS,而且我们后面要讲的Lock显式锁的内部类(ReentrantLock、 ReadWriteLock)都是他的子类,根据名字可以知道他是抽象队列同步器,AQS是ReentrantReadWriteLock和ReentrantLock的基础,因为默认的实现都是在内部类Syn中,而Syn继承了AQS

根据其注释可知:

  • AQS是包装好的实现锁的抽象同步器
  • 依靠原子int表示状态(获取,释放)
  • 内部维护队列,没有获取锁就排队
  • 有重要的内部类ConditionObject,子类一般使用内部类来实现同步操作
  • 有两种线程模式
    • 独占
    • 共享

4.2.2 准备知识点

  • CAS:compare and swap(比较与交换),不使用锁来实现多线程之间的变量同步
    • 涉及三个数:内存值V,比较值A,新值B
    • 当且仅当内存地址V的值与比较值A相等时,将内存地址V的值修改为B,否则就什么都不做。整个比较并替换的操作是一个原子操作
  • 公平锁:线程按它们发出请求锁的顺序来获取锁

4.2.3 状态

volatile保证状态可见性

代码语言:javascript
复制
/**
 * The synchronization state.
 */
private volatile int state;

CAS算法保证原子性

代码语言:javascript
复制
protected final boolean compareAndSetState(int expect, int update) {
    // See below for intrinsics setup to support this
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

4.2.4 acquire 获取独占锁

acquire(int)尝试获取资源,如果获取失败,将线程插入等待队列。插入等待队列后,acquire(int)并没有放弃获取资源,而是根据前置节点状态状态判断是否应该继续获取资源,如果前置节点是头结点,继续尝试获取资源,如果前置节点是SIGNAL状态,就中断当前线程,否则继续尝试获取资源。直到当前线程被park()或者获取到资源,acquire(int)结束。

代码语言:javascript
复制
public final void acquire(int arg) {
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

//子类实现的类,模板模式

4.2.5 release 释放独占锁

首先调用子类的tryRelease()方法释放锁,然后唤醒后继节点,在唤醒的过程中,需要判断后继节点是否满足情况,如果后继节点不为且不是作废状态,则唤醒这个后继节点,否则从tail节点向前寻找合适的节点,如果找到,则唤醒

代码语言:javascript
复制
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

4.2.6 Lock显式锁

  • 使用时最标准用法是在try之前调用lock方法,在finally代码块释放锁
  • 主要说明两个常见子类:
    • ReentrantLock
    • ReentrantReadWriteLock

426.1 ReentrantLock

构造方法

代码语言:javascript
复制
public ReentrantLock() {
        sync = new NonfairSync();
}
//默认非公平锁


//也支持公平锁
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

lock方法

代码语言:javascript
复制
/**
 * Performs lock.  Try immediate barge, backing up to normal
 * acquire on failure.
 */
final void lock() {
    if (compareAndSetState(0, 1)) //CAS保证
        setExclusiveOwnerThread(Thread.currentThread()); //尝试获取锁
    else
        acquire(1); //失败就调用AQS的acquire方法
}
代码语言:javascript
复制
/**
 * Performs non-fair tryLock.  tryAcquire is implemented in
 * subclasses, but both need nonfair try for trylock method.
 */
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {  //如果锁是空闲的
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }else if (current == getExclusiveOwnerThread()) { //重入锁
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

unlock

代码语言:javascript
复制
public void unlock() {
    sync.release(1);
}
代码语言:javascript
复制
public final boolean release(int arg) {
    if (tryRelease(arg)) {   //子类实现 
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}
代码语言:javascript
复制
protected final boolean tryRelease(int releases) {
    int c = getState() - releases;  //计数器减 1
    if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {  //莫得锁了
        free = true;
        setExclusiveOwnerThread(null);   //锁线程设置为空
    }
    setState(c);
    return free;
}

426.2 ReentrantReadWriteLock

ReentrantReadWriteLock读写锁:

  • 允许多个线程同时进入临界区读取数据
  • 写操作互斥读和写
  • 在读取多,写入少的情况就发挥作用了

读写锁有个接口:ReadWriteLock,定义了两个方法:readLcok(),writeLock(),而ReentrantReadWriteLock实现了该接口

获取写锁

代码语言:javascript
复制
protected final boolean tryAcquire(int acquires) {
/*
 * Walkthrough:
 * 1. If read count nonzero or write count nonzero
 *    and owner is a different thread, fail.
 * 2. If count would saturate, fail. (This can only
 *    happen if count is already nonzero.)
 * 3. Otherwise, this thread is eligible for lock if
 *    it is either a reentrant acquire or
 *    queue policy allows it. If so, update state
 *    and set owner.
 */
    Thread current = Thread.currentThread();
    int c = getState();  //状态
    int w = exclusiveCount(c);
    if (c != 0) {
        // (Note: if c != 0 and w == 0 then shared count != 0)
        if (w == 0 || current != getExclusiveOwnerThread()) //不是当前线程
            return false;
        if (w + exclusiveCount(acquires) > MAX_COUNT)   //大于饱和
            throw new Error("Maximum lock count exceeded");
        // Reentrant acquire
        setState(c + acquires);
        return true;
    }
    if (writerShouldBlock() || !compareAndSetState(c, c + acquires))
        return false;
    setExclusiveOwnerThread(current);
    return true;
}

获取读锁

代码语言:javascript
复制
protected final int tryAcquireShared(int unused) {
    Thread current = Thread.currentThread();
    int c = getState();
    if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current)  //存在写锁,但不在本线程
        return -1;
    int r = sharedCount(c);
    if (!readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)){
        if (r == 0) {
            firstReader = current;
            firstReaderHoldCount = 1;
        }else if (firstReader == current) {
            firstReaderHoldCount++;
        }else {
            HoldCounter rh = cachedHoldCounter;
            if (rh == null || rh.tid != getThreadId(current))
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0)
                readHolds.set(rh);
            rh.count++;
        }
        return 1;
    }
    return fullTryAcquireShared(current);
}

4.2.7 使用

  • ReentrantLock
代码语言:javascript
复制
public class Counter {
    private final Lock lock = new ReentrantLock();
    private int count;

    public void add(int n) {
        lock.lock();
        try {
            count += n;
        } finally {
            lock.unlock();
        }
    }
}
  • ReentrantReadWriteLock
代码语言:javascript
复制
public class Counter {
    private final ReadWriteLock rwlock = new ReentrantReadWriteLock();
    private final Lock rlock = rwlock.readLock();  //获取读锁
    private final Lock wlock = rwlock.writeLock(); //获取写锁
    private int[] counts = new int[10];

    public void inc(int index) {
        wlock.lock(); // 加写锁
        try {
            counts[index] += 1;
        } finally {
            wlock.unlock(); // 释放写锁
        }
    }

    public int[] get() {
        rlock.lock(); // 加读锁
        try {
            return Arrays.copyOf(counts, counts.length);
        } finally {
            rlock.unlock(); // 释放读锁
        }
    }
}
  • condition
代码语言:javascript
复制
class conditionTest {
    private final Lock lock = new ReentrantLock();
    private final Condition condition = lock.newCondition();  //一定要这样获取绑定条件

    public void test1() {
        lock.lock();
        try {
            //dosomenthing
            condition.signalAll();
        } finally {
            lock.unlock();
        }
    }

    public void test2() {
        lock.lock();
        try {
            //dosomething
            condition.await();
        } finally {
            lock.unlock();
        }
    }
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-01-23 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 多线程可能出现的安全问题
  • 2. 解决方法
  • 3. 准备知识点及关键字
  • 4 锁
    • 4.1 synchronized内置锁
      • 4.1.1 方法锁
      • 4.1.2 代码块锁
      • 4.1.3 静态锁
      • 4.1.4 类锁与对象锁
      • 4.1.5 内置锁的可重入性
    • 4.2 Lock显式锁
      • 4.2.1 AQS
      • 4.2.2 准备知识点
      • 4.2.3 状态
      • 4.2.4 acquire 获取独占锁
      • 4.2.5 release 释放独占锁
      • 4.2.6 Lock显式锁
      • 426.1 ReentrantLock
      • 426.2 ReentrantReadWriteLock
      • 4.2.7 使用
相关产品与服务
日志服务
日志服务(Cloud Log Service,CLS)是腾讯云提供的一站式日志服务平台,提供了从日志采集、日志存储到日志检索,图表分析、监控告警、日志投递等多项服务,协助用户通过日志来解决业务运维、服务监控、日志审计等场景问题。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档