JUC并发编程---Lock锁

文章目录

  • 什么是Lock
  • synchronized加锁和Lock加锁代码示例
    • synchronized
    • 使用Lock加锁
  • 公平锁和非公平锁
    • 公平锁:
    • 非公平锁:
    • Lock和Synchronized的区别
  • synchronized 版的生产者和消费者
  • Lock 版的生产者和消费者
  • 生产者和消费者出现的问题
  • Condition精准通知和唤醒线程

什么是Lock

官网介绍:
虽然synchronized方法和语句的范围机制使得使用监视器锁更容易编程,并且有助于避免涉及锁的许多常见编程错误,但是有时您
需要以更灵活的方式处理锁。例如,用于遍历并发访问的数据结构的一些算法需要使用“手动”或“链锁定”:您获取节点A的锁定,然
后获取节点B,然后释放A并获取C,然后释放B并获得D等。所述的实施方式中L0Ck接口通过允许获得并在不同的范围释放的锁,并
允许获得并以任何顺序释放多个锁使得能够使用这样的技术。
随着这种增加的灵活性,额外的责任。没有块结构化锁定会删除使用synchronized)方法和语句发生的锁的自动释放。在大多数情
况下,应使用以下惯用语:
Lock l =…1.lock();try /access the resource protected by this lock finally l.unlock();
当在不同范围内发生锁定和解锁时,必须注意确保在锁定时执行的所有代码由try-finally或try-catch保护,以确保在必要时释放锁
定。
Lock实现提供了使用synch ronized方法和语句的附加功能,通过提供非阻塞尝试来获取锁(tryLock()),尝试获取可被中断的
锁(lockInterruptibly()),以及尝试获取可以超时(tryLock(Long,TimeUnit))。
一个Lock类还可以提供与隐式监视锁定的行为和语义完全不同的行为和语义,例如保证排序,非重入使用或死锁检测。如果一个实
现提供了这样的专门的语义,那么实现必须记录这些语义。
请注意,Lock实例只是普通对象,它们本身可以用作synchronized语句中的目标。获取Lock实例的监视器锁与调用该实例的任何
Lock(防法没有特定关系。建议为避免混淆,您不要以这种方式使用L0ck实例,除了在自己的实现中。
除非另有说明,传递任何参数的nuLl值将导致NullPointerException被抛出。

Lock是一个接口,有三个实现类:ReentrantLock(可重入锁)、ReentrantReadWriteLock.ReadLock(读锁),ReentrantReadWriteLock.writeLock(写锁)

synchronized加锁和Lock加锁代码示例

这里统一用一个买票的例子,多个线程实现不同的窗口进行买票

synchronized

public class TicketSale {
    public static void main(String[] args) {
        //并发:多线程操作同一个资源类,把资源丢入线程
        Ticket1 ticket = new Ticket1();
        //@FunctionalInterface 函数式接口,jkd1.8 lambda 表达式(参数)->{代码}
        new Thread(()->{
            for (int i = 0; i < 30; i++) {
                ticket.sale();
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 30; i++) {
                ticket.sale();
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i < 30; i++) {
                ticket.sale();
            }
        },"C").start();
    }
}
//资源类OOP
class  Ticket1{
    //属性  方法
    private int number = 30;
    //卖票的方式
    public synchronized void sale(){
        if (number>0){
            System.out.println(Thread.currentThread().getName()+"卖出了"+(number--)+"票,剩余"+number);
        }
    }
}


使用Lock加锁

public class SaleTicketDemo1 {
    public static void main(String[] args) {
        Ticket ticket=new Ticket();
        new Thread(()->{for (int i = 0; i < 40; i++) ticket.sale();},"A").start();
        new Thread(()->{for (int i = 0; i < 40; i++) ticket.sale();},"B").start();
        new Thread(()->{for (int i = 0; i < 40; i++) ticket.sale();},"C").start();
    }

}
//资源类
class Ticket{
    //属性
    private int number=50;
    Lock lock=new ReentrantLock();

    public void sale(){
        lock.lock();//加锁
        try {
            if (number>0){
                System.out.println(Thread.currentThread().getName()+"卖出了"+(number--)+"票,剩余:"+number);
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();//解锁
        }

    }
}


公平锁和非公平锁

公平锁和非公平锁是在多线程环境下用于同步访问共享资源的机制。它们之间的区别在于线程获取锁的顺序不同。

公平锁:

公平锁是指多个线程按照申请锁的顺序来获取锁,即先来先得的原则。当一个线程释放锁后,等待时间最长的线程会获得锁。
公平锁的优点是保证了资源的公平性,避免了饥饿现象,所有线程都有机会获取到资源。
公平锁的缺点是需要维护一个有序的等待队列,增加了系统开销,降低了并发性能。

非公平锁:

非公平锁是指多个线程获取锁的顺序是不确定的,有可能新申请锁的线程会在等待队列中插队,先于等待时间更长的线程获取到锁。
非公平锁的优点是相对于公平锁,减少了等待时间,提高了吞吐量。
非公平锁的缺点是可能会导致某些线程长时间等待,产生饥饿现象,不公平性可能会造成一些线程无法获得资源。
选择使用公平锁还是非公平锁,取决于具体的业务场景和需求:

如果对资源的访问顺序要求比较高,希望保证公平性,可以选择公平锁。
如果对吞吐量要求比较高,对于资源访问的顺序没有特别的要求,可以选择非公平锁。
需要注意的是,Java中的ReentrantLock默认是非公平锁,但可以通过构造函数参数来指定为公平锁。而synchronized关键字是一种非公平锁。在使用锁的时候,需要根据具体情况选择适合的锁机制。

在ReentrantLock中,默认是非公平锁,如果需要使用公平锁可以通过传入boolean类型的参数进行转换,true为公平锁
JUC并发编程---Lock锁_第1张图片

Lock和Synchronized的区别

1、Synchronized内置的ava关键字,Lock是一个ava类
2、Synchronized无法判断获取锁的状态,Lock可以判断是否获取到了锁
3、Synchronized会自动释放锁,lock必须要手动释放锁!如果不释放锁,死锁
4、Synchronized线程1(获得锁,阻塞)、线程2(等待,傻傻的等);Lock锁就不一定会等待下去(tryLock方法);
5、Synchronized可重入锁,不可以中断的,非公平;Lock,可重入锁,可以判断锁,非公平(可以自己设置);
6、Synchronized适合锁少量的代码同步问题,Lock适合锁大量的同步代码!

synchronized 版的生产者和消费者

生产者和消费者之间通过线程通信问题,生产者和消费者问题 等待唤醒 ,通知唤醒

public class A {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"C").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"D").start();
    }
}
//等待 业务 通知
class Data{//数字  资源类
    private  int number = 0;
    //加操作
    public  synchronized void increment() throws InterruptedException {
        if(number != 0) {
            //等待
            this.wait();//在哪里睡就在哪里醒
        }
        number++;
        //通知其他线程,我+1完毕了
        System.out.println(Thread.currentThread().getName()+"-->"+number);
        this.notifyAll();
    }
    //减操作
    public synchronized void decrement() throws InterruptedException {
        if(number == 0) {
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+"-->"+number);
        this.notifyAll();
    }
}

上面这段代码是4个线程进行交替。4个线程出现的问题在下文会有讲解。正常可以使用2个线程线程交替

Lock 版的生产者和消费者

public class B {
    public static void main(String[] args) {
        Data2 data = new Data2();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"C").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"D").start();
    }
}
//等待 业务 通知
class Data2 {//数字  资源类
    private int number = 0;
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    //    condition.await();//等待
//    condition.signalAll();//唤醒全部
    public void increment() throws InterruptedException {
        lock.lock();
        try {
            //业务代码
            if(number != 0) {
                //等待
                condition.await();
            }
            number++;
            //通知其他线程,我+1完毕了
            System.out.println(Thread.currentThread().getName() + "-->" + number);
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void decrement() throws InterruptedException {
        lock.lock();
        try {
            if(number == 0) {
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName() + "-->" + number);
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

生产者和消费者出现的问题

在通过上面的代码中,如果两个线程进行的话,可以实现生产者和消费者进行0、1的交替通信,那么如果再增加两个线程的话,就有可能出现问题,并不是0、1进行交替,而是可能出现8、9这样的数值。如图:
JUC并发编程---Lock锁_第2张图片

那这种情况就是虚假唤醒,也就是在不该醒的地方,线程醒了并执行了后续的操作。
虚假唤醒出现的原因:
JUC并发编程---Lock锁_第3张图片
如上图:当A线程进入阻塞的时候,其他三个线程就会竞争资源,这个使用可能c会获取资源,那么当它执行加操作的时候,会发现不符合条件就会进行阻塞,那么A、B、D进行争抢资源,当到A之后,发现不符合还是会进入阻塞,重复上面的操作,那么可能C抢到资源,这里注意,在刚刚的步骤中,C已经进行了一次阻塞,这里使用的wait(),那么wait是在哪里睡就在哪里醒,这里c就醒了,并执行了后续的加操作。所以才会出现加到7、8这种数值的情况
那么如果解决呢?可以把if判断更换为while判断。

Condition精准通知和唤醒线程

Condition因素出Object监视器方法(wait notify和notifyAll)成不同的对象,以得到具有多个等待集的每个对象,通过
将它们与使用任意的组合的效果Lock个实现。'Lock替换synchronized方法和语句的使用
Condition取代了对象监视器方法的使用。
条件(也称为条件队/域条件变量)为一个线程暂停执行(“等待”)提供了一种方法,直到另一个线程通知某些状态现在可能为真。
因为访问此共享状态信息发生在不同的线程中,所以它必须被保护,因此某种形式的锁与该条件相关联。等待条件的关键属性是它原

在这里插入代码片

public class C {
public static void main(String[] args) {
Data3 data = new Data3();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.printA();
}
}, “A”).start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.printB();
}
}, “B”).start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.printC();
}
}, “C”).start();
}
}
class Data3 {// 资源类 Lock
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();//监视器
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private int number = 1;//number为1的时候A执行,2的时候B执行,3的时候C执行
public void printA() {
lock.lock();
try {
//业务,判断->执行->通知
while (number != 1) {
//等待
condition1.await();
}
System.out.println(Thread.currentThread().getName() + “—>A”);
//唤醒指定的人:B
number = 2;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB() {
lock.lock();
try {
//业务,判断->执行->通知
while (number != 2) {
//等待
condition2.await();
}
System.out.println(Thread.currentThread().getName() + “—>B”);
//唤醒指定的人:C
number = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC() {
lock.lock();
try {
//业务,判断->执行->通知
while (number!=3){
//等待
condition3.await();
}
System.out.println(Thread.currentThread().getName()+“—>C”);
number=1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
上面代码是通过设置线程执行完之后,后面需要执行那个线程的

你可能感兴趣的:(java,算法,开发语言)