精华内容
下载资源
问答
  • spring单例在高并发下可能出现的错误: 首先,只有当注入的对象是无状态的幂等的才可以保证执行前后不被修改,否则执行一次之后单例对象就会发生改变,在下次执行有肯能造成结果不一样,当在高并发的情况下就会出现...

    在单例模式下,如果类中存在全局变量或者父类中存在全局变量,多个线程并发请求的时候,会出现全局变量的值的混乱和冲突。

    引用资料

    spring单例在高并发下可能出现的错误: 首先,只有当注入的对象是无状态的幂等的才可以保证执行前后不被修改,否则执行一次之后单例对象就会发生改变,在下次执行有肯能造成结果不一样,当在高并发的情况下就会出现,这个线程刚使用单例对象进行属性设置,还未使用的情况下,另一个进程已经将单利对象的数据进行修改属性完成,则远来线程获取到的单例就是一个脏对象不可使用。 当单例对象中含有变化的变量数据,则就不可以使用对象注入的形式注入,那怎么办?
    1、在单例对象中进行new对象,这属于线程自己的内存进行存放数据,其他线程无法使用
    2、在单例对象中注入了可变对象,则在使用的时候进行copy浅拷贝后使用拷贝后的对象不实用单例对象。
    3、通过new Callable()轻量级的创建局部变量可以使用上下文变量,属于线程自己的变量。
    总结:在高并发情况下,单利对象的数据不可以在一个线程使用过,另一个线程调用时单例对象的数据发生改变。 其实单例对象相当于全局变量,线程执行时需要修改数据,再高并发的情况下就会出现当前线程获取到的单例对象数据是脏数据。

    Spring单例与线程安全小结

    一、Spring单例模式与线程安全

    Spring框架里的bean,或者说组件,获取实例的时候都是默认的单例模式,这是在多线程开发的时候要尤其注意的地方。

    单例模式的意思就是只有一个实例。单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。这个类称为单例类。
    当多用户同时请求一个服务时,容器会给每一个请求分配一个线程,这是多个线程会并发执行该请求多对应的业务逻辑(成员方法),此时就要注意了,如果该处理逻辑中有对该单列状态的修改(体现为该单列的成员属性),则必须考虑线程同步问题
    同步机制的比较  ThreadLocal和线程同步机制相比有什么优势呢?ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。

    在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。

    而ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。

    由于ThreadLocal中可以持有任何类型的对象,低版本JDK所提供的get()返回的是Object对象,需要强制类型转换。但JDK 5.0通过泛型很好的解决了这个问题,在一定程度地简化ThreadLocal的使用
     概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。

    Spring使用ThreadLocal解决线程安全问题

    我们知道在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域。就是因为Spring对一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全状态采用ThreadLocal进行处理,让它们也成为线程安全的状态,因为有状态的Bean就可以在多线程中共享了。

    一般的Web应用划分为展现层、服务层和持久层三个层次,在不同的层中编写对应的逻辑,下层通过接口向上层开放功能调用。在一般情况下,从接收请求到返回响应所经过的所有程序调用都同属于一个线程
    ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。
    如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。 或者说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。  线程安全问题都是由全局变量及静态变量引起的。
    若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则就可能影响线程安全。
    1) 常量始终是线程安全的,因为只存在读操作。
    2)每次调用方法前都新建一个实例是线程安全的,因为不会访问共享的资源。
    3)局部变量是线程安全的。因为每执行一个方法,都会在独立的空间创建局部变量,它不是共享的资源。局部变量包括方法的参数变量和方法内变量。
    有状态就是有数据存储功能。有状态对象(Stateful Bean),就是有实例变量的对象 ,可以保存数据,是非线程安全的。在不同方法调用间不保留任何状态。
    无状态就是一次操作,不能保存数据。无状态对象(Stateless Bean),就是没有实例变量的对象 .不能保存数据,是不变类,是线程安全的。
    有状态对象:
    无状态的Bean适合用不变模式,技术就是单例模式,这样可以共享实例,提高性能。有状态的Bean,多线程环境下不安全,那么适合用Prototype原型模式。Prototype: 每次对bean的请求都会创建一个新的bean实例。
    Struts2默认的实现是Prototype模式。也就是每个请求都新生成一个Action实例,所以不存在线程安全问题。需要注意的是,如果由Spring管理action的生命周期, scope要配成prototype作用域。

    有状态的bean和无状态的bean

    有状态对象(Stateful Bean) :就是有实例变量的对象,可以保存数据,是非线程安全的。每个用户有自己特有的一个实例,在用户的生存期内,bean保持了用户的信息,即“有状态”;一旦用户灭亡(调用结束或实例结束),bean的生命期也告结束。即每个用户最初都会得到一个初始的bean。
    无状态对象(Stateless Bean):就是没有实例变量的对象,不能保存数据,是不变类,是线程安全的。bean一旦实例化就被加进会话池中,各个用户都可以共用。即使用户已经消亡,bean 的生命期也不一定结束,它可能依然存在于会话池中,供其他用户调用。由于没有特定的用户,那么也就不能保持某一用户的状态,所以叫无状态bean。但无状态会话bean 并非没有状态,如果它有自己的属性(变量),那么这些变量就会受到所有调用它的用户的影响,这是在实际应用中必须注意的。

    我的理解:一般类中存在全局变量或者静态变量就是有状态的bean.例如实体类,mvc中的model。不存在全局变量的bean,一般为无状态的bean,例如mvc中的dao,service层或者是工具类。有状态的bean,是线程不安全的,无状态的bean是线程安全的。
    线程安全:就是在高并发等的情况下,两个线程同时调用一个有状态的bean,当第一个线程未使用bean的变量时,被第二个线程修改了变量的值,造成bean中的全局变量或者静态变量的值被覆盖而混乱。
    springMVC的bean默认情况下是单例模式,我曾有一次,使几个同类型的service继承了一个有状态的baseService,结果造成了登陆账户的混乱。
    单例模式下建议不要使用有状态的bean,如果不可避免可以用TreadLoad解决,它是线程安全的。另外在方法内,建议使用基本类型。

    Out,Request,Response,Session,Config,Page,PageContext是线程安全的,Application在整个系统内被使用,所以不是线程安全的.
    
    展开全文
  • 多线程入门 高并发安全问题.volatile关键字和原子类讲解第一章 多线程1.1并发与并行1.2线程与进程1.3Thread类1.31Thread类的构造方法1.32Thread类的常用方法1.4创建线程方式一 继承方式1.5创建线程的方式二 实现...

    第一章 多线程

    我们在之前,学习的程序在没有跳转语句的前提下,都是由上至下依次执行,那现在想要设计一个程序,边打游戏边听歌,怎么设计?

    要解决上述问题,咱们得使用多进程或者多线程来解决.

    1.1并发与并行

    • 并行:指两个或多个事件在同一时刻发生(同时执行)。
    • 并发:指两个或多个事件在同一个时间段内发生(交替执行)。
    • 在这里插入图片描述
      在操作系统中,安装了多个程序,并发指的是在一段时间内宏观上有多个程序同时运行,这在单 CPU 系统中,每一时刻只能有一道程序执行,即微观上这些程序是分时的交替运行,只不过是给人的感觉是同时运行,那是因为分时交替运行的时间是非常短的。

    而在多个 CPU 系统中,则这些可以并发执行的程序便可以分配到多个处理器上(CPU),实现多任务并行执行,即利用每个处理器来处理一个可以并发执行的程序,这样多个程序便可以同时执行。目前电脑市场上说的多核 CPU,便是多核处理器,核越多,并行处理的程序越多,能大大的提高电脑运行的效率。

    注意:单核处理器的计算机肯定是不能并行的处理多个任务的,只能是多个任务在单个CPU上并发运行。同理,线程也是一样的,从宏观角度上理解线程是并行运行的,但是从微观角度上分析却是串行运行的,即一个线程一个线程的去运行,当系统只有一个CPU时,线程会以某种顺序执行多个线程,我们把这种情况称之为线程调度。

    在这里插入图片描述

    1.2线程与进程

    • 进程:进程是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;
    • 线程:是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。

    进程
    在这里插入图片描述
    在这里插入图片描述
    进程与线程的区别

    • 进程:有独立的内存空间,进程中的数据存放空间(堆空间和栈空间)是独立的,至少有一个线程。
    • 线程:堆空间是共享的,栈空间是独立的,线程消耗的资源比进程小的多。

    **注意:**下面内容为了解知识点

    1:因为一个进程中的多个线程是并发运行的,那么从微观角度看也是有先后顺序的,哪个线程执行完全取决于 CPU 的调度,程序员是干涉不了的。而这也就造成的多线程的随机性。

    2:Java 程序的进程里面至少包含两个线程,主进程也就是 main()方法线程,另外一个是垃圾回收机制线程。每当使用 java 命令执行一个类时,实际上都会启动一个 JVM,每一个 JVM 实际上就是在操作系统中启动了一个线程,java 本身具备了垃圾的收集机制,所以在 Java 运行时至少会启动两个线程。

    3:由于创建一个线程的开销比创建一个进程的开销小的多,那么我们在开发多任务运行的时候,通常考虑创建多线程,而不是创建多进程。

    线程调度:

    • 分时调度

      ​ 所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。

    • 抢占式调度

      ​ 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
      在这里插入图片描述

    1.3Thread类

    1.31Thread类的构造方法

    线程开启我们需要用到了java.lang.Thread类,API中该类中定义了有关线程的一些方法,具体如下:

    • public Thread():分配一个新的线程对象。
    • public Thread(String name):分配一个指定名字的新的线程对象。
    • public Thread(Runnable target):分配一个带有指定目标新的线程对象。
    • public Thread(Runnable target,String name):分配一个带有指定目标新的线程对象并指定名字。

    1.32Thread类的常用方法

    • public String getName():获取当前线程名称。
    • public void start():导致此线程开始执行; Java虚拟机调用此线程的run方法。
    • public void run():此线程要执行的任务在此处定义代码。
    • public static void sleep(long millis):使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
    • public static Thread currentThread():返回对当前正在执行的线程对象的引用。

    翻阅API后得知创建线程的方式总共有两种,一种是继承Thread类方式,一种是实现Runnable接口方式,方式一我们上一天已经完成,接下来讲解方式二实现的方式。

    1.4创建线程方式一 继承方式

    Java使用java.lang.Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每个线程的作用是完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码。Java使用线程执行体来代表这段程序流。Java中通过继承Thread类来创建启动多线程的步骤如下:

    1. 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。
    2. 创建Thread子类的实例,即创建了线程对象
    3. 调用线程对象的start()方法来启动该线程

    代码如下:
    测试类:

    public class Demo01 {
    	public static void main(String[] args) {
    		//创建自定义线程对象
    		MyThread mt = new MyThread("新的线程!");
    		//开启新线程
    		mt.start();
    		//在主方法中执行for循环
    		for (int i = 0; i < 200; i++) {
    			System.out.println("main线程!"+i);
    		}
    	}
    }
    

    自定义线程类:

    public class MyThread extends Thread {
    	//定义指定线程名称的构造方法
    	public MyThread(String name) {
    		//调用父类的String参数的构造方法,指定线程的名称
    		super(name);
    	}
      	public MyThread() {
    		//不指定线程的名字,线程有默认的名字Thread-0
    	}
    	/**
    	 * 重写run方法,完成该线程执行的逻辑
    	 */
    	@Override
    	public void run() {
    		for (int i = 0; i < 200; i++) {
    			System.out.println(getName()+":正在执行!"+i);
    		}
    	}
    }
    

    1.5创建线程的方式二 实现方式

    采用java.lang.Runnable也是非常常见的一种,我们只需要重写run方法即可。

    步骤如下:

    1. 定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
    2. 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
    3. 调用线程对象的start()方法来启动线程。

    代码如下:

    public class MyRunnable implements Runnable{
    	@Override
    	public void run() {
    		for (int i = 0; i < 20; i++) {
    			System.out.println(Thread.currentThread().getName()+" "+i);
    		}
    	}
    }
    
    public class Demo {
        public static void main(String[] args) {
            //创建自定义类对象  线程任务对象
            MyRunnable mr = new MyRunnable();
            //创建线程对象
            Thread t = new Thread(mr, "小强");
            t.start();
            for (int i = 0; i < 20; i++) {
                System.out.println("旺财 " + i);
            }
        }
    }
    

    通过实现Runnable接口,使得该类有了多线程类的特征。run()方法是多线程程序的一个执行目标。所有的多线程代码都在run方法里面。Thread类实际上也是实现了Runnable接口的类。

    在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象,然后调用Thread对象的start()方法来运行多线程代码。

    实际上所有的多线程代码都是通过运行Thread的start()方法来运行的。因此,不管是继承Thread类还是实现Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的,熟悉Thread类的API是进行多线程编程的基础。

    tips:Runnable对象仅仅作为Thread对象的target,Runnable实现类里包含的run()方法仅作为线程执行体。而实际的线程对象依然是Thread实例,只是该Thread线程负责执行其target的run()方法。

    Thread和Runnable的区别

    如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。

    总结:

    实现Runnable接口比继承Thread类所具有的优势:

    1. 适合多个相同的程序代码的线程去共享同一个资源。
    2. 可以避免java中的单继承的局限性。
    3. 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
    4. 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。

    1.6匿名内部类方式

    使用线程的内匿名内部类方式,可以方便的实现每个线程执行不同的线程任务操作。

    使用匿名内部类的方式实现Runnable接口,重新Runnable接口中的run方法:

    public class NoNameInnerClassThread {
       	public static void main(String[] args) {	   	
    //		new Runnable(){
    //			public void run(){
    //				for (int i = 0; i < 20; i++) {
    //					System.out.println("张宇:"+i);
    //				}
    //			}  
    //	   	}; //---这个整体  相当于new MyRunnable()
            Runnable r = new Runnable(){
                public void run(){
                    for (int i = 0; i < 20; i++) {
                      	System.out.println("张宇:"+i);
                    }
                }  
            };
            new Thread(r).start();
    
            for (int i = 0; i < 20; i++) {
              	System.out.println("费玉清:"+i);
            }
       	}
    }
    

    第二章 高并发及线程安全

    2.1高并发及线程安全

    • 高并发:是指在某个时间点上,有大量的用户(线程)同时访问同一资源。例如:天猫的双11购物节、12306的在线购票在某个时间点上,都会面临大量用户同时抢购同一件商品/车票的情况。

    • 线程安全:在某个时间点上,当大量用户(线程)访问同一资源时,由于多线程运行机制的原因,可能会导致被访问的资源出现"数据污染"的问题。

    • 当一个线程启动后,JVM会为其分配一个独立的"线程栈区",这个线程会在这个独立的栈区中运行。

    • 看一下简单的线程的代码:

      1. 一个线程类:
      public class MyThread extends Thread {
          @Override
          public void run() {
              for (int i = 0; i < 20; i++) {
                  System.out.println("小强: " + i);
              }
          }
      }
      
      1. 测试类:
      public class Demo {
          public static void main(String[] args) {
              //1.创建线程对象
              MyThread mt = new MyThread();
      
              //2.启动线程
              mt.start();
              for (int i = 0; i < 20; i++) {
                  System.out.println("旺财: " + i);
              }
          }
      }
      
    • 启动后,内存的运行机制:

    • 在这里插入图片描述
      多个线程在各自栈区中独立、无序的运行,当访问一些代码,或者同一个变量时,就可能会产生一些问题

    2.2多线程的安全性问题-可见性

    • 例如下面的程序,先启动一个线程,在线程中将一个变量的值更改,而主线程却一直无法获得此变量的新值。

      1. 线程类:
      public class MyThread extends Thread {
      
          boolean flag = false;// 主和子线程共享变量
      
          @Override
          public void run() {
      
              try {
                  Thread.sleep(1000);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
      
              // 把flag的值改为true
              flag = true;
              System.out.println("修改后flag的值为:"+flag);
      
          }
      }
      
      1. 测试类:
      public class Test {
          public static void main(String[] args) {
              /*
                  多线程的安全性问题-可见性:
                      一个线程没有看见另一个线程对共享变量的修改
               */
              // 创建子线程并启动
              MyThread mt = new MyThread();
              mt.start();
      
              // 主线程
              while (true){
                  if (MyThread.flag == true){
                      System.out.println("死循环结束");
                      break;
                  }
              }
              /*
                  按照分析结果应该是: 子线程把共享变量flag改为true,然后主线程的死循环就可以结束
                  实际结果是: 子线程把共享变量flag改为true,但主线程依然是死循环
                  为什么?
                      其实原因就是子线程对共享变量flag修改后的值,对于主线程是不可见的
               */
      
          }
      }
      
      public class MyThread extends Thread{
          static boolean flag = false;// 主线程和子线程共享的变量
      
          @Override
          public void run() {
              try {
                  Thread.sleep(3000);// 暂停\醒了
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              flag = true;
              System.out.println("flag修改后的值为:"+flag);
          }
      }
      
      

    2.3多线程的安全性问题-有序性

    • 有些时候“编译器”在编译代码时,会对代码进行“重排”,例如:

      ​ int a = 10; //1

      ​ int b = 20; //2

      ​ int c = a + b; //3

      第一行和第二行可能会被“重排”:可能先编译第二行,再编译第一行,总之在执行第三行之前,会将1,2编译完毕。1和2先编译谁,不影响第三行的结果。

    • 但在“多线程”情况下,代码重排,可能会对另一个线程访问的结果产生影响:

    • 在这里插入图片描述
      多线程环境下,我们通常不希望对一些代码进行重排的!!

    2.4多线程的安全性问题-原子性

    请看以下示例:

    1.制作线程类

    public class MyThread extends  Thread {
        public static int a = 0;
    
        @Override
        public void run() {
            for (int i = 0; i < 100000; i++) {
                a++;
            }
            System.out.println("修改完毕!");
        }
    }
    
    

    2.制作测试类

    public class Demo {
      public static void main(String[] args) {
          /*
              概述:所谓的原子性是指在一次操作或者多次操作中,
                  要么所有的操作全部都得到了执行并且不会受到任何因素的干扰而中断,
                  要么所有的操作都不执行,多个操作是一个不可以分割的整体。
              演示高并发原子性问题:
                  例如: 一条子线程和一条主线程都对共享变量a进行++操作,每条线程对a++操作100000次
                      最终期望a的值为:200000
              出现高并发原子性问题的原因:虽然计算了2次,但是只对a进行了1次修改
    
           */
          // 创建并启动子线程
          MyThread mt = new MyThread();
          mt.start();
    
          // a变量自增3万次
          for (int i = 0; i < 100000; i++) {
              mt.a++;
          }
    
          // 暂定3秒,为了保证子线程执行完毕
          try {
              Thread.sleep(3000);
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
    
          System.out.println("最终a的值为:"+mt.a);// 期望:200000
      }
    }
    
    

    原因:两个线程访问同一个变量a的代码不具有"原子性"

    第三章 volatile关键字

    • volatile是一个"变量修饰符",它只能修饰"成员变量",它能强制线程每次从主内存获取值,并能保证此变量不会被编译器优化。
    • volatile能解决变量的可见性、有序性;
    • volatile不能解决变量的原子性

    3.1volatile解决可见性

    将1.3的线程类MyThread做如下修改:

    1. 线程类:
    public class MyThread extends Thread {
        public static volatile int a = 0;//增加volatile关键字
        @Override
        public void run() {
            System.out.println("线程启动,休息2秒...");
            try {
                Thread.sleep(1000 * 2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("将a的值改为1");
            a = 1;
            System.out.println("线程结束...");
        }
    }
    
    1. 测试类
    public class Demo {
        public static void main(String[] args) {
            //1.启动线程
            MyThread t = new MyThread();
            t.start();
    
            //2.主线程继续
            while (true) {
                if (MyThread.a == 1) {
                    System.out.println("主线程读到了a = 1");
                }
            }
        }
    }
    

    当变量被修饰为volatile时,会迫使线程每次使用此变量,都会去主内存获取,保证其可见性

    3.2volatile解决有序性

    • 当变量被修饰为volatile时,会禁止代码重排
    • 在这里插入图片描述

    3.3volatile不能解决原子性

    对于示例1.5,加入volatile关键字并不能解决原子性:

    1. 线程类:
    public class MyThread extends  Thread {
        public static volatile int a = 0;
    
        @Override
        public void run() {
            for (int i = 0; i < 10000; i++) {
                //线程1:取出a的值a=0(被暂停)
                a++;
                //写回
            }
            System.out.println("修改完毕!");
        }
    }
    
    
    1. 测试类:
    public class Demo {
        public static void main(String[] args) throws InterruptedException {
            //1.启动两个线程
            MyThread t1 = new MyThread();
            MyThread t2 = new MyThread();
    
            t1.start();
            t2.start();
    
            Thread.sleep(1000);
            System.out.println("获取a最终值:" + MyThread.a);//最终结果仍然不正确。
    
        }
    }
    
    

    所以,volatile关键字只能解决"变量"的可见性、有序性问题,并不能解决原子性问题

    第四章 原子类

    在java.util.concurrent.atomic包下定义了一些对“变量”操作的“原子类”:

    ​ 1).java.util.concurrent.atomic.AtomicInteger:对int变量操作的“原子类”;

    ​ 2).java.util.concurrent.atomic.AtomicLong:对long变量操作的“原子类”;

    ​ 3).java.util.concurrent.atomic.AtomicBoolean:对boolean变量操作的“原子类”;

    它们可以保证对“变量”操作的:原子性、有序性、可见性。

    4.1 AtomicInteger类示例

    我们可以通过AtomicInteger类,来看看它们是怎样工作的

    1. 线程类:
    public class MyThread extends  Thread {
        //public static volatile int a = 0;//不直接使用基本类型变量
    
        //改用"原子类"
        public static AtomicInteger a = new AtomicInteger(0);
    
        @Override
        public void run() {
            for (int i = 0; i < 10000; i++) {
    			// a++;
                a.getAndIncrement();//先获取,再自增1:a++
            }
            System.out.println("修改完毕!");
        }
    }
    
    
    1. 测试类:
    public class Demo {
        public static void main(String[] args) throws InterruptedException {
            //1.启动两个线程
            MyThread t1 = new MyThread();
            MyThread t2 = new MyThread();
    
            t1.start();
            t2.start();
    
            Thread.sleep(1000);
            System.out.println("获取a最终值:" + MyThread.a.get());
        }
    }
    
    

    我们能看到,无论程序运行多少次,其结果总是正确的!

    4.2 AtomicInteger类的工作原理-CAS机制

    在这里插入图片描述
    在这里插入图片描述

    4.3 AtomicIntegerArray类示例

    • 常用的数组操作的原子类:
      1).java.util.concurrent.atomic.AtomicIntegetArray:对int数组操作的原子类。 int[]

      ​ 2).java.util.concurrent.atomic.AtomicLongArray:对long数组操作的原子类。long[]

      ​ 3).java.util.concurrent.atomic.AtomicReferenceArray:对引用类型数组操作的原子类。Object[]

    • 数组的多线程并发访问的安全性问题:

      1. 线程类:
      public class MyThread extends Thread {
          private static int[] intArray = new int[1000];//不直接使用数组
      
          @Override
          public void run() {
              for (int i = 0; i < arr.length(); i++) {
                  intArray[i]++;
              }
          }
      }
      
      
      1. 测试类:
      public class Demo {
          public static void main(String[] args) throws InterruptedException {
              for (int i = 0; i < 1000; i++) {
                  new MyThread().start();//创建1000个线程,每个线程为数组的每个元素+1
              }
      
              Thread.sleep(1000 * 5);//让所有线程执行完毕
      
              System.out.println("主线程休息5秒醒来");
              for (int i = 0; i < MyThread.intArray.length(); i++) {
                  System.out.println(MyThread.intArray[i]);
              }
          }
      }
      
      

      正常情况,数组的每个元素最终结果应为:1000,而实际打印:

      1000
      1000
      1000
      1000
      999
      999
      999
      999
      999
      999
      999
      999
      1000
      1000
      1000
      1000
      

      可以发现,有些元素并不是1000.

    • 为保证数组的多线程安全,改用AtomicIntegerArray类,演示:

      1. 线程类:
      public class MyThread extends Thread {
          private static int[] intArray = new int[1000];//定义一个数组
          //改用原子类,使用数组构造
          public static AtomicIntegerArray arr = new AtomicIntegerArray(intArray);
          @Override
          public void run() {
              for (int i = 0; i < arr.length(); i++) {
                  arr.addAndGet(i, 1);//将i位置上的元素 + 1
              }
          }
      }
      
      
      1. 测试类:
      public class Demo {
          public static void main(String[] args) throws InterruptedException {
              for (int i = 0; i < 1000; i++) {
                  new MyThread().start();
              }
              Thread.sleep(1000 * 5);//让所有线程执行完毕
      
              System.out.println("主线程休息5秒醒来");
              for (int i = 0; i < MyThread.arr.length(); i++) {
                  System.out.println(MyThread.arr.get(i));
              }
          }
      }
      

      先在能看到,每次运行的结果都是正确的。

    第五章 总结

    • 说出进程和线程的概念
      进程: 应用程序的可执行单元,也就是.exe
      线程: 进程的可执行单元

    • 能够理解并发与并行的区别
      并发: 在一个时间段内多个事件交替执行
      并行: 在同一时刻多个事件同时执行

    • 能够描述Java中多线程运行原理
      1.在java中线程的调度是抢占式调度
      2.线程一旦启动,就会在栈区开辟一块独立的栈空间,用来执行线程任务,线程任务执行完毕,线程会在合适的时间就被消耗

    • 能够使用继承类的方式创建多线程
      1.创建子类继承Thread类
      2.在子类中重写run()方法,把线程需要执行的任务放入run方法中
      3.创建子类线程对象
      4.调用start方法启动并执行线程

    • 能够使用实现接口的方式创建多线程
      1.创建实现类实现Runnable接口
      2.在实现类中重写run()方法,把线程需要执行的任务放入run方法中
      3.创建实现类对象,作为参数,传入Thread类的构造方法中,来创建线程对象
      4.调用start方法启动并执行线程

    • 能够说出实现接口方式的好处
      1.可扩展性强
      2.线程对象和任务对象是分开的
      3.线程池中的线程都是实现接口的方式的线程

    • 能够解释安全问题的出现的原因
      可见性–>一个线程没有看见其他线程对共享变量的修改
      有序性–> 一个线程对代码重排,会影响其他线程的执行结果
      原子性–>两个线程对共享变量的操作产生了覆盖的效果

    • 能够说出volatile关键字的作用
      解决可见性,有序性,但不能解决原子性问题

    • 能够掌握原子类AtomicInteger的使用
      解决可见性,有序性,原子性问题,创建对象,使用对象调用方法

    • 能够理解原子类的工作机制
      cas机制: 比较并交换
      拿获取到的值与主内存中的值等值比较:
      如果相等,就进行操作,操作后,写回主内存
      如果不相等,就重新获取值,再与主内存中的值进行等值比较:
      如果相等,就进行操作,操作后,写回主内存
      如果不相等…依次类推

      掌握;
      线程的三种创建方式 必须练习
      线程的执行 必须分析
      volatile关键字的使用 必须练习
      AtomicInteger类的使用 必须练习
      理解:
      原子性,可见性,有序性,cas机制…概念

    展开全文
  • java多线程高并发线程安全问题

    千次阅读 2017-06-26 11:40:29
    在抢购之类的业务处理时,会出现超发之类的线程安全问题。我个人想到的解决方法如下:采用数据库锁,悲观锁有效率低下问题,所以我推荐乐观锁,虽然会增大CPU开销,很多服务和软件都支持乐观锁,如Redis的watch采用...

    在抢购之类的业务处理时,会出现超发之类的线程安全问题。

    我个人想到的解决方法如下:

    1. 采用数据库锁,悲观锁有效率低下问题,所以我推荐乐观锁,虽然会增大CPU开销,很多服务和软件都支持乐观锁,如Redis的watch
    2. 采用FIFO队列,强行把多线程变成单线程,但是也会出现队列内存爆满问题
    3. 采用同步代码块,只给数据库操作的代码加锁,提高效率,对不同数据记录操作采用不同的锁
    展开全文
  • 高并发下才会出现异常 private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() { @Override protected DateFormat initialValue() { return new SimpleDateFormat...
    高并发下才会出现异常
    
    
    private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {
        @Override
        protected DateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }
    };
    
    public static void main(String[] ages) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Calendar calendar = Calendar.getInstance();
    
        ExecutorService executorService = Executors.newFixedThreadPool(15);
        for (int i = 0; i < 500; i++) {
            executorService.execute(() -> {
                try {
                    System.out.println(sdf.format(calendar.getTime()));
                    System.out.println(df.get().parse("2019-06-15 16:35:20"));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
        executorService.shutdown();
    }
    展开全文
  • 从数据安全的角度解决高并发下秒杀问题 这个是博主自己闲的无聊的时候想写个demo玩玩,并不算最好的解决方案,当然也是阉割了很多东西的简化版,现实业务肯定比这个复杂 这里的话不考虑高并发带来的性能问题,什么...
  • 高并发场景下ArrayList线程不安全问题解析 1.在高并发下ArrayList存在并发修改异常 package com.example.demo; import java.util.ArrayList; import java.util.List; import java.util.UUID; import java.util....
  • Java高并发程序中,不得不出现资源竞争以及一些其他严重的问题,比如死锁、线程饥饿、响应性问题和活锁问题。在安全性与活跃性之间通常存在依赖,我们使用加锁机制来确保线程安全,但是如果过度地使用加锁,则可能...
  • 要理解java内存模型以及一些处理高并发的技术手段,理解一些主要的硬件知识是必须的。...高并发问题: CPU多级缓存:缓存一致性,乱序执行优化 缓存一致性:eg.(i初值为1,两个线程对i进行加1操作...
  • 很多网友留言说:在编写多线程并发程序时,我明明对共享资源加锁了啊?为什么还是出问题呢?问题到底出在哪里呢?...错误的加锁方式不但不能解决并发问题,而且还会带来各种诡异的Bug问题,有时难以复现!
  • 并发,在一个成熟的系统中是必不可少的,这也是广大程序猿探讨的热点,高并发下的数据安全尤为重要。博主最近也在巩固这方面的知识,特此整理一下博客,做一下记录。 什么是并发?并发有哪些问题? 提到并发,就...
  • ArrayList是我们常用的数据结构,在并发场景下是线程不安全的。 在读多写少的场景下,我们一般会用读写锁 ReadWriteLock来保证共享对象的线程安全性。 public Object read() { lock.readLock().lock(); // 对...
  • Java高并发--线程安全策略不可变对象发布不可变对象可保证线程安全。实现不可变对象有哪些要注意的地方?比如JDK中的String类。不提供setter方法(包括修改字段、字段引用到的的对象等方法)将所有字段设置为final、...
  • Node.js 中的并发安全问题简介Node.js 采用非阻塞异步IO的方式来处理请求。基于 Libuv 的事件循环机制,node 得以通过单线程来处理高并发的请求。一般情况下,如果采用多线程的方式来处理并发的请求时,我们需要考虑...
  • 一、并发安全根源并发编程出现安全问题的原因无非三个:有序性、可见性、原子性。这三个问题在硬件中有其具体的产生原因。1、有序性。程序的一个要求就是:在编码保证正确的前提下,执行结果必须是正确的,程序要得...
  • 高并发和线程安全

    2020-06-21 19:15:18
    高并发及线程安全的介绍 高并发: 是指在某个时间点上,有大量的用户(线程)同时访问同一资源 线程安全:在某个时间点上,当大量用户(线程)访问同一资源时,由于多线程运行机制的原因,可能会导致被访问的资源出现...
  • 首先问下大家:你使用的SimpleDateFormat类还安全吗?...这里,为什么说SimpleDateFormat类有线程安全问题呢?有些小伙伴可能会提出疑问:我们生产环境上一直在使用SimpleDateFormat类来解析和格式化日期和时间类型...
  • 高并发 问题怎么解决

    2019-06-26 14:43:00
    对于高并发问题,我认为总的来说可以分为...(对于高并发问题,只有不断优化,而不存在绝对的并发安全) 前端:实现负载均衡,配置前置代理服务器,如NGINX,Apache等; 后台:增加网络带宽,DNS域名解析分发多台...
  • 本文介绍了 Java 中高并发及线程安全的相关问题。。。
  • java解决高并发安全- redis分布式锁

    千次阅读 2020-09-11 15:01:22
    常用的锁有单体应用的简单锁synchronize,但是遇到分布式部署的项目时就会在大并发下出现安全问题,数据出现脏数据,此时可以考虑使用redis分布式锁,或者zookeeper锁 在实际开发中集群部署会出现的各种情况都会造成...
  • 1.HashSet和HashMap在高并发场景下也会报java.util.ConcurrentModificationException异常 2.解决办法: Set s= new CopyOnWriteArraySet<>(); private final CopyOnWriteArrayList<E> al; /** * ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,832
精华内容 1,132
关键字:

高并发安全问题