Java 锁的使用

0 线程阻塞、挂起与睡眠

状态

说明

挂起

一般是主动的;不释放CPU,可能释放内存,放在外存;会释放锁

阻塞

一般是被动的;释放CPU,不释放内存;不会释放锁;

休眠

一般是主动的;一般会释放CPU,也可能占着CPU不工作;不会释放锁;

表 线程挂起、阻塞和睡眠的特点         

1 锁的种类

图 java锁类别

悲观锁:总是假设最坏的情况,认为共享资源每次被访问的时候就会出现问题(比如共享数据被修改),所以每次在获取资源操作的时候都会上锁。

乐观锁:总是假设最好的情况,认为共享资源每次访问的时候不会出现问题,线程可以不停执行,无需加锁,只是在提交修改的时候去验证对应的资源是否被其他线程修改了。

共享锁:该锁可被多个线程锁持有。如果线程对数据A加上共享锁后,则其他线程只能对A再加共享锁,不能加排他锁。获得共享锁的线程只能读数据,不能修改数据。

独占锁,也叫排他锁。是指该锁一次只能被一个线程所持有。如果线程对数据A加上排他锁后,则其他线程不能再对A加任何类型的锁。获得排他锁的线程即可以读数据也可以写数据。

1.1 乐观锁的实现

1.1.1 版本号控制

一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数。当数据被修改时,version值会加1,当线程A要更新数据时,在提交更新时,若在刚读取到的version值与当前数据表的version值相等才更新,否则重试更新操作,直到更新成功。

线程

时间节点

线程A

线程B

T1

读取数据A,其版本号为1

T2

读取数据A,其版本号为1

T3

提交更新,对比数据表版本号相等,更新成功,版本号变为2

T4

提交更新,对比数据库版本号不相等,提交更新失败

T5

重新读取数据A,版本号为2

T6

提交更新,对比数据表版本号相等,更新成功,版本号变为3

表 版本号控制操作说明

1.1.2 CAS算法

CAS(Compare And Swap)比较与交换算法,被广泛应用各大框架中。

实现:维护3个变量,当前内存值V、旧的预期值A、即将更新的值B。通过while循环不断获取当前内存中的值V。如果V=A,就把V值更新为B。整个比较并交换操作是原子操作。伪代码如下:

while(V != A) {}

V = B;

当多个线程同时使用CAS操作一个变量时,只有一个会胜出,并成功更新,其余均会失败。允许其他线程再次尝试,也允许其他线程放弃操作。

问题

说明

ABA问题

线程1 修改变量值A,线程2 也修改这个值,在线程2,将值A改成B最后又改成A。导致线程1能通过CAS检测,能顺利提交修改。(容易产生业务逻辑混乱的问题)

CPU开销大

空循环时间过长,会造成CPU开销很大

只能保证一个共享变量的原子操作

操作多个共享变量时,循环CAS无法保证操作的原子性,需要用锁来保证原子性

表 CAS存在的问题

线程

时间节点

小红ATM1

小红ATM2

小张

T1

银行卡余额为100,给小李转100

给小红帐户转100

T2

网络阻塞

小红换过另一台机器,继续给小李转100

T3

网络阻塞

转账成功,小红余额变为0

T4

网络阻塞

转账成功,小红余额变为100

T5

转账成功100,小红余额为0

表 银行转账中出现的ABA问题说明

产生的后果是:小红原预期只给小李转100的,但实际却转了200。

1.2 自旋锁

spin lock,当一个线程在获取锁的时候,如果锁已经被其他线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。

图 自旋锁和非自旋锁

1.2.1 自旋锁优缺点

优点:1)自旋锁不会使线程状态发生切换,一直处于用户态,不会使线程进入阻塞状态,减少了不必要的上下文切换,执行速度快;

缺点:1)如果锁被占用的时间很长,那么自旋的线程会浪费很多处理器资源;2)自旋锁非公平的(也可以实现公平的自旋锁),无法满足等待时间最长的线程优先获取锁;

1.2.2 自旋锁Java实现

public class SpinLockDemo {
    private static class SpinLock {
        private AtomicReference<Thread> owner = new AtomicReference<>();
        private volatile int count = 0; //记录锁重入次数

        public void lock() {
            Thread current = Thread.currentThread();
            if (current == owner.get()) {
                count++;
                return;
            }
            while (!owner.compareAndSet(null,current));
        }

        public void unlock() {
            Thread current = Thread.currentThread();
            if (current == owner.get()) {
                if (count > 0) {
                    count--;
                } else {
                    owner.set(null);
                }
            }
        }
    }

    public static void main(String[] args) {
        SpinLock spinLock = new SpinLock();

        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                Thread currentThread = Thread.currentThread();
                System.out.println(currentThread.getName() + "开始获取自旋锁");
                spinLock.lock();
                try {
                    System.out.println(currentThread.getName() + "获取锁成功");
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                } finally {
                    spinLock.unlock();
                    System.out.println(currentThread.getName() + "释放了自旋锁");
                }
            }
        };

        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable);
        thread1.start();
        thread2.start();
    }

}
//运行结果:
//Thread-1开始获取自旋锁
//Thread-0开始获取自旋锁
//Thread-1获取锁成功
//Thread-1释放了自旋锁
//Thread-0获取锁成功
//Thread-0释放了自旋锁

2 ReentrantLock

2.1 与synchronized(lock)并不互斥

public class MutualExclusion {

    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();

        Thread thread1 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("synchronized thread1 begin");
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("thread1 end");
            }
        });

        Thread thread2 = new Thread(() -> {
            lock.lock();
            System.out.println("lock thread2 begin");
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("thread2 end");
            lock.unlock();
        });

        thread1.start();
        thread2.start();
    }
}
/*
运行结果:
 lock thread2 begin
 synchronized thread1 begin
 thread1 end
 thread2 end
 */

2.2 await与signal源码分析

//生产者-消费者模式
public class AwaitAndSignal {
    private final static ReentrantLock lock = new ReentrantLock();
    private final static Condition condition = lock.newCondition();
    private static volatile boolean ready = false;
    private static long count = 0;

    private static void producer() {
        try {
            lock.lock();
            while (ready) {
                condition.await();
            }
            count++;
            System.out.println("生产:" + count);
            ready = true;
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    private static void consumer() {
        try {
            lock.lock();
            while (!ready) {
                condition.await();
            }
            System.out.println("消费:" + count);
            ready = false;
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            while (true) producer();
        });
        Thread thread2 = new Thread(() -> {
            while (true) consumer();
        });

        thread1.start();
        thread2.start();

    }
}
/*
运行结果:
生产:306357
消费:306357
生产:306358
消费:306358
生产:306359
消费:306359
 */

图 await()方法源码截图

3 ReentrantReadWriteLock 读写锁

public class Test10 {

    private static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    private static void read() {
        lock.readLock().lock();
        System.out.println(Thread.currentThread().getName() + ",read begin");
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread().getName() + ",read end");
        lock.readLock().unlock();
    }

    private static void write() {
        lock.writeLock().lock();
        System.out.println(Thread.currentThread().getName() + ",write begin");
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread().getName() + ",write end");
        lock.writeLock().unlock();
    }

    public static void main(String[] args) throws InterruptedException {
        Thread readThread1 = new Thread(() -> {
            read();
        });
        readThread1.setName("readThread1");
        Thread readThread2 = new Thread(() -> {
            read();
        });
        readThread2.setName("readThread2");

        Thread writeThread1 = new Thread(() -> {
           write();
        });
        writeThread1.setName("writeThread1");
        Thread writeThread2 = new Thread(() -> {
            write();
        });
        writeThread2.setName("writeThread2");

        writeThread1.start();
//        readThread2.start();
        TimeUnit.SECONDS.sleep(1);
        readThread1.start();
//        writeThread2.start();

    }

}
/*
运行结果:
writeThread1,write begin
writeThread1,write end
readThread1,read begin
readThread1,read end

Process finished with exit code 0
 */
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值