线程锁

一、线程锁

1. Synchronized

  采用synchronized修饰符实现的同步机制叫做互斥锁机制,它所获得的锁叫做互斥锁。每个对象都有一个monitor(锁标记),当线程拥有这个锁标记时才能访问这个资源,没有锁标记便进入锁池。任何一个对象系统都会为其创建一个互斥锁,这个锁是为了分配给线程的,防止打断原子操作。每个对象的锁只能分配给一个线程,因此叫做互斥锁。

2.Lock

  Lock锁与synchronized一样,都是可以用来控制同步访问的。 相比于synchronized,Lock需要手动的获取锁与释放锁。Lock是一个接口,常用实现类和方法有:

ReentrantLock
ReentrantReadWriteLock
 - ReadLock
 - WriteLock
方法:
 - lock()
 - unlock()

3.读锁和写锁

  读锁是一种共享锁。可以被多个线程同时,同时访问数据,可以提高访问数据的并发性能。写锁是一种排他锁,只能被一个线程获得。

4.Lock 底层原理

原子操作工具
AtomicInteger
 - AtomicLong
 - AtomicReference
 - ...

CAS
 - Compare And Swap
 - CAS算法,用非阻塞的方式,来获得锁

自旋锁
 - 占用cpu资源
 - 用循环,用CAS来获得锁

二、悲观锁和乐观锁

1.悲观锁

  像它的名字一样,对于并发间操作产生的线程安全问题持悲观状态,悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,不管三七二十一,直接上了锁就操作资源了。

2.乐观锁

  就像它的名字一样,对于并发间操作产生的线程安全问题持乐观状态,乐观锁认为竞争不总是会发生,因此它不需要持有锁,将比较-替换这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。

三、两种常见的锁

1. Synchronized 互斥锁(悲观锁,有罪假设)

   采用synchronized修饰符实现的同步机制叫做互斥锁机制,它所获得的锁叫做互斥锁。每个对象都有一个monitor(锁标记),当线程拥有这个锁标记时才能访问这个资源,没有锁标记便进入锁池。任何一个对象系统都会为其创建一个互斥锁,这个锁是为了分配给线程的,防止打断原子操作。每个对象的锁只能分配给一个线程,因此叫做互斥锁。

public class TicketThread  extends Thread{
    //总票数,多个线程共享这个变量,能修改 ticket–
    private int ticket = 10;     
    
    //执行业务,重写父类run方法
    @Override
    public void run() {
       //业务处理,卖票:票–
           while(true) {     //线程非常多,我想尽量给我资源
              synchronized (this) { //对象锁
              //判断一个条件,出去条件
              if(ticket<=0) {   //多线程可能ticket=-1
                  break;     //退出死循环
              }
              
              //不出现,线程run方法执行太快,不会发生线程冲突
              try {  //不能抛出异常,抛出就不是重写run方法
                  Thread.sleep(100);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              
              System.out.println(“窗口:” + Thread.currentThread().getName() 
                     +, 剩余票数:” + ticket-- );
           }
       }
    }
    
    //3个窗口都买这一个票
    public static void main(String[] args) {
       //目标
       Thread target = new TicketThread();
       
       for(int i=0; i<3; i++) {
           new Thread(target).start();      //3个线程共同作用一个target
       }
    }
}

2.ReentrantReadWriteLock 读写锁(乐观锁,无罪假设)

   ReentrantLock是排他锁,排他锁在同一时刻仅有一个线程可以进行访问,实际上独占锁是一种相对比较保守的锁策略,在这种情况下任何“读/读”、“读/写”、“写/写”操作都不能同时发生,这在一定程度上降低了吞吐量。然而读操作之间不存在数据竞争问题,如果”读/读”操作能够以共享锁的方式进行,那会进一步提升性能。因此引入了ReentrantReadWriteLock,顾名思义,ReentrantReadWriteLock是Reentrant(可重入) Read(读)Write(写)Lock(锁),我们下面称它为读写锁。
   读写锁内部又分为读锁和写锁,读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的。读锁和写锁分离从而提升程序性能,读写锁主要应用于读多写少的场景。

public class TicketLock implements Runnable {
    private int ticket = 10;
    
    //jdk1.6前,性能差异很大,1.6后synchronized底层实现类似Lock,性能伯仲之间
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
 
    @Override
    public void run() {
       while (true) {
           try {
              lock.writeLock().lock();
              
              if (ticket <= 0) {
                  break;
              }
              try {
                  Thread.sleep(10);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              System.out.println(“线程:” + Thread.currentThread().getName() + “,还剩票数:” + ticket--);
           } catch (Exception e) {
              // TODO: handle exception
           } finally {
              lock.writeLock().unlock();   //防止死锁,会自动释放,而synchronized不会释放
           }
       }
    }
 
    public static void main(String[] args) {
       Runnable target = new TicketLock ();
       int windows = 3; // 窗口数量
 
       for (int I = 1; I < windows + 1; i++) {
           new Thread(target, “窗口” + i).start();
       }
    }
}

3.两种方式的区别

  需要注意的是,用sychronized修饰的方法或者语句块在代码执行完之后锁会自动释放,而是用Lock需要我们手动释放锁,所以为了保证锁最终被释放(发生异常情况),要把互斥区放在try内,释放锁放在finally内!
  与互斥锁相比,读-写锁允许对共享数据进行更高级别的并发访问。虽然一次只有一个线程(writer 线程)可以修改共享数据,但在许多情况下,任何数量的线程可以同时读取共享数据(reader 线程)从理论上讲,与互斥锁定相比,使用读-写锁允许的并发性增强将带来更大的性能提高。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Django是一个基于Python的开源Web应用框架,它提供了一套完整的工具和库,用于帮助开发人员快速构建高效、安全和可扩展的Web应用程序。 在Django中,线程锁是一种用于控制并发访问的机制,它可以确保在多个线程同时访问共享资源时的数据一致性和正确性。线程锁可以防止多个线程同时修改同一个资源,从而避免数据竞争和不一致的结果。 Django提供了多种线程锁的实现方式,其中最常用的是使用Python标准库中的`threading`模块提供的锁机制。通过使用`threading.Lock()`创建一个锁对象,并使用`acquire()`方法获取锁,在操作共享资源之前调用`acquire()`方法可以确保只有一个线程可以访问资源。在操作完成后,使用`release()`方法释放锁,以便其他线程可以获取锁并进行操作。 以下是一个简单的示例代码,演示了如何在Django中使用线程锁: ```python import threading # 创建一个全局锁对象 lock = threading.Lock() def my_view(request): # 获取锁 lock.acquire() try: # 执行需要保护的操作 # ... finally: # 释放锁 lock.release() ``` 在上述示例中,`my_view`是一个Django视图函数,通过获取锁对象并在需要保护的操作前后调用`acquire()`和`release()`方法,确保了在同一时间只有一个线程可以执行需要保护的操作。 需要注意的是,线程锁只能在同一个进程内的多个线程之间起作用,如果是多个进程之间的并发访问,需要使用进程锁或其他机制来实现并发控制。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值