java多线程学习(2)-锁的使用

简介

上篇已经对锁的属性做了一个简单的介绍,此篇主要针对于不同锁的使用,分析优缺点,方便以后使用锁的时候能选择合适的锁。引入

准备知识

AQS

在说怎么使用锁之前我想先说下AQS(AbstractQueuedSynchronized),基本上很多同步类都依赖它,AQS维护了一个volatile int state(共享资源的被占用个数)和FIFO一个拥挤堵塞队列

AQS

CAS

CAS(compare and swap 比较并交换)是乐观锁技术,当多个线程尝试操作同一个共享资源的同时,只有一个线程能够成功,其他线程不会堵塞,而是直接失败,可重复尝试。

工作原理,CAS操作包括三个值,内存位置V,预期原值A,更新值B,线程并发更新共享资源时,会先比较V位置和A的值,如果一样,处理器则将V的值更新成B,不一样处理器则不做任何操作。

可以看下java.util.concurrent包中的AtomicIntege类,看下在不使用锁的情况下是怎么保证线程安全的,以下非标准源码,按照原理写的简易版本

public class AtomicInteger extends Number implements java.io.Serializable {
    //需要修改的值v
    public volatile int v;
    
    //获取v的值
    public final int get(){
        return v;
    }
    
    //i为新值,var2是实例域的偏移量
    public final int getAndIncrement(int i,long var2){
        int resultV;
        do{
            resultV = get();
        }while(!unsafe.compareAndSwapInt(this, var2, resultV, i))
        return resultV;
    }
    
    
}

实战

下面将分别演示synchronized、ReentrantLock3种锁的使用

synchronized

synchronized代码
public class SimpleSynchronized implements Runnable {


    public synchronized void methodOne(){
        System.out.println("methodOne:"+Thread.currentThread().getName());
        methodTwo();
        System.out.println("will leave:"+Thread.currentThread().getName());
    }

    public synchronized void methodTwo(){
        System.out.println("methodTwo:"+Thread.currentThread().getName());
    }


    @Override
    public void run() {
        methodOne();
    }
}
main函数
public class Main {

    public static void main(String[] args) {

        SimpleSynchronized simpleSynchronized = new SimpleSynchronized();
        for (int i= 0 ;i < 5;i++){
            new Thread(simpleSynchronized,"thread-"+(i+1)).start();
        }

    }
}
控制台输出
methodOne:thread-1
methodTwo:thread-1
will leave:thread-1
methodOne:thread-5
methodTwo:thread-5
will leave:thread-5
methodOne:thread-4
methodTwo:thread-4
will leave:thread-4
methodOne:thread-3
methodTwo:thread-3
will leave:thread-3
methodOne:thread-2
methodTwo:thread-2
will leave:thread-2
  • 从控制台输出可以看出,methodOne进入了methodTwo,说明synchronized是可重入锁;
  • 然后thread1结束之后是5紧接着进入methodOne,说明synchronized不是公平锁;
  • 当一个线程没有结束时,下一个线程是无法进入方法的,说明synchronized是独占锁。

ReentrantLock

实现代码
public class SimpleReentrantLock implements Runnable{
    private ReentrantLock reentrantLock = new ReentrantLock();
    public void methodOne(){
        System.out.println("enter methodOne"+Thread.currentThread().getName());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        reentrantLock.lock();
        System.out.println("methodOne:"+Thread.currentThread().getName());
        methodTwo();

        System.out.println("will unlock:"+Thread.currentThread().getName());
        reentrantLock.unlock();
        System.out.println("after unlock:"+Thread.currentThread().getName());
    }

    public void methodTwo(){
        System.out.println("enter methodTwo"+Thread.currentThread().getName());
        reentrantLock.lock();
        System.out.println("methodTwo:"+Thread.currentThread().getName());
        reentrantLock.unlock();
        System.out.println("after unlock:"+Thread.currentThread().getName());
    }


    @Override
    public void run() {
        methodOne();
    }

}

执行结果较长,不作粘贴了,从结果能看出,和synchronized差不多,是重入锁;

不过reentrantLock是即可构造公平锁,也可构造非公平锁的,默认为非公平锁,构造公平锁只需要在构造方法中传入true

 ReentrantLock reentrantLock = new ReentrantLock(true);

ReentrantReadWriteLock

此锁能获取两种类型的锁,读锁和写锁,读锁是共享锁,写锁是排他锁,读读共享,读写互斥,此锁也可以构造公平与非公平锁

我们将上面的代码改造使用ReentrantReadWriteLock

public class SimpleReentrantReadWriteLock implements Runnable {
    static List<String> list = new ArrayList<>();
    static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    static Lock r = rwl.readLock();
    static Lock w = rwl.writeLock();
    private int var1;
    private String var2;

    public SimpleReentrantReadWriteLock(int var1,String var2) {
        this.var1 = var1;
        this.var2 = var2;
    }

    @Override
    public void run() {
        switch (var1){
            case 1:
                read();
                break;
            case 2:
                write(var2);
                break;
            case 3:
                clearArg();
                break;
            default:
                read();
                break;
        }

    }

    public void read(){

        try {
            r.lock();
            System.out.println("进入读函数了:"+Thread.currentThread().getName());
            if (list.size()>0)
            System.out.println("list有值了:"+list);
        }catch (Exception e){

        }finally {
            r.unlock();
        }
    }

    public void write(String value){
        w.lock();
        try {
            System.out.println("进入写函数了:" + Thread.currentThread().getName());
            list.add(value);
            r.lock();
            w.unlock();
            //释放写锁,获取读锁
            Thread.sleep(2000);
            System.out.println("释放写锁");
        }catch (Exception e){
            System.out.println(e);
        }finally {
            r.unlock();
        }
    }

    public void clearArg(){
        w.lock();
        System.out.println("清空队列:"+Thread.currentThread().getName());
        list.clear();
        w.unlock();
    }
}

main函数

public class Main {


    public static void main(String[] args) {

        SimpleReentrantReadWriteLock slock1 = new SimpleReentrantReadWriteLock(1,null);
        SimpleReentrantReadWriteLock slock2 = new SimpleReentrantReadWriteLock(2,"a");
        SimpleReentrantReadWriteLock slock3 = new SimpleReentrantReadWriteLock(3,null);
        for (int i= 0 ;i < 10;i++){
            new Thread(slock1,"slock1-thread-"+(i+1)).start();
            new Thread(slock2,"slock2-thread-"+(i+1)).start();
            new Thread(slock3,"slock3-thread-"+(i+1)).start();

        }

    }
}

从此代码的运行结果我们可以发现:

  • 写锁是能降级成为读锁的
  • 支持重入锁
  • 支持公平与非公平锁

下面将准备线程池方面的知识,yeah!!!!

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

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

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏difcareer的技术笔记

JNI实现源码分析【四 函数调用】正文0x01:dvmCallMethodV0x02:nativeFunc0x03: 何时赋值

有了前面的铺垫,终于可以说说虚拟机是如何调用JNI方法的了。JNI方法,对应Java中的native方法,所以我们跟踪对Native方法的处理即可。

10740
来自专栏Java编程技术

ClassLoader解惑

一个Java程序要想运行起来,首先需要经过编译生成 .class文件,然后创建一个运行环境(jvm)来加载字节码文件到内存运行,而.class 文件是怎样被加载...

16110
来自专栏逍遥剑客的游戏开发

C++的反射和序列化

17720
来自专栏C语言及其他语言

[每日一题]C语言程序设计教程(第三版)课后习题7.1

题目描述 用简单素数筛选法求N以内的素数。 输入 N 输出 2~N的素数 样例输入 100 样例输出 2 3 5 7 11 13 17 19 23 29 31 ...

46750
来自专栏Java面试通关手册

Java多线程学习(二)synchronized关键字(2)

Java面试通关手册(Java学习指南,欢迎Star,会一直完善下去,欢迎建议和指导):https://github.com/Snailclimb/Java_G...

22960
来自专栏哲学驱动设计

Linq to xml API中 XName 的奇怪实现

    最近,在使用LinqToXml的时候,使用到其中一个重要的类:XName。它表示一个XML元素/XML属性的“名字”。    System.Xml.Li...

20270
来自专栏君赏技术博客

Object-C中的黑魔法

在Swift中存在Option类型,也就是使用?和!声明的变量。但是OC里面没有这个特征,因为在XCODE6.3之后出现新的关键词定义用于OC转SWIFT时候可...

19310
来自专栏日常分享

Java 循环队列的实现

  队列(Queue)是限定只能在一端插入、另一端删除的线性表。允许删除的一端叫做队头(front),允许插入的一端叫做队尾(rear),没有元素的队列称为“空...

25130
来自专栏IMWeb前端团队

ES6学习之函数传参

本文作者:IMWeb Terrance 原文出处:IMWeb社区 未经同意,禁止转载 ECMAScript 6 (or ECMAScript 201...

255100
来自专栏岑玉海

Hbase 学习(三)Coprocessors

Coprocessors 之前我们的filter都是在客户端定义,然后传到服务端去执行的,这个Coprocessors是在服务端定义,在客户端调用,然后在服...

425110

扫码关注云+社区

领取腾讯云代金券