JUC —— Lock锁

真正的多线程(公司)开发,不会去创建一个类,用该类去继承Thread类或实现Runnable接口,线程就是一个单独的资源类,没有任何附属的操作

Synchronized传统锁

public class SynchronizedTest {
    public static void main(String[] args) {
        //创建资源类
        Ticket ticket = new Ticket();
        //函数式接口使用lambda表达式
        new Thread(()->{
            for(int i=1;i<40;i++){
                ticket.sale();
            }
        },"A").start();
        new Thread(()->{
            for(int i=1;i<40;i++){
                ticket.sale();
            }
        },"B").start();
        new Thread(()->{
            for(int i=1;i<40;i++){
                ticket.sale();
            }
        },"C").start();
    }
}
//资源类,只有属性和方法
class Ticket{
    private int number = 30;
    //买票 synchronized 本质:队列,锁
    public synchronized void sale(){
        if(number > 0){
            System.out.println(Thread.currentThread().getName()+"卖出了第"+(number--)+"张票,剩余"+number+"张票");
        }
    }
}

测试结果:

A卖出了第30张票,剩余29张票
B卖出了第29张票,剩余28张票
B卖出了第28张票,剩余27张票
B卖出了第27张票,剩余26张票
B卖出了第26张票,剩余25张票
B卖出了第25张票,剩余24张票
B卖出了第24张票,剩余23张票
B卖出了第23张票,剩余22张票
B卖出了第22张票,剩余21张票
B卖出了第21张票,剩余20张票
B卖出了第20张票,剩余19张票
B卖出了第19张票,剩余18张票
B卖出了第18张票,剩余17张票
B卖出了第17张票,剩余16张票
B卖出了第16张票,剩余15张票
B卖出了第15张票,剩余14张票
B卖出了第14张票,剩余13张票
B卖出了第13张票,剩余12张票
B卖出了第12张票,剩余11张票
B卖出了第11张票,剩余10张票
B卖出了第10张票,剩余9张票
B卖出了第9张票,剩余8张票
B卖出了第8张票,剩余7张票
B卖出了第7张票,剩余6张票
B卖出了第6张票,剩余5张票
B卖出了第5张票,剩余4张票
B卖出了第4张票,剩余3张票
B卖出了第3张票,剩余2张票
B卖出了第2张票,剩余1张票
B卖出了第1张票,剩余0张票

Lock锁

点击ReentrantLock查看源码可以看到

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

公平锁: 十分公平,可以先来后到
非公平锁: 十分不公平,可以插队(ReentrantLock默认非公平锁)

使用Lock锁三步骤:

  • 创建lock对象 Lock lock = new ReentrantLock();
  • 加锁 lock.lock();
  • 解锁 lock.unlock();
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockTest {
    public static void main(String[] args) {
        //创建资源类
        Ticket2 ticket = new Ticket2();
        //函数式接口使用lambda表达式
        new Thread(()->{ for(int i=1;i<40;i++) ticket.sale();},"A").start();
        new Thread(()->{ for(int i=1;i<40;i++) ticket.sale();},"B").start();
        new Thread(()->{ for(int i=1;i<40;i++) ticket.sale();},"C").start();
    }
}

//资源类,只有属性和方法
class Ticket2{
    private int number = 30;
    //买票 使用lock锁
    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();
        }
    }
}

测试结果:

A卖出了第30张票,剩余29张票
A卖出了第29张票,剩余28张票
C卖出了第28张票,剩余27张票
C卖出了第27张票,剩余26张票
C卖出了第26张票,剩余25张票
C卖出了第25张票,剩余24张票
B卖出了第24张票,剩余23张票
B卖出了第23张票,剩余22张票
B卖出了第22张票,剩余21张票
B卖出了第21张票,剩余20张票
B卖出了第20张票,剩余19张票
B卖出了第19张票,剩余18张票
B卖出了第18张票,剩余17张票
B卖出了第17张票,剩余16张票
B卖出了第16张票,剩余15张票
B卖出了第15张票,剩余14张票
B卖出了第14张票,剩余13张票
B卖出了第13张票,剩余12张票
B卖出了第12张票,剩余11张票
B卖出了第11张票,剩余10张票
B卖出了第10张票,剩余9张票
B卖出了第9张票,剩余8张票
B卖出了第8张票,剩余7张票
B卖出了第7张票,剩余6张票
B卖出了第6张票,剩余5张票
B卖出了第5张票,剩余4张票
B卖出了第4张票,剩余3张票
B卖出了第3张票,剩余2张票
B卖出了第2张票,剩余1张票
B卖出了第1张票,剩余0张票

Synchronized和Lock区别

区别:

  • Synchronized是关键字;Lock是一个java类
  • Synchronized无法判断锁的状态;Lock是可以判断是否获取到了锁
  • Synchronized会自动释放锁;Lock必须要手动释放锁,如果不释放锁会出现 死锁
  • Synchronized 线程1(获取锁,阻塞) 线程2(一直等待);Lock锁不会一直等待
  • Synchronized可重入锁,不可以中断的,非公平;Lock可重入锁,可以判断锁,默认非公平(可以手动设置)
  • Synchronized适合锁少量的代码同步问题;Lock适合锁大量的同步代码

生产者和消费者

Synchronized版解决:

/**
 * 线程之间的通信问题,生产者和消费者问题  等待唤醒,具体业务,通知换线
 * 线程交替执行 A B操作同一个变量 num=0
 * A num+1
 * B num-1
 */
public class A {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(()->{
            try {
                for(int i=0;i<10;i++)data.increment();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();
        new Thread(()->{
            try {
                for(int i=0;i<10;i++)data.decrement();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"B").start();
    }
}
//资源类
class Data{
    private int num = 0;
    //+1
    public synchronized  void increment() throws InterruptedException {
        //等待
        if(num != 0) {
            this.wait();
        }
        num ++;
        System.out.println(Thread.currentThread().getName()+"=>"+num);
        //通知其他线程,+1完毕
        this.notify();
    }
    //-1
    public synchronized void decrement() throws InterruptedException {
        if(num == 0){
            //等待
            this.wait();
        }
        num --;
        System.out.println(Thread.currentThread().getName()+"=>"+num);
        //通知其他线程,-1完毕
        this.notify();
    }
}

执行结果:

A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0

存在问题:如果此时在添加两个线程 C,D,再次查看结果

Data data = new Data();
        new Thread(()->{
            try {
                for(int i=0;i<10;i++)data.increment();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();
        new Thread(()->{
            try {
                for(int i=0;i<10;i++)data.decrement();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"B").start();
        new Thread(()->{
            try {
                for(int i=0;i<10;i++)data.increment();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"C").start();
        new Thread(()->{
            try {
                for(int i=0;i<10;i++)data.decrement();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"D").start();
    }

执行结果:

A=>1
B=>0
A=>1
B=>0
C=>1
B=>0
C=>1
B=>0
C=>1
B=>0
C=>1
B=>0
C=>1
B=>0
C=>1
B=>0
C=>1
B=>0
C=>1
D=>0
B=>-1
D=>-2
D=>-3
D=>-4
D=>-5
D=>-6
D=>-7
D=>-8
D=>-9
D=>-10
A=>-9
C=>-8
A=>-7
C=>-6
A=>-5

可以看到数据存在一些问题,原因就是我们在判断等待的时候使用的if
在官方文档的wait()方法解释中可以找到:
线程也可以唤醒,而不会被通知,中端或超时,即所谓的虚假唤醒。虽然这在实践中很少会发生,但应用程序必须通过测试应该是线程被唤醒的条件来防范,并且如果条件不满足则继续等待。换句话说,等待应该总是出现在循环中,可以使用while
解决办法:将判断等待条件用的if修改为在while循环中判断等待条件

    //+1
    public synchronized  void increment() throws InterruptedException {
        //等待
        while (num != 0) {
            this.wait();
        }
        num ++;
        System.out.println(Thread.currentThread().getName()+"=>"+num);
        //通知其他线程,+1完毕
        this.notify();
    }
    //-1
    public synchronized void decrement() throws InterruptedException {
        while (num == 0){
            //等待
            this.wait();
        }
        num --;
        System.out.println(Thread.currentThread().getName()+"=>"+num);
        //通知其他线程,-1完毕
        this.notify();
    }

执行结果:

A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
C=>1
D=>0
C=>1
D=>0
C=>1
D=>0
C=>1
D=>0
C=>1
D=>0
C=>1
D=>0
C=>1
D=>0
C=>1
D=>0
C=>1
D=>0
C=>1
D=>0

JUC版解决:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class B {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(()->{
            try {
                for(int i=0;i<10;i++)data.increment();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();
        new Thread(()->{
            try {
                for(int i=0;i<10;i++)data.decrement();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"B").start();
        new Thread(()->{
            try {
                for(int i=0;i<10;i++)data.increment();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"C").start();
        new Thread(()->{
            try {
                for(int i=0;i<10;i++)data.decrement();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"D").start();
    }
}
//资源类
class Data{
    private int num = 0;
    //使用lock锁
    Lock lock = new ReentrantLock();
    //JUC中配合lock提供的用于做现成通讯的接口
    Condition condition = lock.newCondition();
    //+1
    public  void increment() throws InterruptedException {
        //加锁
        lock.lock();
        try {
            while (num != 0) {
                //等待
                condition.await();
            }
            num ++;
            System.out.println(Thread.currentThread().getName()+"=>"+num);
            //通知其他线程,+1完毕
            condition.signalAll();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //解锁
            lock.unlock();
        }
    }
    //-1
    public void decrement() throws InterruptedException {
        //加锁
        lock.lock();
        try {
            while (num == 0){
                //等待
                condition.await();
            }
            num --;
            System.out.println(Thread.currentThread().getName()+"=>"+num);
            //通知其他线程,-1完毕
            condition.signalAll();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //解锁
            lock.unlock();
        }
    }
}

执行结果:

A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
A=>1
D=>0
A=>1
D=>0
A=>1
D=>0
A=>1
D=>0
C=>1
D=>0
C=>1
D=>0
C=>1
D=>0
C=>1
D=>0
C=>1
D=>0
C=>1
D=>0
C=>1
B=>0
C=>1
B=>0
C=>1
B=>0
C=>1
B=>0

从上述代码测试结果可以发现lock+condition使用和synchronized+wait效果一样,但任何一个新的技术,绝对不是仅仅只是覆盖原来的技术,一定会有优势和补充
condition的优势和补充: 精准的通知和唤醒线程

//A执行完调用B,B执行完调用C,C执行完调用A
public class C {
    public static void main(String[] args) {
        Data data = new Data();
        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 Data{
    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();

    private int num = 1; //1的时候A执行,2的时候B执行,3的时候C执行

    public void printA(){
        lock.lock();
        try{
            while (num != 1){
                //等待
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName()+"执行");
            num = 2;
            //唤醒指定的人B
            condition2.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void printB(){
        lock.lock();
        try{
            while (num != 2){
                //等待
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName()+"执行");
            num = 3;
            //唤醒C
            condition3.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void printC(){
        lock.lock();
        try{
            while (num != 3){
                //等待
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName()+"执行");
            num = 1;
            //唤醒A
            condition1.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

执行结果:

A执行
B执行
C执行
A执行
B执行
C执行
A执行
B执行
C执行
A执行
B执行
C执行
A执行
B执行
C执行
A执行
B执行
C执行
A执行
B执行
C执行
A执行
B执行
C执行
A执行
B执行
C执行
A执行
B执行
C执行
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值