多线程并发安全问题与线程锁
一、多线程并发安全问题
二、什么是线程锁及分类
三、synchronized关键字
多线程并发安全问题
当多个线程并发操作同一临界资源,由于线程切换时机不确定,导致操作临界资源的顺序出现混乱严重时可能导致系统瘫痪.
临界资源:操作该资源的全过程同时只能被单个线程完成.
想象一个场景:账户中有100万,A和B一起取钱,A取了50万,B想要取100万,这个时候无论谁先 取到了????,如果不加控制、另一个在再取????就会导致余额变成负数。
如果A取完????再让B来取这种现象就可以避免。
public class Account {private double balance;public void draw(double drawAccount) {if(balance >= drawAccount) {System.out.println(Thread.currentThread().getName() + " 取钱成功!吐出钞票:" + drawAccount);try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}balance -= drawAccount;System.out.println("余额为:" + balance);} else {System.out.println(Thread.currentThread().getName() + " 取钱失败!余额不足!");}}
}public class DrawThread extends Thread {private Account accountprivate double drawAccount;public DrawThread(String name, Account account, double drawAccount) {super(name);this.account = account;this.drawAccount = drawAccount;}public void run() {account.draw(drawAccount);}
}public class DrawTest { public static void main(String[] args) {Account account = new Account(1000);new DrawThread("甲", account, 800).start();new DrawThread("乙", account, 800).start();}
}
以上代码输出结果
甲,取钱成功!吐出钞票:800,余额为:200
已,取钱成功!吐出钞票:800,余额为:200
什么是线程锁及分类
线程锁:主要用来给方法、代码块加锁。当某个方法或者代码块使用锁时,那么在同一时刻至多仅有一个线程在执行该段代码。当有多个线程访问同一对象的加锁方法/代码块时,同一时间只有一个线程在执行,其余线程必须要等待当前线程执行完之后才能执行该代码段,但是,其余线程是可以访问该对象中的非加锁代码块的。
线程锁的分类
悲观锁、乐观锁
公平锁、非公平锁
重量级锁、轻量级(偏向锁)
重入锁(递归锁)与 不可重入锁(自旋锁)
悲观锁
顾名思义,就是比较悲观的锁,总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。
乐观锁
反之,总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。
公平锁
公平锁是指多个线程按照申请锁的顺序来获取锁,线程直接进入队列中排队,队列中的第一个线程才能获得锁。公平锁的优点是等待锁的线程不会饿死。缺点是整体吞吐效率相对非公平锁要低,等待队列中除第一个线程以外的所有线程都会阻塞,CPU唤醒阻塞线程的开销比非公平锁大。
非公平锁
非公平锁是多个线程加锁时直接尝试获取锁,能抢到锁到直接占有锁,抢不到才会到等待队列的队尾等待。但如果此时锁刚好可用,那么这个线程可以无需阻塞直接获取到锁,所以非公平锁有可能出现后申请锁的线程先获取锁的场景。非公平锁的优点是可以减少唤起线程的开销,整体的吞吐效率高,因为线程有几率不阻塞直接获得锁,CPU不必唤醒所有线程。缺点是处于等待队列中的线程可能会饿死,或者等很久才会获得锁。
偏向锁
偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。
轻量级
轻量级锁是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。
重量级锁
重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。
synchronized关键字
修饰方法:当一个方法使用synchronized修饰后,这个方法称为"同步方法",即:多个线程不能同时在方法内部执行.只能有先后顺序的一个一个进行. 将并发操作同一临界资源的过程改为同步执行就可以有效的解决并发安全问题.
修饰静态方法:当在静态方法上使用synchronized后,该方法是一个同步方法.由于静态方法所属类,所以一定具有同步效果. 静态方法使用的同步监视器对象为当前类的类对象(Class的实例).。
修改代码块:有效的缩小同步范围可以在保证并发安全的前提下尽可能的提高并发效率.同步块可以更准确的控制需要多个线程排队执行的代码片段.
public class SyncDemo {public static void main(String[] args) {Table table = new Table();Thread t1 = new Thread(){public void run(){while(true){int bean = table.getBean();
System.out.println(getName()+":"+bean);}}};Thread t2 = new Thread(){public void run(){while(true){int bean = table.getBean();
Thread.yield();System.out.println(getName()+":"+bean);}}};t1.start();t2.start();}
}class Table{private int beans = 20;//桌子上有20个豆子/*** 当一个方法使用synchronized修饰后,这个方法称为同步方法,多个线程不能* 同时执行该方法。* 将多个线程并发操作临界资源的过程改为同步操作就可以有效的解决多线程并发* 安全问题。* 相当于让多个线程从原来的抢着操作改为排队操作。*/public synchronized int getBean(){if(beans==0){throw new RuntimeException("没有豆子了!");}Thread.yield();return beans--;}
}//* 多线程并发安全问题* 当多个线程并发操作同一临界资源,由于线程切换的时机不确定,导致操作顺序出现* 混乱,严重时可能导致系统瘫痪。* 临界资源:同时只能被单一线程访问操作过程的资源。
public class Account {private String accountNo;private double balance;public synchronized void draw(double drawAccount) {if(balance >= drawAccount) {System.out.println(Thread.currentThread().getName() + " 取钱成功!吐出钞票:" + drawAccount);try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}balance -= drawAccount;System.out.println("余额为:" + balance);} else {System.out.println(Thread.currentThread().getName() + " 取钱失败!余额不足!");}}
}
接上面取钱的案列,输出结果为:
甲,取钱成功!吐出钞票:800,余额为:200
已,取钱失败!余额不足!
修饰静态方法:
/*** 静态方法上如果使用synchronized,则该方法一定具有同步效果。*/
public class SyncDemo {public static void main(String[] args) {Thread t1 = new Thread(){public void run(){Boo.dosome();}};Thread t2 = new Thread(){public void run(){Boo.dosome();}};t1.start();t2.start();}
}class Boo{/*** synchronized在静态方法上使用是,指定的同步监视器对象为当前类的类对象。* 即:Class实例。* 在JVM中,每个被加载的类都有且只有一个Class的实例与之对应,后面讲反射*/public synchronized static void dosome(){Thread t = Thread.currentThread();try {System.out.println(t.getName() + ":正在执行dosome方法...");Thread.sleep(5000);System.out.println(t.getName() + ":执行dosome方法完毕!");} catch (InterruptedException e) {e.printStackTrace();}}}
}
修改代码块:
public class SyncDemo {public static void main(String[] args) {Shop shop1 = new Shop();Shop shop2 = new Shop();Thread t1 = new Thread("邓先生"){public void run(){shop.buy();}};Thread t2 = new Thread("黄小姐"){public void run(){shop.buy();}};t1.start();t2.start();}
} class Shop{public void buy(){try {Thread t = Thread.currentThread();System.out.println(t.getName()+“:正在挑衣服”);Thread.sleep(5000);synchronized (this) {System.out.println(t.getName() + ":正在试衣服");Thread.sleep(5000);}System.out.println(t.getName()+"结账离开");} catch (InterruptedException e) {e.printStackTrace();}}
}
问题来了:
public class SyncDemo {public static void main(String[] args) {Shop shop1 = new Shop();Shop shop2 = new Shop();Thread t1 = new Thread("邓先生"){public void run(){shop.buy();}};Thread t2 = new Thread("黄小姐"){public void run(){shop.buy();}};t1.start();t2.start();}
} class Shop{public void buy(){try {Thread t = Thread.currentThread();System.out.println(t.getName()+“:正在挑衣服”);Thread.sleep(5000);synchronized (new Object()) {System.out.println(t.getName() + ":正在试衣服");Thread.sleep(5000);}System.out.println(t.getName()+"结账离开");} catch (InterruptedException e) {e.printStackTrace();}}
}
使用同步块时要指定同步监视器对象。该对象可以是java中任何
引用类型实例(语法层面上)。
但是该对象必须满足多个需要同步执行线程看到的必须是【同一个对象】。
选取有效且合适的锁对象原则:指定临界资源作为同步监视器对象(抢谁就锁谁)
synchronized ("abc") {//有效但不合适(不需要起作用时仍然发挥作用)
synchronized (new Object()) {//凡是new一定不行!
互斥锁:
当多个线程执行不同的代码片段,但是这些代码片段之间不能同时运行时就要设置为互斥的. 使用synchronized锁定多个代码片段,并且指定的同步监视器是同一个时,这些代码片段之间就是互斥的.
public class SyncDemo4 {public static void main(String[] args) {Foo foo = new Foo();Thread t1 = new Thread(){public void run(){foo.methodA();}};Thread t2 = new Thread(){public void run(){foo.methodB();}};t1.start();t2.start();}class Foo{public synchronized void methodA(){Thread t = Thread.currentThread();try {System.out.println(t.getName()+":正在执行A方法...");Thread.sleep(5000);System.out.println(t.getName()+":执行A方法完毕!");} catch (InterruptedException e) {e.printStackTrace();}}public synchronized void methodB(){Thread t = Thread.currentThread();try {System.out.println(t.getName()+":正在执行B法...");Thread.sleep(5000);System.out.println(t.getName()+":执行B方法完毕!");} catch (InterruptedException e) {e.printStackTrace();}}
}public class SyncDemo4 {public static void main(String[] args) {Boo boo = new Boo();Thread t1 = new Thread(){public void run(){boo.methodA();}};Thread t2 = new Thread(){public void run(){boo.methodB();}};t1.start();t2.start();}
}
运行结果:
Thread-0:正在执行A方法...
Thread-1:正在执行B方法...
Thread-1:执行B方法完毕!!!
Thread-0:执行A方法完毕!!!
死锁
现象:当两个线程分别持有一个锁对象的同时都在等待对方先释放锁时会形成一种僵持
局面,此时就是死锁
避免死锁的原则:
1:尽量避免嵌套synchronized
2:若避免不了,则多个线程执行嵌套时,持有锁对象的顺序要一致.
总结
什么是多线程并发安全问题:
当多个线程并发操作同一临界资源,由于线程切换时机不确定,导致执行顺序出现混乱。
解决办法:
将并发操作改为同步操作就可有效的解决多线程并发安全问题
- 同步与异步的概念:同步和异步都是说的多线程的执行方式。
多线程各自执行各自的就是异步执行,而多线程执行出现了先后顺序进行就是同步执行
- synchronized的两种用法
1.直接在方法上声明,此时该方法称为同步方法,同步方法同时只能被一个线程执行
2.同步块,推荐使用。同步块可以更准确的控制需要同步执行的代码片段。
**有效的缩小同步范围可以在保证并发安全的前提下提高并发效率**
- 同步监视器对象的选取:
对于同步的成员方法而言,同步监视器对象不可指定,只能是this
对于同步的静态方法而言,同步监视器对象也不可指定,只能是类对象
对于同步块而言,需要自行指定同步监视器对象,选取原则:
1.必须是引用类型
2.多个需要同步执行该同步块的线程看到的该对象必须是**同一个**
- 互斥性
当使用多个synchronized修饰了多个代码片段,并且指定的同步监视器都是同一个对象时,这些代码片段就是互斥的, 多个线程不能同时在这些代码片段上执行。
多线程并发安全问题与线程锁相关推荐
- JAVE SE 学习day_09:sleep线程阻塞方法、守护线程、join协调线程同步方法、synchronized关键字解决多线程并发安全问题
一.sleep线程阻塞方法 static void sleep(long ms) Thread提供的静态方法sleep可以让运行该方法的线程阻塞指定毫秒,超时后线程会自动回到RUNNABLE状态,等待 ...
- Java 线程安全问题及线程锁(读书笔记)
多线程安全问题: 首先整理多线程同步的知识点,开头肯定是要先探讨探讨多线程安全的问题.那么嘛叫线程安全问题呢? 答: 我们知道Jvm虚拟机的设计中线程的执行是抢占式的,线程的执行时间是由底层系统决定的 ...
- 解决多线程并发安全问题
解决多线程的并发安全问题,java无非就是加锁,具体就是两个方法 (1) Synchronized(java自带的关键字) (2) lock 可重入锁 (可重入锁这个包java.util.concur ...
- 对Java多线程编程的初步了解,实现多线程的三种方式以及多线程并发安全的线程同步机制
什么叫进程?什么叫线程? 进程相当于一个应用程序,线程就是进程中的一个应用场景或者说是一个执行单元,一个进程可以启动多个线程,每个线程执行不同的任务,一个线程不能单独存在,他必须是进程的一部分,当进程 ...
- python多线程_【python多线程02】各种线程锁
0x00 前言 本片文章讲述了小明同学在编写python多线程过程中遇到一些奇怪现象,小明根据这些奇怪现象挖掘背后的原因...通过遇到的问题,引申出全局解释器锁,同步锁,递归锁,信号量... 0x01 ...
- 线程并发编程之线程锁
实现并发的方式有多种,其中有进程.线程.基于异步事件机制的编程等等.而针对多线程编程应为同一进程下的多个线程之间是共享进程的用户地址空间和 pc 等资源.所以会存在着数据竞争的情况,故而就会涉及到线程 ...
- Java高并发编程:线程锁技术
目录 1 什么是线程锁 2 synchronized 1. 对象锁 2. 修饰对象方法 3. 类锁 4. 对象锁和类锁 5. 卖火车票示例 6. 生产一个消费一个示例 3 Lock 3.1 重入锁 R ...
- 【Java 并发编程】线程锁机制 ( 线程安全 | 锁机制 | 类锁 | 对象锁 | 轻量级锁 | 重量级锁 )
文章目录 一.线程安全 二.锁机制 ( 类锁 | 对象锁 ) 三.锁分类 ( 轻量级锁 | 重量级锁 ) 一.线程安全 多个线程同时访问 同一个共享变量 时 , 只要能保证 数据一致性 , 那么该变量 ...
- 【并发编程】线程锁--Synchronized、ReentrantLock(可重入锁)
在说锁之前,我们要明白为什么要加锁,不加锁会怎样? 在并发编程中,很容易出现线程安全问题,接下来我们看个很经典的例子--银行取钱,来看一下有关线程安全的问题. 取钱的流程可以分为一下几个步骤: 1.用 ...
最新文章
- 单例模式及getInstance()的用法
- php在线备忘录,备忘录模式(Memento)
- Struts2-整理笔记(三)结果处理跳转、获得servletAPI原生
- Win Phone 8 实现页面导航
- half-sync/half-async 和 Leader/Followers 模式的主要区别
- 2019计算机二级java软件_2019年计算机二级Java考试冲刺题及答案(2)
- 一次使用duplicate创建测试数据库的过程
- java mysql websocket_javaweb-ajax-websocket-mysql
- 现在的女孩找男朋友都是怎么考虑的?
- fasterrcnn论文_【论文解读】Yolo三部曲解读——Yolov1
- vue父子组件的传值
- python print%s s_python - print(%s's %s is %s. % \) 有具体代码,请问这种怎么解释?
- 如何搭建谷歌离线地图服务
- Django优化(减少数据库查询次数)---select_related和prefetch_related的使用
- Markdown博客系统的搭建与使用
- 企业微信网页授权及JS-SDK碰到检查域名所有权不通过的问题
- 计算机专业C语言编程学习重点:指针化难为易
- 录取麻省理工计算机博士全奖,你们要的麻省理工博士全奖录取来啦~~
- 【从零单排之微软面试100题系列】09二叉查找树的后序遍历
- 洛谷P1902 刺杀大使(二分答案+bfs验证)