大数据必学Java基础(七十八):线程安全问题
线程安全问题
出现问题:
(1)出现了两个10张票或者3个10张票:
(2)出现0,-1,-2可能:
上面的代码出现问题:
出现了重票,错票 --》 线程安全引起的问题
原因:多个线程,在争抢资源的过程中,导致共享的资源出现问题。一个线程还没执行完,另一个线程就参与进来了,开始争抢。
解决:
在我的程序中加入“锁” --》 加同步 --》 同步监视器
一、方法1:同步代码块
【1】同步代码块演示1:
package com.lanson.test04;
/**
* @author : Lansonli
*/
public class BuyTicketThread implements Runnable {
int ticketNum = 10;
@Override
public void run() {
//此处有1000行代码
for (int i = 1; i <= 100 ; i++) {
synchronized (this){//把具有安全隐患的代码锁住即可,如果锁多了就会效率低 --》this就是这个锁
if(ticketNum > 0){
System.out.println("我在"+Thread.currentThread().getName()+"买到了北京到哈尔滨的第" + ticketNum-- + "张车票");
}
}
}
//此处有1000行代码
}
}
复制
【2】同步代码块演示2:
public class BuyTicketThread extends Thread {
public BuyTicketThread(String name){
super(name);
}
//一共10张票:
static int ticketNum = 10;//多个对象共享10张票
//每个窗口都是一个线程对象:每个对象执行的代码放入run方法中
@Override
public void run() {
//每个窗口后面有100个人在抢票:
for (int i = 1; i <= 100 ; i++) {
synchronized (BuyTicketThread.class){//锁必须多个线程用的是同一把锁!!!
if(ticketNum > 0){//对票数进行判断,票数大于零我们才抢票
System.out.println("我在"+this.getName()+"买到了从北京到哈尔滨的第" + ticketNum-- + "张车票");
}
}
}
}
}
复制
【3】同步监视器总结:
总结1:认识同步监视器(锁子) -- synchronized(同步监视器){ }
1)必须是引用数据类型,不能是基本数据类型
2)也可以创建一个专门的同步监视器,没有任何业务含义
3)一般使用共享资源做同步监视器即可
4)在同步代码块中不能改变同步监视器对象的引用
5)尽量不要String和包装类Integer做同步监视器
6)建议使用final修饰同步监视器
总结2:同步代码块的执行过程
1)第一个线程来到同步代码块,发现同步监视器open状态,需要close,然后执行其中的代码
2)第一个线程执行过程中,发生了线程切换(阻塞 就绪),第一个线程失去了cpu,但是没有开锁open
3)第二个线程获取了cpu,来到了同步代码块,发现同步监视器close状态,无法执行其中的代码,第二个线程也进入阻塞状态
4)第一个线程再次获取CPU,接着执行后续的代码;同步代码块执行完毕,释放锁open
5)第二个线程也再次获取cpu,来到了同步代码块,发现同步监视器open状态,拿到锁并且上锁,由阻塞状态进入就绪状态,再进入运行状态,重复第一个线程的处理过程(加锁)
强调:同步代码块中能发生CPU的切换吗?能!!! 但是后续的被执行的线程也无法执行同步代码块(因为锁仍旧close)
总结3:其他
1)多个代码块使用了同一个同步监视器(锁),锁住一个代码块的同时,也锁住所有使用该锁的所有代码块,其他线程无法访问其中的任何一个代码块
2)多个代码块使用了同一个同步监视器(锁),锁住一个代码块的同时,也锁住所有使用该锁的所有代码块, 但是没有锁住使用其他同步监视器的代码块,其他线程有机会访问其他同步监视器的代码块
二、方法2:同步方法
【1】代码展示:
public class BuyTicketThread implements Runnable {
int ticketNum = 10;
@Override
public void run() {
//此处有1000行代码
for (int i = 1; i <= 100 ; i++) {
buyTicket();
}
//此处有1000行代码
}
public synchronized void buyTicket(){//锁住的是this
if(ticketNum > 0){
System.out.println("我在"+Thread.currentThread().getName()+"买到了北京到哈尔滨的第" + ticketNum-- + "张车票");
}
}
}
复制
public class BuyTicketThread extends Thread {
public BuyTicketThread(String name){
super(name);
}
//一共10张票:
static int ticketNum = 10;//多个对象共享10张票
//每个窗口都是一个线程对象:每个对象执行的代码放入run方法中
@Override
public void run() {
//每个窗口后面有100个人在抢票:
for (int i = 1; i <= 100 ; i++) {
buyTicket();
}
}
public static synchronized void buyTicket(){//锁住的 同步监视器: BuyTicketThread.class
if(ticketNum > 0){//对票数进行判断,票数大于零我们才抢票
System.out.println("我在"+Thread.currentThread().getName()+"买到了从北京到哈尔滨的第" + ticketNum-- + "张车票");
}
}
}
复制
【2】总结:
总结1:
多线程在争抢资源,就要实现线程的同步(就要进行加锁,并且这个锁必须是共享的,必须是唯一的。
咱们的锁一般都是引用数据类型的。
目的:解决了线程安全问题。
总结2:关于同步方法
1) 不要将run()定义为同步方法
2) 非静态同步方法的同步监视器是this
静态同步方法的同步监视器是 类名.class 字节码信息对象
3) 同步代码块的效率要高于同步方法
原因:同步方法是将线程挡在了方法的外部,而同步代码块锁将线程挡在了代码块的外部,但是却是方法的内部
4) 同步方法的锁是this,一旦锁住一个方法,就锁住了所有的同步方法;同步代码块只是锁住使用该同步监视器的代码块,而没有锁住使用其他监视器的代码块
三、方法3:Lock锁
【1】Lock锁引入
JDK1.5后新增新一代的线程同步方式:Lock锁
与采用synchronized相比,lock可提供多种锁方案,更灵活
synchronized是Java中的关键字,这个关键字的识别是靠JVM来识别完成的呀。是虚拟机级别的。
但是Lock锁是API级别的,提供了相应的接口和对应的实现类,这个方式更灵活,表现出来的性能优于之前的方式。
【2】代码演示
package com.lanson.test04;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author : Lansonli
*/
public class BuyTicketThread implements Runnable {
int ticketNum = 10;
//拿来一把锁:
Lock lock = new ReentrantLock();//多态 接口=实现类 可以使用不同的实现类
@Override
public void run() {
//此处有1000行代码
for (int i = 1; i <= 100 ; i++) {
//打开锁:
lock.lock();
try{
if(ticketNum > 0){
System.out.println("我在"+Thread.currentThread().getName()+"买到了北京到哈尔滨的第" + ticketNum-- + "张车票");
}
}catch (Exception ex){
ex.printStackTrace();
}finally {
//关闭锁:--->即使有异常,这个锁也可以得到释放
lock.unlock();
}
}
//此处有1000行代码
}
}
复制
【3】Lock和synchronized的区别
1)Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁
2)Lock只有代码块锁,synchronized有代码块锁和方法锁
3)使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
【4】优先使用顺序
Lock -- 同步代码块(已经进入了方法体,分配了相应资源)-- 同步方法(在方法体之外)
四、线程同步的优缺点
【1】对比
1)线程安全,效率低
2)线程不安全,效率高
【2】可能造成死锁
死锁
1)不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
2)出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
【3】代码演示
public class TestDeadLock implements Runnable {
public int flag = 1;
static Object o1 = new Object(),o2 = new Object();
public void run(){
System.out.println("flag=" + flag);
// 当flag==1锁住o1
if (flag == 1) {
synchronized (o1) {
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
// 只要锁住o2就完成
synchronized (o2) {
System.out.println("2");
}
}
}
// 如果flag==0锁住o2
if (flag == 0) {
synchronized (o2) {
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
// 只要锁住o1就完成
synchronized (o1) {
System.out.println("3");
}
}
}
}
public static void main(String[] args) {
// 实例2个线程类
TestDeadLock td1 = new TestDeadLock();
TestDeadLock td2 = new TestDeadLock();
td1.flag = 1;
td2.flag = 0;
// 开启2个线程
Thread t1 = new Thread(td1);
Thread t2 = new Thread(td2);
t1.start();
t2.start();
}
}
【4】解决方法
减少同步资源的定义,避免嵌套同步
- 点赞
- 收藏
- 关注作者
评论(0)