java中怎么判断一段代码时线程安全还是非线程安全_「Java并发」线程安全与锁机制...

b425afa262176b372c1a75ac4531d165.png
线程,状态,常用方法线程状态:常用方法线程安全线程安全的实现方法1、互斥同步:同步是指在多个线程并发访问共享数据时,保证共享数据在同一个时刻只被一条线程使用。互斥方式:临界区、互斥量和信号量。2、非阻塞同步3、无同步方案一、synchronizedsynchronized与static synchronized 的区别synchronized方法与synchronized代码快的区别synchronized关键字是不能继承的synchronized(class)、synchronized(this)与synchronized(object)static synchronized会锁住对象的所有static synchronized方法(因为共用同一个锁class)二、区别ThreadLocal 与 synchronized理解ThreadLocal中提到的变量副本runnable单例变量,多线程使用时,ThreadLocal实现线程安全springMVC值Controller(单例),ThreadLocal实现线程安全多浏览器多次调用如下,Session不同,request处理线程不同(试着推倒:一个session,服务器维护一个对应的线程,处理request给单例controller)三、web线程安全:servlet、struts2、springMVC、spring Bean线程安全在Java里,线程安全一般体现在两个方面:servlet,非线程安全springMVC,非线程安全,controller是单例,但它不依赖成员变量传参,因此基本没影响;单例不会创建对象和垃圾回收;servlet和struts1均是单实例、多线程,在不加锁的情况下尽量不要使用局部变量等带来线程不安全的变量,只有请求和局部变量时线程安全的。struts2,线程安全,一个action请求,对应一个实例;Spring 单例Bean和Java 单例模式四、静态同步方法,非静态同步方法,同步语句块

线程,状态,常用方法

线程状态:

  • NEW:新建
  • RUNNABLE:运行
  • WAITING:无限期等待,等得其他线程显式地唤醒没有设置Timeout参数的Object.wait();没有设置Timeout参数的Thread.wait()。
  • TIMED_WAITING:限期等待,在一定时间之后会由系统自动唤醒。设置Timeout参数的Object.wait();设置Timeout参数的Thread.wait();Thread.sleep()方法。
  • BLOCKED:阻塞,等待获取一个排它锁,等待进入一个同步区域。
  • TERMINATED:结束

常用方法

  • object.wait()在其他线程调用此对象的notify()或者notifyAll()方法,或超过指定时间量前,当前线程T等待(线程T必须拥有该对象的锁)。线程T被放置在该对象的休息区中,并释放锁。在被唤醒、中断、超时的情况下,从对象的休息区中删除线程T,并重新进行线程调度。一旦线程T获得该对象的锁,该对象上的所有同步申明都被恢复到调用wait()方法时的状态,然后线程T从wait()方法返回。如果当前线程在等待之前或在等待时被任何线程中断,则会抛出 InterruptedException。在按上述形式恢复此对象的锁定状态时才会抛出此异常。在抛出此异常时,当前线程的中断状态被清除只有该对象的锁被释放,并不会释放当前线程持有的其他同步资源
  • object.notify()唤醒在此对象锁上等待的单个线程。此方法只能由拥有该对象锁的线程来调用。
  • Thread.sleep()在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。监控状态依然保持、会自动恢复到可运行状态,不会释放对象锁。如果任何线程中断了当前线程。当抛出InterruptedException异常时,当前线程的中断状态被清除。让出CPU分配的执行时间
  • thread.join():在一个线程对象上调用,使当前线程等待这个线程对象对应的线程结束。
  • Thread.yield():暂停当前正在执行的线程对象,并执行其他线程。
  • thread.interrupt():中断线程,停止其正在进行的一切。中断一个不处于活动状态的线程不会有任何作用。如果线程在调用Object类的wait()方法、或者join()、sleep()方法过程中受阻,则其中断状态将被清除,并收到一个InterruptedException。
  • Thread.interrupted():检测当前线程是否已经中断,并且清除线程的中断状态(回到非中断状态)。
  • thread.isAlive():如果线程已经启动且尚未终止,则为活动状态。
  • thread.setDaemon():需要在start()方法调用之前调用。当正在运行的线程都是后台线程时,Java虚拟机将退出。否则当主线程退出时,其他线程仍然会继续执行。

线程安全

线程安全:当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交换执行,也不需要进行额外的同步,或者调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象就是线程安全的。

线程安全的实现方法

1、互斥同步:同步是指在多个线程并发访问共享数据时,保证共享数据在同一个时刻只被一条线程使用。互斥方式:临界区、互斥量和信号量。

  • Synchronized关键字

编译后会在同步块前后分别形成monitorenter和monitorexit这两个字节码指令。这两个指令都需要一个引用类型的参数来指明要锁定和解锁的对象。如果没有明确指定对象参数,那就根据synchronized修饰的是实例方法还是类方法,去取对应的对象实例或Class对象来作为锁对象。

在执行monitorenter指令时,首先尝试获取对象的锁,如果没有被锁定或者当前线程已经拥有了该对象的锁,则将锁计数器加1,相应的执行moniterexit时,将锁计数器减1,当计数器为0时,锁就被释放了。如果获取对象锁失败,则当前线程就要阻塞等待。

  • ReentrantLock相对synchronized的高级功能:等待可中断:当持有锁的线程长期不释放锁时,正在等待的线程可以选择放弃等待,改为处理其他事情。公平锁:多个线程在等待同一个锁时,必须按照申请锁的事件顺序来一次获取锁;而非公平锁在被释放时,任何一个等待锁的线程都有机会获得锁。Synchronized中的锁是非公平锁,ReentrantLock默认也是非公平锁。锁绑定多个条件:一个ReentrantLock对象可以同时绑定多个Condition对象。

2、非阻塞同步

基于冲突检测的乐观并发策略:先进行操作,如果没有其他线程争用共享数据,那操作就成功了;如果共享数据有争用,产生了冲突,那就再进行其他的补偿措施(一般是不断的尝试,直到成功为止)。

AtomicInteger等原子类中提供了方法实现了CAS指令。

3、无同步方案

  • 可重入代码:可以在代码执行的任何时刻中断它,转而去执行另一段代码,而在控制权返回后,原来的程序不会出现任何错误。

特征:不依赖存储在堆上的数据和公用的系统资源、用到的状态量都由参数传入,不调用非可重入的方法等。如果一个方法,它的返回结果是可以预测的,只要出入了相同的数据,就能返回相同的结果,那它就满足可重入性的要求。

  • 线程本地存储:如果一段代码中所需要的数据必须与其它代码共享,那就看看这些共享数据的代码是否能保证在同一个线程中执行。ThreadLocal:线程级别的局部变量,为每个使用该变量的线程提供一个独立的变量副本,每个线程修改副本时不影响其他线程对象的副本。ThreadLocal实例通常作为静态私有字段出现在一个类中。

一、synchronized

synchronized与static synchronized 的区别

synchronized是对类的当前实例进行加锁,防止其他线程同时访问该类的该实例的所有synchronized块,注意这里是“类的当前实例”, 类的两个不同实例就没有这种约束了。

那么static synchronized恰好就是要控制类的所有实例的访问了,

synchronized方法与synchronized代码快的区别

synchronized methods(){} 与synchronized(this){}之间没有什么区别,只是synchronized methods(){} 便于阅读理解,而synchronized(this){}可以更精确的控制冲突限制访问区域,有时候表现更高效率。

synchronized关键字是不能继承的

继承时子类的覆盖方法必须显示定义成synchronized。(但是如果使用继承开发环境的话,会默认加上synchronized关键字)

synchronized(class)、synchronized(this)与synchronized(object)

  • 对于实例同步方法,锁是当前实例对象。
  • 对于静态同步方法,锁是当前对象的Class对象。
  • 对于同步方法块,锁是Synchonized括号里配置的对象。

static synchronized会锁住对象的所有static synchronized方法(因为共用同一个锁class)

二、区别ThreadLocal 与 synchronized

ThreadLocal是一个线程隔离(或者说是线程安全)的变量存储的管理实体(注意:不是存储用的),它以Java类方式表现;

synchronized是Java的一个保留字,只是一个代码标识符,它依靠JVM的锁机制来实现临界区的函数、变量在CPU运行访问中的原子性。

两者的性质、表现及设计初衷不同,因此没有可比较性。

理解ThreadLocal中提到的变量副本

事实上,我们向ThreadLocal中set的变量不是由ThreadLocal来存储的,而是Thread线程对象自身保存。当用户调用 ThreadLocal对象的set(Object o)时,该方法则通过Thread.currentThread()获取当前线程,将变量存入Thread中的一个Map内,而Map的Key就是当前的 ThreadLocal实例。

理解Thread和 ThreadLocal对变量的引用关系

runnable单例变量,多线程使用时,ThreadLocal实现线程安全

 public static void main(String[] args) { Runnable runa = new Runnable() { private int simInt = 0; private ThreadLocal tlParam = new ThreadLocal(); @Override public void run() { while (true) { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } if (tlParam.get() == null) { tlParam.set(0); } simInt++; tlParam.set(tlParam.get()+1); System.out.println(Thread.currentThread().hashCode() + ":simInt:" + simInt); System.out.println(Thread.currentThread().hashCode() + ":tlParam:" + tlParam.get()); } } }; Thread t1 = new Thread(runa); Thread t2 = new Thread(runa); t1.start(); t2.start(); }

springMVC值Controller(单例),ThreadLocal实现线程安全

private int simpleParam = 0; private ThreadLocal tlParam; @RequestMapping("/beat") @ResponseBody public String beat() { if (tlParam == null) { tlParam = new ThreadLocal(); } if (tlParam.get() == null) { tlParam.set(5000); } simpleParam++; tlParam.set(tlParam.get() + 1); long start = System.currentTimeMillis(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } long end = System.currentTimeMillis(); return MessageFormat.format("cost:{0}, hashCode:{1}, simpleParam:{2}, tlParam:{3}", (end - start), this.hashCode(), simpleParam, tlParam.get()); }

多浏览器多次调用如下,Session不同,request处理线程不同(试着推倒:一个session,服务器维护一个对应的线程,处理request给单例controller)

cost:1,000, hashCode:1,646,605,611, simpleParam:35, tlParam:5,023

cost:1,000, hashCode:1,646,605,611, simpleParam:36, tlParam:5,005

三、web线程安全:servlet、struts2、springMVC、spring Bean线程安全

在Java里,线程安全一般体现在两个方面:

  • 1、多个thread对同一个java实例的访问(read和modify)不会相互干扰,它主要体现在关键字synchronized。如 ArrayList和Vector,HashMap和Hashtable(后者每个方法前都有synchronized关键字)。如果你在 interator一个List对象时,其它线程remove一个element,问题就出现了。
  • 2、每个线程都有自己的字段,而不会在多个线程之间共享。它主要体现在java.lang.ThreadLocal类,而没有Java关键字支持,如像static、transient那样。

servlet,非线程安全

Servlet体系结构是建立在Java多线程机制之上的,它的生命周期是由Web容器负责的。当客户端第一次请求某个Servlet时,Servlet容器将会根据web.xml配置文件实例化这个Servlet类。当有新的客户端请求该Servlet时,一般不会再实例化该Servlet类,也就是有多个线程在使用这个实例。

因此对于servlet的成员变量,存在线程安全性问题

如何保证servlet线程安全?

  • 1.实现 SingleThreadModel 接口
    该接口指定了系统如何处理对同一个Servlet的调用。如果一个Servlet被这个接口指定,那么在这个Servlet中的service方法将不会有两个线程被同时执行,当然也就不存在线程安全的问题。
  • 2.同步对共享数据的操作
    使用synchronized 关键字能保证一次只有一个线程可以访问被保护的区段,在本论文中的Servlet可以通过同步块操作来保证线程的安全.
  • 避免使用实例变量
    本实例中的线程安全问题是由实例变量造成的,只要在Servlet里面的任何方法里面都不使用实例变量,那么该Servlet就是线程安全的。

springMVC,非线程安全,controller是单例,但它不依赖成员变量传参,因此基本没影响;单例不会创建对象和垃圾回收;

spring的Controller默认是Singleton的,这意味着每个request过来,系统都会用原有的instance去处理,这样导致了两个结果:一是我们不用每次创建Controller,二是减少了对象创建和垃圾收集的时间;由于只有一个Controller的instance,当多个线程调用它的时候,它里面的instance变量就不是线程安全的了。

如何保证 spring mvc 线程安全?

  • 1.Controller使用ThreadLocal成员变量。
  • 2.将spring mvc 的 Controller中声明 scope=”prototype”,每次都创建新的controller .

servlet和struts1均是单实例、多线程,在不加锁的情况下尽量不要使用局部变量等带来线程不安全的变量,只有请求和局部变量时线程安全的。

struts2,线程安全,一个action请求,对应一个实例;

Struts 2 Action对象为每一个请求产生一个实例,因此没有线程安全问题。所以我们可以在Struts2的Action里面去定义属性。但是Struts2由于 Action和普通的Java类没有任何区别(也就是不用像Struts1里面那样去实现一个Struts的接口,有兴趣的朋友可以自己去了解),所以我们可以用Spring去管理Struts2的Action,这个时候我们就要注意了,因为当我们在spring里面去定义bean的时候,spring默认用的是单例模式。所以在这个时候,你就要修改Spring的配置文件—-即修改scope为prototype。

为什么struts1中并没有考虑到线程问题,因为所有的代码都是写在execute的方法中,所有变量都是定义在里面,所以没有线程安全问题。
而现在的struts2就不一样了。struts2的action中就像一个POJO一样,定义了很多的类变量。这就有线程安全问题了。。此时,就使用scope=prototype来指定是个原型模式,而不是单例,这样就解决了线程安全问题。每个线程都是一个新的实例.

Spring 单例Bean和Java 单例模式

区别:Spring的的单例是基于BeanFactory也就是spring容器,单例Bean在此Spring容器内是单个的,Java的单例是基于JVM,每个JVM内一个单例。

四、静态同步方法,非静态同步方法,同步语句块

  • 对于实例同步方法,锁是当前实例对象。
  • 对于静态同步方法,锁是当前对象的Class对象。
  • 对于同步方法块,锁是Synchonized括号里配置的对象。

进行多线程编程,同步控制是非常重要的,而同步控制就涉及到了锁。

对代码进行同步控制我们可以选择同步方法,也可以选择同步块,这两种方式各有优缺点,至于具体选择什么方式,就见仁见智了,同步块不仅可以更加精确的控制对象锁,也就是控制锁的作用域,何谓锁的作用域?锁的作用域就是从锁被获取到其被释放的时间。而且可以选择要获取哪个对象的对象锁。但是如果在使用同步块机制时,如果使用过多的锁也会容易引起死锁问题,同时获取和释放所也有代价,而同步方法,它们所拥有的锁就是该方法所属的类的对象锁,换句话说,也就是this对象,而且锁的作用域也是整个方法,这可能导致其锁的作用域可能太大,也有可能引起死锁,同时因为可能包含了不需要进行同步的代码块在内,也会降低程序的运行效率。而不管是同步方法还是同步块,我们都不应该在他们的代码块内包含无限循环,如果代码内部要是有了无限循环,那么这个同步方法或者同步块在获取锁以后因为代码会一直不停的循环着运行下去,也就没有机会释放它所获取的锁,而其它等待这把锁的线程就永远无法获取这把锁,这就造成了一种死锁现象。

详细解说一下同步方法的锁,同步方法分为静态同步方法与非静态同步方法。

所有的非静态同步方法用的都是同一把锁——实例对象本身,也就是说如果一个实例对象的非静态同步方法获取锁后,该实例对象的其他非静态同步方法必须等待获取锁的方法释放锁后才能获取锁,可是别的实例对象的非静态同步方法因为跟该实例对象的非静态同步方法用的是不同的锁,所以毋须等待该实例对象已获取锁的非静态同步方法释放锁就可以获取他们自己的锁。

而所有的静态同步方法用的也是同一把锁——类对象本身,这两把锁是两个不同的对象,所以静态同步方法与非静态同步方法之间是不会有竞态条件的。但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁,而不管是同一个实例对象的静态同步方法之间,还是不同的实例对象的静态同步方法之间,只要它们同一个类的实例对象!

对于同步块,由于其锁是可以选择的,所以只有使用同一把锁的同步块之间才有着竞态条件,这就得具体情况具体分析了,但这里有个需要注意的地方,同步块的锁是可以选择的,但是不是可以任意选择的!!!!这里必须要注意一个物理对象和一个引用对象的实例变量之间的区别!使用一个引用对象的实例变量作为锁并不是一个好的选择,因为同步块在执行过程中可能会改变它的值,其中就包括将其设置为null,而对一个null对象加锁会产生异常,并且对不同的对象加锁也违背了同步的初衷!这看起来是很清楚的,但是一个经常发生的错误就是选用了错误的锁对象,因此必须注意:同步是基于实际对象而不是对象引用的!多个变量可以引用同一个对象,变量也可以改变其值从而指向其他的对象,因此,当选择一个对象锁时,我们要根据实际对象而不是其引用来考虑!作为一个原则,不要选择一个可能会在锁的作用域中改变值的实例变量作为锁对象!!!!

51515e312cdbf93aaf2f59871201ef3a.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值