Java并发控制 学习笔记1

文章介绍了并发控制的三种主要方法:悲观锁、乐观锁和自旋锁。悲观锁适用于写操作频繁或长时间事务,而读操作多时适合乐观锁。自旋锁在事务执行时间短的情况下较为适用。文章通过代码示例展示了如何使用synchronized关键字、Lock类以及AtomicInteger实现这三种锁机制,并提到了使用volatile保证可见性的重要性。
摘要由CSDN通过智能技术生成

一、并发控制的方法

1、悲观锁:常用的互斥锁都属于悲观锁,一个线程访问共享资源时其他线程不能访问。

2、乐观锁:允许同时访问共享数据,只有在提交时利用如版本号检查是否有冲突,应用github。

3、什么时候用乐观锁、什么时候用悲观锁?

写操作比较多的时候悲观锁,读操作多用乐观锁。再就是一个事务执行时间很长时用乐观锁,冲突很多时考虑悲观锁。

二、什么是自旋锁?

正常的锁当一个线程拿不到共享资源时,会把该线程阻塞,等共享资源释放后,再唤醒该阻塞线程并进行调度;

自旋锁当一个线程拿不到共享资源时,它就一直while循环来询问该资源释放了没有,会一直占用cpu资源。

适用情况:如果大多事务的执行时间很短,自旋锁空转一会就能拿到资源,就用自旋锁,这时如果采用阻塞/唤醒思路的话,调度的时间消耗会比较大。

三、代码实现:

例1(leetcode1114按序打印):

方案1(synchronized关键字):

        既可以实现自旋锁,也可以实现阻塞/唤醒思路,但要注意自旋锁时很容易死锁,所以synchronized包裹哪一部分代码考虑清楚,同时若出现“不可见”问题,考虑适用volatile关键字。

代码(自旋锁):

class Foo {

    private volatile Integer flag = 0;

    public Foo() {
        
    }

    public void first(Runnable printFirst) throws InterruptedException {
        
// 1、正常synchronized解决了“不可见问题”,
// 但因为这里flag!=0在synchronized代码块外边,
// 所以这里的flag用的是“线程存储空间中的值”,
// 不是主存中的最新值,所以使用volatile来定义flag变量;

// 2、另外synchronized(this)为什么不是synchronized(flag),
// 因为Integer flag是引用类型,改变flag的值,flag指向的存储空间就变了,
// 作为共享资源的变量存储空间是一直不能变的。
        while (flag !=0) {} 
        synchronized(this) {
            printFirst.run();
            flag = 1;
        }
        
    }

    public void second(Runnable printSecond) throws InterruptedException {
        
            
        while (flag != 1) {}
        synchronized(this) {
            System.out.println(".." + flag);
            printSecond.run();
            flag = 2;
        }
        
    }

    public void third(Runnable printThird) throws InterruptedException {
        
        while (flag != 2);
        synchronized(this) {
            System.out.println(flag);
            flag = 0;
            printThird.run();
        }

    }
}


///
synchronized少包裹一些代码也行,如下:

class Foo {

    private volatile Integer flag = 0;

    public Foo() {
        
    }

    public void first(Runnable printFirst) throws InterruptedException {
        
        
        while (flag !=0) {}
        
        printFirst.run();
        synchronized(this) {
            flag = 1;
        }
        
    }

    public void second(Runnable printSecond) throws InterruptedException {
        
            
        while (flag != 1) {}

        printSecond.run();
        synchronized(this) {
            flag = 2;
        }
        
    }

    public void third(Runnable printThird) throws InterruptedException {
        
        while (flag != 2);
        printThird.run();
        synchronized(this) {
            flag = 0;
        }

    }
}

代码(阻塞/唤醒思路):

class Foo {

    private Integer flag = 0;

    public Foo() {
        
    }

    public void first(Runnable printFirst) throws InterruptedException {
        // 可以用this,也可以另外定义一个一定不会被修改的Object,
        // 但不能用flag,原因如上代码。
        synchronized(this) {
            while (flag !=0) {this.wait();}
            printFirst.run();
            flag = 1;
            this.notifyAll();
        }
        
    }

    public void second(Runnable printSecond) throws InterruptedException {
        synchronized(this) {
            
            while (flag != 1) {this.wait();}
            printSecond.run();
            flag = 2;
            this.notifyAll();
        }
        
    }

    public void third(Runnable printThird) throws InterruptedException {
        synchronized(this) {
            while (flag != 2) this.wait();
            flag = 0;
            printThird.run();
            this.notifyAll();
        }

    }
}

方案2(Lock类):和synchronized关键字差不多,只不过是一个封装好的类,还是得借助volatile关键字实现解决可见性,下面代码是自旋锁思路,也可以用条件锁Condition类实现阻塞思路,如文章最后的链接中的方法(synchronized配合wait等方法  == Lock配合Condition,只不过前者更底层);

class Foo {

    private volatile Integer flag;
    private Lock lock = new ReentrantLock();

    public Foo() {
        flag = 0;
    }

    public void first(Runnable printFirst) throws InterruptedException {
        
        while (flag != 0) {}
        lock.lock();
        printFirst.run();
        flag = 1;
        lock.unlock();
    }

    public void second(Runnable printSecond) throws InterruptedException {
        while (flag != 1);   
        lock.lock();     
        printSecond.run();
        flag = 2;
        lock.unlock();
    }

    public void third(Runnable printThird) throws InterruptedException {
        while (flag != 2);
        lock.lock();
        printThird.run();
        flag = 0;
        lock.unlock();
    }
}

方案3(原子类AtomicInteger类):

也是既可以实现自旋锁,也可以实现阻塞唤醒,AtomicInteger不用=、!=、+等运算符,要调用get()方法、set()方法、incrementAndGet()等方法。

自旋锁代码:

class Foo {
    
    private AtomicInteger flag = new AtomicInteger(0);

    public Foo() {
        System.out.println(flag);
    }

    public void first(Runnable printFirst) throws InterruptedException {
        
        while (flag.get() != 0);
        printFirst.run();
        flag.set(1);
    }

    public void second(Runnable printSecond) throws InterruptedException {
        
        while (flag.get() != 1);
        printSecond.run();
        flag.set(2);
    }

    public void third(Runnable printThird) throws InterruptedException {
        
        while (flag.get() != 2);
        printThird.run();
        flag.set(0);
    }
}

阻塞/唤醒思路代码:

使用xx.wait、xx.notifyALL函数时,xx一定得对于所有线程可见。xx可以是另外定义的一个量,并配合synchronized关键字实现对所有线程可见,或者不用synchronized用volatile修饰一下也可以。

对于AtomicInteger类 阻塞不好实现,wait、notifyAll还是配合synchronized关键字用。

方案4(信号量、计数器、阻塞队列等,这些方面实现阻塞/唤醒思路,同时因为这些工具类都是线程安全的,都能满足“可见性”,所以只要这些工具类能有类似AtomicInteger类get/set方法的方法,那也能实现自旋锁,不过我还没试过...),下面链接都属阻塞/唤醒思路的:

链接:力扣https://leetcode.cn/problems/print-in-order/solutions/488685/si-chong-bu-tong-de-shi-xian-fang-shi-zong-jie-by-/

end...

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值