Java多线程与各种锁

news/2025/5/21 3:35:57

Java多线程与各种锁

  • 一、Synchronize线程同步
  • 二、各种Lock锁
    • 1、普通锁
    • 2、公平锁与非公平锁
    • 3、乐观锁与悲观锁以及CAS优化乐观锁
    • 4、重入锁与重入自旋锁


一、Synchronize线程同步

public class BuyController {public static void main(String[] args) {MyThread myThread1 = new MyThread();new Thread(myThread1, "购票者1").start();new Thread(myThread1, "购票者2").start();new Thread(myThread1, "购票者3").start();}
}class MyThread implements Runnable {//票数是多个线程的共享资源private int ticket = 10;@Override// public void run() {  //原public synchronized void run() {while (ticket > 0) {try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "还剩下" + ticket-- + "张票");}}
}

在这里插入图片描述
也可以这么写

class MyThread2 implements Runnable {//票数是多个线程的共享资源private int ticket = 10;@Overridepublic void run() {synchronized (MyThread2.class) {while (ticket > 0) {try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "还剩下" + ticket-- + "张票");}}}
}

二、各种Lock锁

Synchronized在发生异常时会自动释放占有的锁,因此不会出现死锁;而Lock发生异常时,不会主动释放占有的锁,必须手动来释放锁,可能引起死锁的发生。

1、普通锁

/*** 服务*/
class MyService {private Lock lock = new ReentrantLock();public void testMethod() {lock.lock();try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();} finally {//不管是否有异常都要释放锁lock.unlock();}}
}/*** 线程*/
class MyThread3 implements Runnable {private MyService service;public MyThread3(MyService service) {this.service = service;}@Overridepublic void run() {service.testMethod();}
}class LockTest {public static void main(String[] args) {MyService service = new MyService();MyThread3 t1 = new MyThread3(service);MyThread3 t2 = new MyThread3(service);MyThread3 t3 = new MyThread3(service);new Thread(t1, "窗口1").start();new Thread(t2, "窗口2").start();new Thread(t3, "窗口3").start();}
}

2、公平锁与非公平锁

//公平锁
Lock nonFairLock=new ReentrantLock(true);
//默认非公平锁
Lock fairLock=new ReentrantLock(false);

重入锁

public class BuyController {public static void main(String[] args) {Lock lock = new ReentrantLock();lock.lock();lock.lock();lock.unlock();lock.unlock();}
}

举例

class LockTest2 {//默认是非公平锁//static ReentrantLock lock=new ReentrantLock();//公平锁static ReentrantLock lock = new ReentrantLock(true);public static void main(String[] args) {Runnable myRunnable = new Runnable() {@Overridepublic void run() {while (true) {try {lock.lock();System.out.println(Thread.currentThread().getName() + " 获得了锁对象");} finally {lock.unlock();}}}};for (int i = 0; i < 5; i++) {new Thread(myRunnable, "线程" + i).start();}}
}

如果是:static ReentrantLock lock=new ReentrantLock(true); 这就是公平锁, 可以看到系统会按照线程等待的先后时间顺序,有序的为每个线程分配锁对象,这个顺序一直就是 0-2-4-3-1。

在这里插入图片描述

如果是:static ReentrantLock lock=new ReentrantLock(); 这默认就是非公平锁,可以看到多线程执行之后,系统更倾向于让一个之前获得锁的线程再次获得锁,这显然就体现了非公平性。

在这里插入图片描述

3、乐观锁与悲观锁以及CAS优化乐观锁

在这里插入图片描述
根据从上面的概念描述我们可以发现:

  • 悲观锁适合写操作多的场景,先加锁可以保证写操作时数据正确。
  • 乐观锁适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升。
public class OptimisticPessimisticLock {//悲观锁的调用方式//synchronizedpublic synchronized void testMethod( ) {//操作同步资源}//ReentrantLockprivate ReentrantLock lock = new ReentrantLock();//需要保证多个线程使用同一个锁public void modifyPublicResources() {lock.lock();//操作同步资源lock.unlock();}//乐观锁的调用方式private AtomicInteger atomicInteger = new AtomicInteger();//需要保证多个线程使用同一个public void modifyPublicResources2() {//操作同步资源atomicInteger.incrementAndGet();//执行自增1}
}

我们可以发现悲观锁基本都是在显式的锁定之后再操作同步资源,synchronized和Lock这种方式又有一个名字,叫做互斥锁,一次只能有一个持有锁的线程进入,再加上还有不同线程争夺锁这个机制,效率比较低,所以又称“悲观锁”。
而乐观锁则直接去操作同步资源。那么,为何乐观锁能够做到不锁定同步资源也可以正确的实现线程同步呢?我们通过介绍乐观锁的主要实现方式 “CAS” 的技术原理来为大家解惑。

CAS算法涉及到三个操作数:

  • 需要读写的内存值 V。
  • 进行比较的值 A。
  • 要写入的新值 B。

实现思想 CAS(V, A, B),V为内存地址、A为预期原值,B为新值。如果内存地址的值与预期原值相匹配,那么将该位置值更新为新值。否则,说明已经被其他线程更新,处理器不做任何操作;无论哪种情况,它都会在 CAS 指令之前返回该位置的值。而我们可以使用自旋锁,循环CAS,重新读取该变量再尝试再次修改该变量,也可以放弃操作。

public class OptimisticPessimisticLock {private static int count = 0;public static void main(String[] args) throws InterruptedException {AtomicInteger atomicInteger = new AtomicInteger();//计数器用于阻塞CountDownLatch countDownLatch = new CountDownLatch(2);for (int i = 0; i < 2; i++) {new Thread(new Runnable() {@Overridepublic void run() {for (int j = 0; j < 1000; j++) {//CAS执行自增atomicInteger.incrementAndGet();//普通的自增count++;}countDownLatch.countDown();}}).start();}countDownLatch.await();System.out.println("atomicInteger:" + atomicInteger.get());System.out.println("count:" + count);}
}

java语言CAS底层如何实现?
利用unsafe提供的原子性操作方法。
什么是ABA问题?怎么解决?
当一个值从A变成B,又更新回A,普通CAS机制会误判通过检测。利用版本号比较可以有效解决ABA问题。

public class MyOptimisticLock {public Personnel findAndUpdate(List<Personnel> personnels) {//从数据库中获取所有员工列表List<Personnel> personnelList = findPersonnelList();if (personnelList != null && personnelList.size() > 0) {//获取第一个员工并修改Personnel personnel = personnelList.get(0);personnel.setName("Tom");Integer result = updatePersonnel(personnel);if (result == 1) {return personnel;} else {//已被更新 则再次获取findAndUpdate(personnelList);}} return null;}
}@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Personnel {private String name;private Integer age;private Integer version;
}
	<update id="updatePersonnel" parameterType="com.yuange.mapper.PersonnelUpdate">UPDATE t_personnel SET NAME = #{name} ,AGE = #{age} ,VERSION = VERSION + 1 WHERENAME = #{name}			AND VERSION = #{version}</update>	

简要说明:表设计时,需要往表里加一个version字段。每次查询时,查出带有version的数据记录,更新数据时,判断数据库里对应id的记录的version是否和查出的version相同。若相同,则更新数据并把版本号+1;若不同,则说明,该数据发送并发,被别的线程使用了,进行递归操作,再次执行递归方法,知道成功更新数据为止。
上述findAndUpdate方法即实现了一个乐观锁,作用是冲数据库里更新一条数据病返回前端。如果并发率大,一次请求可能则会重复执行很多次findAndUpdate,则性能低。如果并发很乐观,用户请求少,则不需要用synchronized,多线程时性能高。

乐观锁适用于写比较少的情况下,即冲突比较少发生,这样可以省去了锁的开销,加大了系统的整个吞吐量。
但如果经常产生冲突,乐观锁 的重复尝试 反倒会降低了性能,所以这种情况下用悲观锁就比较合适。

4、重入锁与重入自旋锁

在JAVA环境下 ReentrantLock 和synchronized 都是 可重入锁

/*** @author lichangyuan* @create 2021-10-11 14:08*/
public class ReentryLock implements Runnable {//synchronized版public synchronized void get() {System.out.println(Thread.currentThread().getId());set();}public synchronized void set() {System.out.println(Thread.currentThread().getId());}//ReentrantLock版ReentrantLock lock = new ReentrantLock();public void get2() {lock.lock();System.out.println(Thread.currentThread().getId());set2();lock.unlock();}public void set2() {lock.lock();System.out.println(Thread.currentThread().getId());lock.unlock();}@Overridepublic void run() {get();get2();}public static void main(String[] args) {ReentryLock reentryLock = new ReentryLock();new Thread(reentryLock).start();new Thread(reentryLock).start();}
}

在这里插入图片描述
我们以自旋锁作为例子

class SpinLock1 {private AtomicReference<Thread> owner = new AtomicReference<>();public void lock() {Thread current = Thread.currentThread();while (!owner.compareAndSet(null, current)) {}}public void unlock() {Thread current = Thread.currentThread();owner.compareAndSet(current, null);}
}
  1. 若有同一线程两调用lock() ,会导致第二次调用lock位置进行自旋,产生了死锁
    说明这个锁并不是可重入的。(在lock函数内,应验证线程是否为已经获得锁的线程)
  2. 若1问题已经解决,当unlock()第一次调用时,就已经将锁释放了,实际上不应释放锁。
    (采用计数次进行统计)

该自旋锁即为可重入锁。

class SpinLock2 {private AtomicReference<Thread> owner = new AtomicReference<>();private 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();//如果当前count值不为0说明前面有重入锁发生且未解锁,知道值递减为0则解锁当前线程if (current == owner.get()) {if (count != 0) {count--;} else {owner.compareAndSet(current, null);}}}
}
文章来源:https://blog.csdn.net/weixin_46146718/article/details/120268413
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:https://dhexx.cn/news/show-18326.html

相关文章

Word转图片(使用Spire.doc)

Spire.Doc for .NET是一款由E-iceblue公司开发的专业的Word .NET类库。支持.net&#xff0c;WPF&#xff0c;Silverlight&#xff0c; 下载地址&#xff1a;http://www.e-iceblue.com/Download/download-word-for-net-now.html 安装后&#xff0c;找到spire.doc.dll。引入项目。…

SPARK-SQL - Dataset创建方式汇总

创建方式如下 从RDD[T] Encoders中创建从List Encoders中创建 示例代码 import org.apache.spark.api.java.JavaRDD; import org.apache.spark.api.java.JavaSparkContext; import org.apache.spark.rdd.RDD; import org.apache.spark.sql.Dataset; import org.apache.spar…

Zookeeper分布式协调

Zookeeper分布式协调一、Zookeeper是什么&#xff1f;1、开启zookeeper服务及使用二、使用zookeeper1、连接zookeeper工具类2、参数介绍3、监听服务上下线提示4、分布式锁三、CuratorLock框架实现分布式锁四、实践五、其他1、如何关闭 org.apache.zookeeper.clientcnxn 的(控制…

undefined reference to libiconv_open ext/iconv/.libs/iconv.o by install phpsource

错误信息&#xff1a;ext/iconv/.libs/iconv.o(.text0x30e2): In function php_iconv_stream_filter_factory_create:/home/jjdai/work/zhupiter/php-5.2.0/ext/iconv/iconv.c:2419: undefined reference to libiconv_opencollect2: ld returned 1 exit statusmake: *** [sapi/…

SPARK-SQL - RDD/Dataset/DataFrame的互相转换

转换用到的方法如下 rdd()&#xff0c;as()&#xff0c;toDF()代码示例 import org.apache.spark.api.java.JavaRDD; import org.apache.spark.api.java.JavaSparkContext; import org.apache.spark.rdd.RDD; import org.apache.spark.sql.*; import pojo.Dog;import java.uti…

什么是阶梯电价

什么是阶梯电价&#xff1f; 阶梯电价全名为阶梯式累进电价&#xff0c;是指将现行单一形式的居民电价&#xff0c;改为按照用户消费的电量分段定价&#xff0c;用电价格随用电量增加逐级递增的一种电价定价机制。即把居民每个月的用电分成基本用电、正常用电、高质量用电三档。…

Elasticsearch入门学习

Elasticsearch一、安装二、操作索引1、介绍2、创建一个空索引&#xff08;库&#xff09;3、修改副本&#xff08;库&#xff09;4、删除索引&#xff08;库&#xff09;三、基础使用1、数据插入数据2、局部更新3、删除数据4、查询数据5、两种查询模式6、复杂搜索7、全文搜索8、…

hdu2896-病毒侵袭

病毒侵袭 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 7924 Accepted Submission(s): 2061 Problem Description当太阳的光辉逐渐被月亮遮蔽&#xff0c;世界失去了光明&#xff0c;大地迎来最黑暗的时刻。。…

MySQL 性能优化的最佳20多条经验分享[转]

今天&#xff0c;数据库的操作越来越成为整个应用的性能瓶颈了&#xff0c;这点对于Web应用尤其明显。关于数据库的性能&#xff0c;这并不只是DBA才需要担心的事&#xff0c;而这更是我们程序员需要去关注的事情。当我们去设计数据库表结构&#xff0c;对操作数据库时&#xf…

SPARK-SQL - 读写数据的时候使用分区 partitionBy()

关键代码 partitionBy() dataset_1.write().mode(SaveMode.Overwrite).partitionBy("year", "month", "day").parquet(Utils.BASE_PATH "/trackerSession_partition"); 代码示例 import org.apache.spark.sql.Dataset; import org…