JUC并发编程之线程锁(一)

简介: 1.ReentrantLock(互斥锁)、2.ReentRantReaderWriterLock(互斥读写锁)、3.StampedLock(无障碍锁)、4.Condition(自定义锁)、5.LockSupport

 



问题引出:

       由于传统的线程控制需要用到同步机制Synchronized与 Object类中的wait();notify();函数进行控制,但是这样控制并不容易,所以在JUC中提供了全新的框架,

框架核心接口为:1.Lock();2.ReaderWriteLock();

1.ReentrantLock(互斥锁)

ReentrantLock是一种互斥锁,意思是一旦有一个线程获取到锁,那么其他线程就无法运行将会进行等待阻塞。其中又分为公平锁和非公平锁。区别在于获取锁的机制是否公平。并且该锁通过一个FIFO队列管理所有的等待线程。

以下是ReentrantLock类的常用方法:

方法名 描述
ReentrantLock() 创建一个新的ReentrantLock实例。
ReentrantLock(boolean fair) 创建一个新的ReentrantLock实例,根据参数fair的值决定是否按公平顺序获取锁。
lock() 获取锁,如果锁不可用,则当前线程将被阻塞,直到锁可用。
unlock() 释放锁,使得其他等待锁的线程可以尝试获取锁。
tryLock() 尝试获取锁,如果锁可用则获取锁并返回true,否则立即返回false。
isFair() 判断锁是否是公平锁,如果是公平锁则返回true,否则返回false。
new Condition() 创建一个与该锁关联的Condition对象,用于线程间等待和通知,在调用Condition的await方法时,当前线程会释放锁。

案例:有五个售票员同时售卖8张票

package Example2110;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
class  Ticeket{
    private int ticket = 8;
//    创建互斥锁并设置为公平锁,所有线程运行都是等概率的
    ReentrantLock reentrantLock = new ReentrantLock(true);
    public  void sale(){
        while (true){
            //        开启锁只有一个线程能通过
            reentrantLock.lock();
            try {
//                模拟网络延迟
                TimeUnit.SECONDS.sleep(1);
                if (ticket>0){
                    System.out.println(Thread.currentThread().getName()+"售卖第"+ticket--+"张票");
                }else {
                    System.out.println("卖完咯");
                    break;
                }
            }catch (Exception e){
                e.printStackTrace();
            }finally {
//                释放锁
                reentrantLock.unlock();
//                线程让行,让其他线程也有机会运行
                Thread.yield();
            }
        }
    }
}
public class javaDemo {
    public static void main(String[] args) {
        Ticeket ticket = new Ticeket();
//        创建多个售卖对象
        for (int i=1;i<=5;i++){
            new Thread(()->{
                ticket.sale();
            },"售票员"+i).start();
        }
    }
}

image.gif

image.gif编辑

问题引出:

       独占锁的最大弊端就在于其阻断了其他线程只允许一个线程工作。在很多情况下就会造成性能问题。所以引入了ReentrantLock(读写互斥锁)

面试题:ReentrantLock 是如何实现可重入性的?

(1)什么是可重入性

一个线程持有锁时,当其他线程尝试获取该锁时,会被阻塞;而这个线程尝试获取自己持有锁时,如果成功说明该锁是可重入的,反之则不可重入。

(2)synchronized是如何实现可重入性

synchronized关键字经过编译后,会在同步块的前后分别形成monitorenter和monitorexit两个字节码指令。每个锁对象内部维护一个计数器,该计数器初始值为0,表示任何线程都可以获取该锁并执行相应的方法。根据虚拟机规范要求,在执行monitorenter指令时,首先要尝试获取对象的锁,如果这个对象没有被锁定,或者当前线程已经拥有了对象的锁,把锁的计数器+1,相应的在执行monitorexit指令后锁计数器-1,当计数器为0时,锁就被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到对象锁被另一个线程释放为止。

(3)ReentrantLock如何实现可重入性

ReentrantLock使用内部类Sync来管理锁,所以真正的获取锁是由Sync的实现类控制的。Sync有两个实现,分别为NonfairSync(非公公平锁)和FairSync(公平锁)。Sync通过继承AQS实现,在AQS中维护了一个private volatile int state来计算重入次数,避免频繁的持有释放操作带来的线程问题。

(4)代码分析

当一个线程在获取锁过程中,先判断state的值是否为0,如果是表示没有线程持有锁,就可以尝试获取锁。

当state的值不为0时,表示锁已经被一个线程占用了,这时会做一个判断current==getExclusiveOwnerThread(),这个方法返回的是当前持有锁的线程,这个判断是看当前持有锁的线程是不是自己,如果是自己,那么将state的值+1,表示重入返回即可。


2.ReentRantReaderWriterLock(互斥读写锁)

        这个锁的机制和ReentrantLock区别并不大,但是将其权力拆分出来,分别为共享锁读,和互斥锁写锁。意思就是多线程想要修改数据就只允许其中一个线程修改其他等待,但是读取数据大家都可以随意读取。

ReentrantReaderWriter类的常用操作方法:

方法名 描述
readLock() 获取读锁,如果写锁被占用,则当前线程会被阻塞,直到写锁释放。
writeLock() 获取写锁,如果读锁或写锁被占用,则当前线程会被阻塞,直到所有的读锁和写锁都释放。

注意:实例化该对象用到开头提到框架中的ReaderWriterLock接口进行向上转型得到对象。

如:ReaderWriteLock readWriterLock = new RenntrantReaderWriterLock();

以下案例通过ReentrantReaderWriterLock实现一个银行10个ATM机,五个进行存款,五个进行读取账户信息

package Example2111;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
class  Bank{
    private String name = "黄田";
    private double account = 0;
    private ReadWriteLock readWriteLock= new ReentrantReadWriteLock();
//    存钱
    public void write(double money){
//        设置写锁并开启
        this.readWriteLock.writeLock().lock();
        try {
            account = account+money;
//            模拟网络延迟
            TimeUnit.SECONDS.sleep(1);
            System.out.println("您正在使用"+Thread.currentThread().getName()+"存入存款:"+money+",当前账户余额为"+account);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
//            更新完数据就可以解放写锁
            this.readWriteLock.writeLock().unlock();
        }
    }
//    读取存款
    public void Read(){
//        设置读取共享锁
        this.readWriteLock.readLock().lock();
        try {
//            模拟网络延迟
            TimeUnit.SECONDS.sleep(2);
            System.out.println("您正在使用"+Thread.currentThread().getName()+"读取账户信息:"+":当前账户人名称"+name+",账户人当前的余额为"+account);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
//            读取完数据自动解锁
            this.readWriteLock.readLock().unlock();
        }
    }
}
public class javaDemo {
    public static void main(String[] args) {
        Bank bank = new Bank();
        double moneys[]=new double[]{10,500,300,400};
//        五个ATM存款
        for (int i =1;i<=5;i++){
            new Thread(()->{
                for (int j=0;j<moneys.length;j++){
                    bank.write(moneys[j]);
                }
            },"银行分行ATM"+i+"号取款机").start();
        }
//        五个ATM读取存款
        for (int i=5;i<=10;i++){
            new Thread(()->{
                bank.Read();
            },"银行分行ATM"+i).start();
        }
    }
}

image.gif

image.gif编辑

面试题:synchronized 和 ReentrantLock 区别是什么?

synchronized 是和 if、else、for、while 一样的关键字,ReentrantLock 是类,这是二者的本质区别。既然 ReentrantLock 是类,那么它就提供了比synchronized 更多更灵活的特性,可以被继承、可以有方法、可以有各种各样的类变量

synchronized 早期的实现比较低效,对比 ReentrantLock,大多数场景性能都相差较大,但是在 Java 6 中对 synchronized 进行了非常多的改进。

相同点:两者都是可重入锁

两者都是可重入锁。“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。

主要区别如下:

   ReentrantLock 使用起来比较灵活,但是必须有释放锁的配合动作;

   ReentrantLock 必须手动获取与释放锁,而 synchronized 不需要手动释放和开启锁;

   ReentrantLock 只适用于代码块锁,而 synchronized 可以修饰类、方法、变量等。

   二者的锁机制其实也是不一样的。ReentrantLock 底层调用的是 Unsafe 的park 方法加锁,synchronized 操作的应该是对象头中 mark word

Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:

   普通同步方法,锁是当前实例对象

   静态同步方法,锁是当前类的class对象

   同步方法块,锁是括号里面的对象

 


3.StampedLock(无障碍锁)

       问题引出:虽然ReentrantLock和ReentrantReaderWriter解决了并发访问下数据写入安全和效率的问题,但是如果出现非常多的线程的时候,有可能会造成一些线程一直阻塞。调度减少的情况。为此JUC提供了StampedLock(无障碍锁)该所的特点在于若干个读线程之间互不干预。并且可以保证多个写线程的独占操作

以下是StampedLock类的常用方法:

方法 描述
readLock() 获取读锁。
tryReadLock() 尝试获取读锁,如果获取成功返回一个非负数,否则返回一个负数。
optimisticRead() 获取一个乐观读锁。
tryConvertToOptimisticRead(stamp) 尝试将锁从写锁转换为乐观读锁,如果转换成功返回一个非负数,否则返回一个负数。
tryConvertToReadLock() 尝试将锁从乐观读锁转换为普通读锁,如果转换成功返回一个非负数,否则返回一个负数。
writeLock() 获取写锁。
tryWriteLock() 尝试获取写锁,如果获取成功返回一个非负数,否则返回一个负数。
unlock() 释放锁。
unlockRead() 释放读锁。
unlockWrite() 释放写锁。
validate() 验证锁是否仍然有效。

 在StampedLock中有三种模式,乐观读,读,写用以提高并发处理性能,也用以转换锁的类型

案例代码:

假设我们有一个名为Point的类,表示二维平面上的一个点,其中包含x坐标和y坐标。我们希望实现对这个点的读写操作,并且要确保在写操作时其他线程不能同时读取或写入。

package Example2113;
import org.omg.CORBA.BAD_CONTEXT;
import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.locks.StampedLock;
class Point{
//    坐标
    double x;
    double y;
    private final StampedLock stampedLock = new StampedLock();
    public void set(double x,double y){
//        获取写锁
        long stamp = stampedLock.writeLock();
        try {
            this.x = x;
            this.y = y;
        }catch (Exception e){
            e.printStackTrace();
        }finally {
//            执行完后释放写锁
            stampedLock.unlockWrite(stamp);
        }
    }
    public double[] get(){
//        尝试获取乐观锁
        long stamp = stampedLock.tryOptimisticRead();
        double currentX =x;
        double currentY =y;
//        如果获取乐观锁失败则转为悲观锁
        if (!stampedLock.validate(stamp)){
//            获取
            stamp  = stampedLock.readLock();
            try {
                currentX =x;
                currentY = y;
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                stampedLock.unlockRead(stamp);
            }
        }
        return new double[]{currentX, currentY};
    }
}
public class javaDemo {
    public static void main(String[] args) {
        Point point = new Point();
        Random random = new Random(10);
        for (int i =0;i<5;i++){
            new Thread(()->{
                point.set(random.nextDouble(), random.nextDouble());
                double recieve[] = point.get();
                System.out.println(Arrays.toString(recieve));
            }).start();
        }
//        for (int j = 0;j<20;j++){
//            new Thread(()->{
//                double recieve[] = point.get();
//                System.out.println(Arrays.toString(recieve));
//            }).start();
//        }
    }
}

image.gif

image.gif编辑

问:乐观锁和悲观锁是什么?

乐观锁和悲观锁是并发编程中两种不同的锁策略。

    1. 乐观锁:乐观锁假设多个线程之间的并发冲突很少发生,所以它们可以同时读取共享数据而无需阻塞其他线程。在乐观锁中,线程首先尝试获取读锁,即乐观读取操作。如果没有发生写入冲突,那么乐观读锁会立即完成,并返回结果。但如果有其他线程在此期间进行了写入操作,则需要重新获取悲观锁再次尝试。
    2. 悲观锁:悲观锁假设多个线程之间的并发冲突经常发生,因此每个线程在访问共享数据之前会悲观地认为会发生冲突,所以必须先获得独占锁(写锁)或共享锁(读锁)。只有持有锁的线程完成操作后,其他线程才能访问共享数据。悲观锁会导致其他线程阻塞等待锁的释放,从而降低并发性能。

    问:为什么要用Stamp,代码中的long stamp是什么意思

    stamp是一个标记,用于记录锁的状态。在读取锁和写入锁上下文之间传递,以确保数据一致性。

    面试题 :请谈谈 ReadWriteLock 和 StampedLock

    ReadWriteLock包括两种子锁

    (1)ReadWriteLock

    ReadWriteLock 可以实现多个读锁同时进行,但是读与写和写于写互斥,只能有一个写锁线程在进行。

    (2)StampedLock

    StampedLock是Jdk在1.8提供的一种读写锁,相比较ReentrantReadWriteLock性能更好,因为ReentrantReadWriteLock在读写之间是互斥的,使用的是一种悲观策略,在读线程特别多的情况下,会造成写线程处于饥饿状态,虽然可以在初始化的时候设置为true指定为公平,但是吞吐量又下去了,而StampedLock是提供了一种乐观策略,更好的实现读写分离,并且吞吐量不会下降。

    StampedLock包括三种锁:

    (1)写锁writeLock:

    writeLock是一个独占锁写锁,当一个线程获得该锁后,其他请求读锁或者写锁的线程阻塞, 获取成功后,会返回一个stamp(凭据)变量来表示该锁的版本,在释放锁时调用unlockWrite方法传递stamp参数。提供了非阻塞式获取锁tryWriteLock。

    (2)悲观读锁readLock:

    readLock是一个共享读锁,在没有线程获取写锁情况下,多个线程可以获取该锁。如果有写锁获取,那么其他线程请求读锁会被阻塞。悲观读锁会认为其他线程可能要对自己操作的数据进行修改,所以需要先对数据进行加锁,这是在读少写多的情况下考虑的。请求该锁成功后会返回一个stamp值,在释放锁时调用unlockRead方法传递stamp参数。提供了非阻塞式获取锁方法tryWriteLock。

    (3)乐观读锁tryOptimisticRead:

    tryOptimisticRead相对比悲观读锁,在操作数据前并没有通过CAS设置锁的状态,如果没有线程获取写锁,则返回一个非0的stamp变量,获取该stamp后在操作数据前还需要调用validate方法来判断期间是否有线程获取了写锁,如果是返回值为0则有线程获取写锁,如果不是0则可以使用stamp变量的锁来操作数据。由于tryOptimisticRead并没有修改锁状态,所以不需要释放锁。这是读多写少的情况下考虑的,不涉及CAS操作,所以效率较高,在保证数据一致性上需要复制一份要操作的变量到方法栈中,并且在操作数据时可能其他写线程已经修改了数据,而我们操作的是方法栈里面的数据,也就是一个快照,所以最多返回的不是最新的数据,但是一致性得到了保证。

    面试题:如何让 Java 的线程彼此同步?

      1. synchronized
      2. volatile
      3. ReenreantLock
      4. 使用局部变量实现线程同步

      4.Condition(自定义锁)

             在JUC中允许用户进行锁对象的创建,可以通过Condition接口实现。经常用于生产者消费者模型中。

      以下是Condition接口的常用方法

      方法 描述
      await() 当前线程等待,并释放锁。等价于Object.wait()
      await(long time, TimeUnit unit) 当前线程等待一段时间,并释放锁。等价于Object.wait()
      awaitUninterruptibly() 当前线程不可中断地等待,并释放锁。
      signal() 唤醒一个等待该条件的线程。等价于Object.notify()
      signalAll() 唤醒所有等待该条件的线程。等价于Object.notifyAll()

      使用案例:

      假设有一个生产者-消费者模型,多个生产者线程负责向共享队列中生产数据,多个消费者线程负责从队列中消费数据。

      package Example2114;
      import java.util.LinkedList;
      import java.util.Queue;
      import java.util.concurrent.locks.Condition;
      import java.util.concurrent.locks.Lock;
      import java.util.concurrent.locks.ReentrantLock;
      class SharedQueue {
          private final Lock lock = new ReentrantLock();
          private final Condition notFull = lock.newCondition();
          private final Condition notEmpty = lock.newCondition();
          private static final int MAX_SIZE = 10;
          private Queue<Integer> queue = new LinkedList<>();
          public void produce(int num) throws InterruptedException {
              lock.lock();
              try {
                  while (queue.size() == MAX_SIZE) {
                      // 队列满时,等待条件notFull
                      notFull.await();
                  }
                  queue.offer(num);
                  System.out.println("Produced: " + num);
                  // 唤醒一个等待notEmpty条件的线程
                  notEmpty.signal();
              } finally {
                  lock.unlock();
              }
          }
          public int consume() throws InterruptedException {
              lock.lock();
              try {
                  while (queue.isEmpty()) {
                      // 队列空时,等待条件notEmpty
                      notEmpty.await();
                  }
                  int num = queue.poll();
                  System.out.println("Consumed: " + num);
                  // 唤醒一个等待notFull条件的线程
                  notFull.signal();
                  return num;
              } finally {
                  lock.unlock();
              }
          }
      }
      public class javaDemo {
          public static void main(String[] args) {
              SharedQueue sharedQueue = new SharedQueue();
              // 创建生产者线程
              Thread producer1 = new Thread(() -> {
                  try {
                      for (int i = 0; i < 20; i++) {
                          sharedQueue.produce(i);
                      }
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              });
              Thread producer2 = new Thread(() -> {
                  try {
                      for (int i = 20; i < 40; i++) {
                          sharedQueue.produce(i);
                      }
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              });
              // 创建消费者线程
              Thread consumer1 = new Thread(() -> {
                  try {
                      for (int i = 0; i < 20; i++) {
                          sharedQueue.consume();
                      }
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              });
              Thread consumer2 = new Thread(() -> {
                  try {
                      for (int i = 0; i < 20; i++) {
                          sharedQueue.consume();
                      }
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              });
              // 启动线程
              producer1.start();
              producer2.start();
              consumer1.start();
              consumer2.start();
          }
      }

      image.gif

      image.gif编辑

      案例2:实现缓存队列的读取:

      package Example2116;
      import java.util.LinkedList;
      import java.util.Queue;
      import java.util.concurrent.TimeUnit;
      import java.util.concurrent.locks.Condition;
      import java.util.concurrent.locks.Lock;
      import java.util.concurrent.locks.ReentrantLock;
      //缓冲读取与写入
      class DataBuffer{
          private final Lock lock = new ReentrantLock();
          private final Condition notUll = lock.newCondition();
          private final Condition notEmpty = lock.newCondition();
          private static final int MAX_SIZE = 20;
          Queue<String> queue = new LinkedList<>();
          public void Set(String str){
      //        获取互斥锁
              lock.lock();
              try {
      //            当缓冲区满了
                  while (queue.size()==MAX_SIZE){
                      notUll.await();
                  }
      //            模拟网络延迟
                  TimeUnit.SECONDS.sleep(2);
      //            放入数据并尝试唤醒等待的消费者线程
                  queue.offer(str);
                  System.out.println(Thread.currentThread().getName()+"放入数据"+str);
                  notEmpty.signal();
              }catch (Exception e){
                  e.printStackTrace();
              }finally {
                  lock.unlock();
              }
          }
          public  String Get() throws Exception{
      //        使用互斥锁
              lock.lock();
              try {
                  while (queue.isEmpty()){
                      notEmpty.await();
                  }
                  String string = queue.poll();
                  System.out.println("获取到缓冲区内容"+string);
                  notUll.signal();
                  return string;
              }finally {
      //            解锁
                  lock.unlock();
              }
          }
      }
      public class javaDemo {
          public static void main(String[] args) {
              DataBuffer dataBuffer = new DataBuffer();
              for (int i=0;i<5;i++){
                  new Thread(()->{
                      for (int j = 0;j<20;j++){
                          dataBuffer.Set("数据"+j+"号数、");
                      }
                  },"生产者"+i+"号").start();
              }
              for (int i=0;i<50;i++){
                  new Thread(()->{
                      for (int j=0;j<100;j++){
                          try {
                              System.out.println("消费者取走"+dataBuffer.Get());
                          }catch (Exception e){}
                      }
                  }).start();
              }
          }
      }

      image.gif

      image.gif编辑

      面试题: Java 如何实现多线程之间的通讯和协作?

      Java中线程通信协作的最常见的两种方式:

      1、syncrhoized加锁的线程的Object类的wait()/notify()/notifyAll()

      2、ReentrantLock类加锁的线程的Condition类的await()/signal()/signalAll()


      5.LockSupport

      由于JDK1.2时候为了预防死锁废除了一些Thread中的方法,但是一部分人认为这几个方法在操作上实际会更加直观,所以就有了LockSupport来替代这几个方法

      LockSupport类的常用方法:

      方法签名 说明
      park() 阻塞当前线程,直到调用unpark(Thread thread)或者中断当前线程。
      park(Object blocker) 阻塞当前线程,并关联一个特定的阻塞对象,用于调试和监控的目的。
      parkNanos(long nanos) 阻塞当前线程,最多阻塞指定的纳秒数,参数nanos为等待时间。
      parkNanos(Object blocker, long nanos) 阻塞当前线程,并关联一个特定的阻塞对象,最多阻塞指定的纳秒数。
      unpark(Thread thread) 解除指定线程的阻塞状态。可以提前唤醒被阻塞的线程,使其继续执行。

      案例代码:设置一个长辈线程和一个孩子辈线程,只有当长辈线程吃饭时候子线程才能进行吃饭

      package Example2117;
      import java.util.concurrent.TimeUnit;
      import java.util.concurrent.locks.LockSupport;
      public class javaDemo {
          public static void main(String[] args) {
      //        子线程
              Thread son = new Thread(()->{
      //            阻塞线程
                  LockSupport.park();
                  System.out.println("子辈开始吃饭");
              });
      //        父辈线程
              Thread father = new Thread(()->{
                  try {
                      TimeUnit.SECONDS.sleep(3);
                      System.out.println("长辈开始吃饭");
                  }catch (Exception e){
                      e.printStackTrace();
                  }finally {
                      LockSupport.unpark(son);
                  }
              });
              son.start();
              father.start();
          }
      }

      image.gif

      image.gif编辑

       

      面试题:Java Concurrency API 中的 Lock 接口(Lock interface)是什么?对比同步它有什么优势?

      Lock 接口比同步方法和同步块提供了更具扩展性的锁操作。他们允许更灵活的结构,可以具有完全不同的性质,并且可以支持多个相关类的条件对象。

      它的优势有:

      (1)可以使锁更公平

      (2)可以使线程在等待锁的时候响应中断

      (3)可以让线程尝试获取锁,并在无法获取锁的时候立即返回或者等待一段时间

      (4)可以在不同的范围,以不同的顺序获取和释放锁

      整体上来说 Lock 是 synchronized 的扩展版,Lock 提供了无条件的、可轮询的(tryLock 方法)、定时的(tryLock 带参方法)、可中断的(lockInterruptibly)、可多条件队列的(newCondition 方法)锁操作。另外 Lock 的实现类基本都支持非公平锁(默认)和公平锁,synchronized 只支持非公平锁,当然,在大部分情况下,非公平锁是高效的选择。


      目录
      相关文章
      |
      2月前
      |
      安全 Java 调度
      Java编程时多线程操作单核服务器可以不加锁吗?
      Java编程时多线程操作单核服务器可以不加锁吗?
      44 2
      |
      5天前
      |
      并行计算 数据处理 调度
      Python中的并发编程:探索多线程与多进程的奥秘####
      本文深入探讨了Python中并发编程的两种主要方式——多线程与多进程,通过对比分析它们的工作原理、适用场景及性能差异,揭示了在不同应用需求下如何合理选择并发模型。文章首先简述了并发编程的基本概念,随后详细阐述了Python中多线程与多进程的实现机制,包括GIL(全局解释器锁)对多线程的影响以及多进程的独立内存空间特性。最后,通过实例演示了如何在Python项目中有效利用多线程和多进程提升程序性能。 ####
      |
      1月前
      |
      数据挖掘 程序员 调度
      探索Python的并发编程:线程与进程的实战应用
      【10月更文挑战第4天】 本文深入探讨了Python中实现并发编程的两种主要方式——线程和进程,通过对比分析它们的特点、适用场景以及在实际编程中的应用,为读者提供清晰的指导。同时,文章还介绍了一些高级并发模型如协程,并给出了性能优化的建议。
      30 3
      |
      1月前
      |
      Java C++
      【多线程】JUC的常见类,Callable接口,ReentranLock,Semaphore,CountDownLatch
      【多线程】JUC的常见类,Callable接口,ReentranLock,Semaphore,CountDownLatch
      33 0
      |
      1月前
      |
      运维 API 计算机视觉
      深度解密协程锁、信号量以及线程锁的实现原理
      深度解密协程锁、信号量以及线程锁的实现原理
      33 1
      |
      2月前
      |
      负载均衡 Java 调度
      探索Python的并发编程:线程与进程的比较与应用
      本文旨在深入探讨Python中的并发编程,重点比较线程与进程的异同、适用场景及实现方法。通过分析GIL对线程并发的影响,以及进程间通信的成本,我们将揭示何时选择线程或进程更为合理。同时,文章将提供实用的代码示例,帮助读者更好地理解并运用这些概念,以提升多任务处理的效率和性能。
      60 3
      |
      1月前
      |
      Java 应用服务中间件 测试技术
      Java21虚拟线程:我的锁去哪儿了?
      【10月更文挑战第8天】
      32 0
      |
      1月前
      |
      安全 调度 数据安全/隐私保护
      iOS线程锁
      iOS线程锁
      26 0
      |
      1月前
      |
      Java API
      【多线程】乐观/悲观锁、重量级/轻量级锁、挂起等待/自旋锁、公平/非公锁、可重入/不可重入锁、读写锁
      【多线程】乐观/悲观锁、重量级/轻量级锁、挂起等待/自旋锁、公平/非公锁、可重入/不可重入锁、读写锁
      31 0
      |
      1月前
      |
      安全 Java 程序员
      【多线程-从零开始-肆】线程安全、加锁和死锁
      【多线程-从零开始-肆】线程安全、加锁和死锁
      43 0