ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

Java学习笔记----如何保证线程安全

2022-01-04 20:30:23  阅读:143  来源: 互联网

标签:Java Thread ---- 线程 salethread1 new ticket public


如何保证线程安全

什么是线程安全问题?

在多线程情况下,对共享内存中的变量做写操作时,就会容易出现线程安全问题。
举个栗子,我们现在要用两个线程实现两个窗口售卖5张票

    class ThreadSaleTicket extends Thread{
       public int ticket = 5;

        @Override
        public void run() {
            while(ticket > 0){
                sale();
            }

        }
        public void sale(){
            System.out.println(Thread.currentThread().getName() + "售出第: " + (5-ticket+1) + " 张票");
            ticket--;
        }
    }

    public class threadSecurityTest{
        public static void main(String[] args) {
            ThreadSaleTicket salethread1 = new ThreadSaleTicket();
            Thread thread1 = new Thread(salethread1,"窗口1");
            Thread thread2 = new Thread(salethread1,"窗口2");
            thread1.start();
            thread2.start();
        }
    }

output:
在这里插入图片描述
是不是,有问题了吧,卖出了6张票,完了,出现线程安全问题了!

如何解决线程安全问题?

1 使用局部变量或者定义为final类型

我们知道,JVM内存分为五个区域:
对不起稍微丑了点
其中,PC寄存器、Java虚拟机栈和本地方发栈是线程私有的,方法区和Java对是线程共享的区域。因此最容易想到的保证线程安全的方式就是尽可能使用局部变量,局部变量存在方法体内,会存放在Java虚拟机栈的栈帧中,而这块区域每个线程私有,就不会存在线程安全问题。
来,咱们修改一下:

 class ThreadSaleTicket extends Thread{
       //public int ticket = 5;
        @Override
        public void run() {
            int ticket = 5;
            while(ticket > 0){
                //sale();
                System.out.println(Thread.currentThread().getName() + "售出第: " + (5-ticket+1) + " 张票");
                ticket--;
            }
        }
//        public void sale(){
//            System.out.println(Thread.currentThread().getName() + "售出第: " + (5-ticket+1) + " 张票");
//            ticket--;
//        }
    }
    public class threadSecurityTest{
        public static void main(String[] args) {
            ThreadSaleTicket salethread1 = new ThreadSaleTicket();
            Thread thread1 = new Thread(salethread1,"窗口1");
            Thread thread2 = new Thread(salethread1,"窗口2");
            thread1.start();
            thread2.start();
        }
    }

output:
在这里插入图片描述
好像不得行,改成局部变量了之后,每个线程单独有了一份ticket,卖出了两倍的票数。。。
如果定义为final之后,那更完了,票数不能修改了,咋卖。。

2 上锁

Java提供关键字synchronized和Lock接口来实现排它锁,在可能会出现线程安全的代码上加上锁,防止某一时刻有多个线程进入修改同一共享变量出现线程安全问题,sychronized以时间换空间的方式,主要侧重点在于解决多个线程之间访问资源的同步

class ThreadSaleTicket extends Thread{
       public int ticket = 5;
       public Object oj = new Object();
        @Override
        public void run() {
            int ticket = 5;
                while(ticket > 0){
                    sale();
                }
        }
        public void sale(){
            synchronized (oj){
                if(ticket > 0){
                    System.out.println(Thread.currentThread().getName() + "售出第: " + (5-ticket+1) + " 张票");
                    ticket--;
                }
            }
        }
    }
    public class threadSecurityTest{
        public static void main(String[] args) {
            ThreadSaleTicket salethread1 = new ThreadSaleTicket();
            Thread thread1 = new Thread(salethread1,"窗口1");
            Thread thread2 = new Thread(salethread1,"窗口2");
            thread1.start();
            thread2.start();
        }
    }

output:
在这里插入图片描述
是不是就好了呢,哈哈哈,成功正确卖出5张票

但是呢,思考一下,我们使用多线程并发的初心是什么呢,原是为了提升CPU的利用率以及系统的响应速度,如果动不动在代码块中加锁,那么程序将会退化成并行执行,我们使用多线程的意义在哪里呢?因此,我们寻找更优秀的解决方式。

3 使用ThreadLocal

threadLocal为每个使用该变量的线程提供独立的副本,所以每个线程都可以独立的改变自己的副本,而不会影响其他线程所对应的的副本。它采用空间换时间的方式,让多线程之间每个线程之间的数据相互隔离。
ThreadLocal实现原理:
threadlocal类中有一个map,用于存储每一个线程的变量副本,map中元素的键为线程对象,而值对应线程的变量副本

class ThreadSaleTicket extends Thread{
       public  int ticket = 5;
       public static ThreadLocal<Integer> value = new ThreadLocal<>(){};
        @Override
        public void run() {
            try {
                Thread.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            while(ticket > 0){
                    value.set(ticket--);
                    System.out.println(Thread.currentThread().getName() + "售出第: " + (5-value.get()+1) + " 张票");
                }
        }
    }
    public class threadSecurityTest{
        public static void main(String[] args) {
            ThreadSaleTicket salethread1 = new ThreadSaleTicket();
            Thread thread1 = new Thread(salethread1,"窗口1");
            Thread thread2 = new Thread(salethread1,"窗口2");
            thread1.start();
            thread2.start();
        }
    }

output:
在这里插入图片描述

4 使用juc包下面的一些线程安全类

juc包提供一些线程安全的容器或原子类来保证多线程环境下的线程安全,例如atomic包下面的运用了CAS的AtomicBoolean、AtomicInteger、AtomicReference等原子变量类,locks包下的AbstractQueuedSynchronizer(AQS)以及使用AQS的ReentantLock、ReentrantReadWriteLock等等。还有一些并发容器类例如ConcurrentHashMap、CopyOnWriteArrayList

标签:Java,Thread,----,线程,salethread1,new,ticket,public
来源: https://blog.csdn.net/weiqu4386/article/details/108695225

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有