多线程和高并发关于Java锁的问题

本文深入解析Java中的锁机制,包括公平锁与非公平锁的区别,可重入锁的工作原理,自旋锁的优劣,读写锁的应用场景,以及Synchronized与Lock的不同之处。并通过示例代码展示了如何实现线程间的有序执行。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Java锁

公平锁/非公平锁

概念:所谓公平锁,就是多个线程按照申请锁的顺序来获取锁,类似排队,先到先得。而非公平锁,则是多个线程抢夺锁,会导致优先级反转饥饿现象

区别:公平锁在获取锁时先查看此锁维护的等待队列为空或者当前线程是等待队列的队首,则直接占有锁,否则插入到等待队列,FIFO原则。非公平锁比较粗鲁,上来直接先尝试占有锁,失败则采用公平锁方式。非公平锁的优点是吞吐量比公平锁更大。

synchronizedjuc.ReentrantLock默认都是非公平锁ReentrantLock在构造的时候传入true则是公平锁

 /**
     * Creates an instance of {@code ReentrantLock}.
     * This is equivalent to using {@code ReentrantLock(false)}.
     * 默认创建的非公平锁
     */
    public ReentrantLock() {
        sync = new NonfairSync();
    }

    /**
     * Creates an instance of {@code ReentrantLock} with the
     * given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

可重入锁/递归锁

可重入锁又叫递归锁,指的同一个线程在外层方法获得锁时,进入内层方法会自动获取锁。也就是说,线程可以进入任何一个它已经拥有锁的代码块。比如get方法里面有set方法,两个方法都有同一把锁,得到了get的锁,就自动得到了set的锁。

就像有了家门的锁,厕所、书房、厨房就为你敞开了一样。可重入锁可以避免死锁的问题。

public class Recursive {
	public static void main(String[] args) {
		RecuriveDemo recuriveDemo = new RecuriveDemo();
		new Thread(new Runnable() {
			public void run() {
				recuriveDemo.lockA();
			}
		}, "A").start();
	}

}

class RecuriveDemo {

	public synchronized void lockA() {
		System.out.println(Thread.currentThread().getName() + "\t come in lockA");
		lockB();
	}

	public synchronized void lockB() {
		System.out.println(Thread.currentThread().getName() + "\t come in lockB");
	}
}

在这里插入图片描述

锁的配对

锁之间要配对,加了几把锁,最后就得解开几把锁,下面的代码编译和运行都没有任何问题。但锁的数量不匹配会导致死循环。

/**
 * @author MT
 *
 */
public class LockDemo {
	public static void main(String[] args) {
		LockPairing lockPairing = new LockPairing();
		new Thread(new Runnable() {
			public void run() {
				lockPairing.lockA();
			}
		}, "A").start();
	}

}

class LockPairing{
	private Lock lock = new ReentrantLock();
	private Lock lock2 = new ReentrantLock();
	
	public  void lockA() {
		lock.lock();
		lock2.lock();
		try {
			lockB();
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
			lock2.unlock();
			lock.unlock();
		}
		
	
	
	}

	public  void lockB() {
		System.out.println(Thread.currentThread().getName() + "\t come in lockB");
	}
}

在这里插入图片描述

自旋锁

所谓自旋锁,就是尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取。自己在那儿一直循环获取,就像“自旋”一样。这样的好处是减少线程切换的上下文开销,缺点是会消耗CPU。CAS底层的getAndAddInt就是自旋锁思想。

/**
 * @author MT
 *
 */
public class SpinLock {
	AtomicReference<Thread> reference = new AtomicReference<>();
	
	private void myLock(){
		Thread thread = Thread.currentThread();
		System.out.println(thread.getName()+ "come in");
		while (!reference.compareAndSet(null, thread)) {
			
		}
	}
	
	private void unLock(){
		Thread thread = Thread.currentThread();
		reference.compareAndSet(thread, null);
		System.out.println(thread.getName() + "go out");
	}
	
	public static void main(String[] args) {
		SpinLock spinLock = new SpinLock();
		new Thread( new Runnable() {
			public void run() {
				spinLock.myLock();
				try {
					TimeUnit.SECONDS.sleep(5);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				spinLock.unLock();
			}
		},"aa").start();;
		
		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		new Thread(new Runnable() {
			public void run() {
				spinLock.myLock();
				spinLock.unLock();
			}
		},"bb").start();;
		
		
	}

}

读写锁/独占/共享锁

读锁共享的写锁独占的juc.ReentrantLocksynchronized都是独占锁,独占锁就是一个锁只能被一个线程所持有。有的时候,需要读写分离,那么就要引入读写锁,即juc.ReentrantReadWriteLock

比如缓存,就需要读写锁来控制。缓存就是一个键值对,以下Demo模拟了缓存的读写操作,读的get方法使用了ReentrantReadWriteLock.ReadLock(),写的put方法使用了ReentrantReadWriteLock.WriteLock()。这样避免了写被打断,实现了多个线程同时读。


/**
 * @author MT
 *
 */
public class ReadWriteLock {
	private volatile Map<String, String> map = new HashMap<String, String>();
	private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
	
	public void write(String key , String value){
		lock.writeLock().lock();
		try {
			System.out.println(Thread.currentThread().getName() + "正在写入" + key);
			map.put(key, value);
			try {
				TimeUnit.MICROSECONDS.sleep(500);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			 
			System.out.println(Thread.currentThread().getName() + "写入完成");
			
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
			lock.writeLock().unlock();
		}
	
	}
	
	private void read(String key ){
		
		lock.readLock().lock();
		try {
			System.out.println(Thread.currentThread().getName() + "正在读取");
			String value = map.get(key);
			try {
				TimeUnit.MICROSECONDS.sleep(500);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			 
			System.out.println(Thread.currentThread().getName() + "读取到" + value);
			
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
			lock.readLock().unlock();
		}
		
	}
	
	public static void main(String[] args) {
		ReadWriteLock readWriteLock = new ReadWriteLock();
		
		for(int i = 0; i < 5; i++){
			final int temp = i ;
			new Thread( new Runnable() {
				
				public void run() {
					readWriteLock.write(temp + "", temp + "");
				}
			}).start();;
		}
		
		for(int i = 0; i < 5; i++){
			final int temp = i ;
			new Thread( new Runnable() {
				
				public void run() {
					readWriteLock.read(temp + "");
				}
			}).start();;
		}
	}

}

在这里插入图片描述

Synchronized和Lock的区别

synchronized关键字和java.util.concurrent.locks.Lock都能加锁,两者有什么区别呢?

  1. 原始构成sync是JVM层面的,底层通过monitorentermonitorexit来实现的。Lock是JDK API层面的。(sync一个enter会有两个exit,一个是正常退出,一个是异常退出)
  2. 使用方法sync不需要手动释放锁,而Lock需要手动释放。
  3. 是否可中断sync不可中断,除非抛出异常或者正常运行完成。Lock是可中断的,通过调用interrupt()方法。
  4. 是否为公平锁sync只能是非公平锁,而Lock既能是公平锁,又能是非公平锁。
  5. 绑定多个条件sync不能,只能随机唤醒。而Lock可以通过Condition来绑定多个条件,精确唤醒。

实现三个线程顺序执行 A线程打印5遍,B线程打印十遍,C线程打印十五遍,循环十次


package com.matao.concurrent;

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

/**
 * @author MT
 *
 */
public class Demo1 {
	public static void main(String[] args) {
	PrintDemo printDemo	= new PrintDemo();
	
	new Thread(new Runnable() {
		public void run() {
			for (int i = 0; i < 10; i++) {
				printDemo.print5();
			}
		}
	},"aa").start();
	
	new Thread(new Runnable() {
		public void run() {
			for (int i = 0; i < 10; i++) {
				printDemo.print10();
			}
		}
	},"bb").start();
	
	
	new Thread(new Runnable() {
		public void run() {
			for (int i = 0; i < 10; i++) {
				printDemo.print15();
			}
		}
	},"cc").start();
	}


}

class PrintDemo{
	
	volatile int number = 1;
	private ReentrantLock lock = new ReentrantLock();
	private Condition c1 = lock.newCondition();
	private Condition c2 = lock.newCondition();
	private Condition c3 = lock.newCondition();
	
	public void print5(){
		lock.lock();
		try {
			//判断
			while (number !=1) {
				c1.await();
			}
			
			//打印
			for(int i =1; i <= 5;i++){
				System.out.println(Thread.currentThread().getName() +"打印"+ i);
			}
			//通知
			number = 2;
			c2.signal();
			
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
			lock.unlock();
		}
		
	}
	
	public void print10(){
		lock.lock();
		try {
			//判断
			while (number !=2) {
				c2.await();
			}
			
			//打印
			for(int i =1; i <= 10;i++){
				System.out.println(Thread.currentThread().getName() +"打印"+ i);
			}
			//通知
			number = 3;
			c3.signal();
			
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
			lock.unlock();
		}
		
	}
	
	public void print15(){
		lock.lock();
		try {
			//判断
			while (number !=  3) {
				c3.await();
			}
			
			//打印
			for(int i =1; i <= 15;i++){
				System.out.println(Thread.currentThread().getName() +"打印"+ i);
			}
			//通知
			number = 1;
			c1.signal();
			
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
			lock.unlock();
		}
		
	}
	
	
}

CountDownLatch

CountDownLatch内部维护了一个计数器,只有当计数器==0时,某些线程才会停止阻塞,开始执行。

CountDownLatch主要有两个方法,countDown()来让计数器-1,await()来让线程阻塞。当count==0时,阻塞线程自动唤醒。
班长关门:main线程是班长,6个线程是学生。只有6个线程运行完毕,都离开教室后,main线程班长才会关教室门。

CyclicBarrier

CountDownLatch是减,而CyclicBarrier是加,理解了CountDownLatchCyclicBarrier就很容易。
王者荣耀:我们在加载游戏的时候,是相互等待的,线程之间相互等待,只有十个玩家都加载完成,十个玩家才能开始游戏,区别就是CountDownLatch是一个等多个线程,而CyclicBarrier是线程相互等待

/**
 * 
 */
package com.matao.concurrent;

import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;

/**
 * @author MT
 *
 */
public class Player implements Runnable{
	private  String hero;
	private CyclicBarrier barrier;
	//private CountDownLatch latch;

	public Player(String hero, CyclicBarrier barrier) {
		this.hero = hero;
		this.barrier = barrier;
	}
	
	@Override
	public void run() {
		try {
			TimeUnit.SECONDS.sleep(new Random().nextInt(5));
			System.out.println(hero + "已经加载完成,等待别的玩家加载");
			barrier.await();
			System.out.println(hero + "看到别的玩家加载完成,开始游戏");
		} catch (Exception e) {
			e.printStackTrace();
		}
		
	}

}



package com.matao.concurrent;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author MT
 *
 */
public class CyclicBarrierDemo {
	public static void main(String[] args) {
		String[] heros = {"孙悟空","妲己","安其拉","李白","狄仁杰","张飞"};
		ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(6);
		CyclicBarrier cyclicBarrier = new CyclicBarrier(6);
		for (int i = 0; i < heros.length; i++) {
			newFixedThreadPool.execute(new Player(heros[i], cyclicBarrier));
		}
		newFixedThreadPool.shutdown();
	}

}


在这里插入图片描述

Semaphore

CountDownLatch的问题是不能复用。比如count=3,那么加到3,就不能继续操作了。而Semaphore可以解决这个问题,比如6辆车3个停车位,对于CountDownLatch只能停3辆车,而Semaphore可以停6辆车,车位空出来后,其它车可以占有,这就涉及到了Semaphore.accquire()Semaphore.release()方法。

public static void main(String[] args) {
		Semaphore semaphore=new Semaphore(3);
		for (int i = 1; i <=6 ; i++) {
		    new Thread(()->{
		        try {
		            //占有资源
		            semaphore.acquire();
		            System.out.println(Thread.currentThread().getName()+"\t抢到车位");
		            try{ TimeUnit.SECONDS.sleep(3);} catch (Exception e){e.printStackTrace(); }
			    System.out.println(Thread.currentThread().getName()+"\t停车3秒后离开车位");
			    } 
			    catch (InterruptedException e) {e.printStackTrace();} 
			    //释放资源
			    finally {semaphore.release();}
		    },String.valueOf(i)).start();
		}
	}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值