0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

线程安全怎么办

科技绿洲 来源:Java技术指北 作者:Java技术指北 2023-10-10 15:00 次阅读

线程安全一直是多线程开发中需要注意的地方,可以说,并发安全保证了所有的数据都安全。

1 线程不安全示例

线程安全其实是多线程编程里面的一个核心点,所有的设计和代码都是为了实现线程的高效与安全。

多线程中有几个比较核心概念,即原子性,可见性,顺序性。那么线程安全也会围绕着这三个核心来展开喽。

下面我们看一两个简单的问题多线程。

简单买票的线程安全问题

public class ThreadSafeDemo3 {
    public static void main(String[] args) throws InterruptedException {
        TicketStation station = new TicketStation();
        new Thread(station,"软软").start();
        new Thread(station,"冰冰").start();
        new Thread(station,"指北君").start();
    }
}
class TicketStation implements Runnable{
    int ticketCount = 10;
    boolean hasTicket = true;
    @Override
    public void run() {
        while(hasTicket){buyTicket();}
    }
    private void buyTicket(){
        if (ticketCount < 1) {
            hasTicket = false;
            return;
        }
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " get the ticket"+ ticketCount--);
    }
}

运行几遍就有可能会出现下面的错误不预期的结果。一个线程卖完了票,但是另外两个线程都还不知道。

图片
image-20210926221216385

多线程操作非线程安全对象问题

public class ThreadSafeDemo2 {
    public static void main(String[] args) throws InterruptedException {
        List< String > list = new ArrayList<  >();
        for (int i =0 ;i< 20 ;i++) {
            new Thread(() - > {
                for (int j = 0; j < 5; j++) {
                    list.add(Thread.currentThread().getName() + j);
                }
            }, "thread" + i).start();
        }
        Thread.sleep(1000*3);
        System.out.println(list.size());
    }
}

以上代码多执行几次之后会,所得list的size不会等于100。问题就在于多线程操作同一个线程不安全的List的时候,会是结果与预期不符,出现线程安全问题。

以上是两个线程不安全的示例,而对于线程安全,应该要做到如下:

当多个线程访问某个方法的时候,不管你通过怎样的调用方法,或者说这些线程如何交替执行,我们在主程序中不需要去做任何的同步,这个类的行为都是我们设想的正确行为,那么我们可以说这个类是线程安全的。即可以保证原子性,可见性,顺序性。

2 并发安全的问题根源

线程不安全指的是多线程并发执行某个代码时,产生了逻辑上的错误,结果和预期值不相同。

其原因可以总结如下:

  • Java线程是抢占执行的。
  • 有些操作不是原子的,cpu在处理某一个线程的时候,有可能被其他线程抢去做工。
  • 内存共享可变。
  • 指令重排序:Java编译器在编译代码时,会对最终执行的指令重排序,它会保证原有逻辑不变的情况下,提高程序的运行效率。

3 线程安全不是绝对的

《深入理解JVM》中有讲到如果要保证绝对线程安全,在大多数的应用场景下是难以做到的,或者说很难做到,即使做到,也会付出很大的代价。而且在Java中标注的某些线程安全的类也不是绝对的线程安全,也需要在调用时使用一些额外操作。

我们大多时候都是尽量保证线程的相对安全,对一个对象单独操作的时候保证线程安全,而对于一些特殊的调用情况,我们则需要采取一些同步操作付诸。

4 线程安全的实现方法

我们在写代码的时候,保证线程安全的方法有多种,下面我们介绍几种方式。

4.1 互斥同步

互斥的特点是在同一时刻只有一个线程获得执行权利,其余线程则会等待。( 同一时刻,只有一个线程在操作共享数据 ) 互斥是实现同步的一种手段, 临界区、互斥量、信号量都是主要的互斥实现方式。即通过实现互斥来最终完成同步的目的。

4.1.1 Synchronized

synchronized是同步锁,主要用来控制线程同步,保证某个锁住的内容不被多个线程同步执行。上述买票的例子中,在buyTicket方法上加上synchronized关键字,就可以使线程同步执行了。

private synchronized  void  buyTicket(){}

其中synchronized 使用有几点注意:

  1. 加到非静态方法前,表示锁this,即当前对象
  2. 加到静态方法前,表示锁当前类的所有类对象
4.1.2 Lock

Lock 是Java1.6之后引入的。使用Lock可以对锁进行多种操作,可以手动的获取锁,释放锁。

我们用ReentrantLock (ReentrantLock传送门。。。)改写上述购票行为。

class TicketLockStation implements Runnable{
    private Lock lock = new ReentrantLock();
    int ticketCount = 10;
    boolean hasTicket = true;
    @Override
    public void run() {
        while(hasTicket){buyTicket();}
    }
    private void  buyTicket(){
        lock.lock();
        try {
        if (ticketCount < 1) {
            hasTicket = false;
            return;
        }
        Thread.sleep(1000);
        System.out.println(Thread.currentThread().getName() + " get the ticket"+ ticketCount--);

        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

以上为简单Lock示例,其中Lock中还有tryLock()(如果获取不到锁立即返回), tryLock(long time, TimeUnit unit)(一段时间后获取不到锁则返回)等方法。

上边就是Lock的简单示例。

4.2 非阻塞同步

非阻塞同步可以描述为 基于冲突检测的乐观并发策略 ,关键点就是冲突检测以及乐观的并发策略。

冲突检测是指当发生共享数据抢夺的话,我们会进行重试检测,直到成功为止。而乐观的并发策略的实现大多时候都不需要挂起线程。

4.2.1 CAS

CAS(Compare And Swap)是非阻塞的一个实现,其核心指令有3个操作数,分别为内存地址V, 旧值A,新值B。当CAS执行时当 V的内存地址对应的值与A匹配时,本操作就会用B来更新V对应的值,否则不执行更新。但无论是否更新了V对应的值,都会返回V处对应的旧值。而且此操作为原子操作。

CAS有一个缺点就是ABA问题,V处的值原来是A,后来变成了B,然后又变成了A。使用CAS检查的时候发现其值没变化,然而实际上已经发生了变化。对于解决ABA问题,可以使用版本号的思路来解决,在更新变量的时候把版本号加一。然后对比的时候也对比版本号,版本号与值全都相等则执行更新。

4.3 无同步方案

保证线程安全的方法中,也并不是一定要使用同步。同步只是在保证共享数据在有竞争条件的时候使用。如果有方法可以避免共享数据的竞争,那么自然就不需要任何同步操作去保证数据的正确。所以在有一些场景下,代码自身就已经保证了线程安全,而无须使用同步方法。

4.3.1 栈封闭

其实主要就是尽量保证数据的操作在一个栈帧中,也就是局部变量,避免过多的去操作共享内存数据。

4.3.2 线程本地存储

如果代码中所需的数据必须与其他代码共享,那么就可以看看这些共享数据的代码是否能保证在同一个线程中执行。如果可以保证共享数据在同一个线程之内是可见的,那么线程之间也就不会出现数据的竞争。

ThreadLocal就是一个最典型的例子,Web应用中的Request也是这样的思路。

4.3.3 可重入代码 (Reentrant Code)

可重入代码指在代码执行的任何时刻中断它,转而去执行另外一段代码,而控制权返回后,原来的程序不会出现任何错误。所有的可重入代码都是线程安全的。一般而言可重入代码不依赖存储在堆上的数据以及公共的系统资源,用到的状态量都是有参数传入,或者说不调用,非可重入的方法等。

总结

关于线程安全,我们可以总结以下的一些思路。

  1. 使用互斥同步的方法。
    • 使用Synchronized
    • 使用Lock
  2. 使用非阻塞同步方案。
    • CAS 等。
  3. 无同步方案
    其实就是在设计上尽量避免共享变量的使用,这样也就可以避免线程安全问题的发生。
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 数据
    +关注

    关注

    8

    文章

    6989

    浏览量

    88931
  • 程序
    +关注

    关注

    117

    文章

    3782

    浏览量

    80990
  • 多线程
    +关注

    关注

    0

    文章

    278

    浏览量

    19940
  • 代码
    +关注

    关注

    30

    文章

    4774

    浏览量

    68504
  • 线程
    +关注

    关注

    0

    文章

    504

    浏览量

    19674
收藏 人收藏

    评论

    相关推荐

    诺基亚n70白屏怎么办

    诺基亚n70白屏怎么办
    发表于 09-01 15:58 3550次阅读
    诺基亚n70白屏<b class='flag-5'>怎么办</b>

    显示桌面没了怎么办

    显示桌面没了怎么办 我的windows xp的显示桌面的图标没有了怎么办。下载一个放到系统目
    发表于 01-18 19:00 3850次阅读

    笔记本风扇噪音很大怎么办

    笔记本风扇噪音很大怎么办 教,我的笔记本的风扇噪音很大,怎么办?  可以尝试一下给风扇加一点“油”——钟表油!首先
    发表于 01-21 10:51 1895次阅读

    文件或目录损坏怎么办

    文件或目录损坏怎么办 我的D盘分区是NTFS格式的,但现在变成RAW。而且双击D盘就提示:无法访问D:/ 文件或目录损坏且无法读取。怎么办
    发表于 02-25 10:16 1107次阅读

    内存报警怎么办

    内存报警怎么办    近日,我的电脑无法启动,同时在开机时发出表示内存出问题的“嘀嘀”警报声,请问是什么原因,应该如
    发表于 02-25 11:37 2103次阅读

    电池换新无法可依怎么办

    电池坏了怎么办?修。修不好怎么办?换。
    发表于 03-19 11:23 1402次阅读

    日常运营中网站受到安全威胁时该怎么办

    很多站长辛辛苦苦做站,却因为安全措施不到位导致网站被挂马,点进去都是灰色链接,如果不及时处理,很容易招致搜索引擎惩罚,那么网站被挂马怎么办?出现这种棘手的问题该怎么处理?
    发表于 11-16 11:17 579次阅读

    linux无法识别U盘怎么办

    linux无法识别U盘怎么办
    发表于 05-19 09:08 1.7w次阅读
    linux无法识别U盘<b class='flag-5'>怎么办</b>

    linux下telnet不能使用怎么办

     linux下telnet不能使用怎么办?yum安装方式处理
    发表于 05-26 09:34 5817次阅读
    linux下telnet不能使用<b class='flag-5'>怎么办</b>

    网络安全密钥忘记了怎么办

    网络安全密钥忘了怎么办呢?由于我们的日常生活中并不是会用到网络安全密钥。因此很多情况下我们在设置之后都会忘记。那么网络安全密钥忘记了怎么办
    的头像 发表于 01-11 16:58 2.2w次阅读

    键槽滚键了怎么办

    键槽滚键了怎么办
    发表于 03-07 16:37 7次下载

    风机轴承烧结导致轴承位磨损怎么办

    风机轴承烧结导致轴承位磨损怎么办
    发表于 05-31 15:36 3次下载

    电机过热怎么办

    电机过热怎么办?WAYON维安PPTC有方案
    的头像 发表于 11-01 15:08 705次阅读
    电机过热<b class='flag-5'>怎么办</b>?

    pcb钻孔偏孔了怎么办

    pcb钻孔偏孔了怎么办
    的头像 发表于 11-22 11:10 3475次阅读
    pcb钻孔偏孔了<b class='flag-5'>怎么办</b>?

    风机轴磨损怎么办

    电子发烧友网站提供《风机轴磨损怎么办.docx》资料免费下载
    发表于 01-07 11:04 0次下载