大数据必学Java基础(七十八):线程安全问题

举报
Lansonli 发表于 2022/11/08 00:58:03 2022/11/08
【摘要】 线程安全问题出现问题:(1)出现了两个10张票或者3个10张票:;(2)出现0,-1,-2可能:;上面的代码出现问题:出现了重票,错票 --》 线程安全引起的问题原因:多个线程,在争抢资源的过程中,导致共享的资源出现问题。一个线程还没执行完,另一个线程就参与进来了,开始争抢。解决:在我的程序中加入“锁” --》 加同步 --》 同步监视器一、方法1:同步代码块【1】同步代码块演示1:pack...

线程安全问题

出现问题:

(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】解决方法

减少同步资源的定义,避免嵌套同步

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。