线程限制性
通俗的讲,就是不想共享数据了,在自己的线程内部操作。
Ad-hoc线程限制
Ad-hoc线程限制是指维护线程限制性的任务全部落在实现上的这种情况。因为没有可见性修饰符与本地变量等语言特性协助将对象限制在目标线程上,所以这种方式是非常容易出错的。
栈限制
线程限制一种特例,通过本地变量触及对象。本地变量本身就被限制执行线程中:存在于这个执行线程栈。其他线程无法访问。
ThreaLocal
一种维护线程限制的更加规范的方式是使用 Threadlocal,它允许你将每个线程与持有数值的对象关联在一起。 ThreadLocal提供了get与set访问器,为每个使用它的线程维护一份单独的拷贝。所以get总是返回由当前执行线程通过set设置的最新值。
应用场景
ThreaLocal主要为了防止在基于可变的单体或全局变量的设计中,出现共享的情况。
两种场景:
1、数据库连接获取Connection。
一个单线程应用可能会维护一个全局的数据库连接。此Connection在启动时就已经被初始化。这样就避免了为每一个方法都传递一个Connection。因为JDBC规范并未要求Connection本身一定是线程安全的。通过利用ThreadLocal存储JDBC连接,每个线程都会拥有属于自己的Connection。
2、一个频繁执行的操作急需要向buffer这样的临时对象,还需要避免每次都重新分配该临时对象。
在springboot中应用
首先封装ThreadLocal,设置get和set方法以及移除方法,在一次请求中,在Filter中拦截请求,在doFiler中存储当前线程的ID,在Controller里面可以调用ThreadLocal中的方法,比如使用get方法获取线程ID,之后在调用完之后,在拦截器里面调用ThreadLocal中移除方法,将值移除。这里在主方法里面要注册过滤器并建立实例,在继承的类里添加拦截器实例。
StringBuffer:线程安全 -- 源码里面方法含有Synchronized关键字,但是由于含有了这个关键字,性能会有所下降。
StringBuilder:相爱难测不安全 -- 源码里面方法不含有Synchronized关键字。为什么依旧使用?因为在一个方法中采用定义局部变量的方法来使用时,由于存在堆栈限制,也是线程安全的,而且性能比StringBuffer更好。
SimpleDateForma:非线程安全共享对象,一旦有多个对象调用,会发送异常。
解决办法:可以使用堆栈限制方法,在方法中建立对象(每次调用都会新建一次,局部变量概念),就可以正常运行。
JotaTime终端DateTime:线程安全,数据处理上有更多优势。(推荐)
ArrayList:线程不安全
HashSet:线程不安全
HashMap:线程不安全
先检查再执行:if(condition(a)) { handle(a) } :线程不安全,如果两个线程都访问到了if,且都通过了,就会发生线程不安全。不具有原子性。
同步容器
ArrayList-->Vector、Stack(两者方法前有同步关键字)
Vector:在多线程操作顺序不一样的时候(比如一个删除元素,一个获取元素,加入当线程执行到第9个元素时要获取时,此时另一个线程将第九个元素移除了),也会造成异常,线程不安全。
另外,遍历时不做更新,单线程、多线程(概率更大)都会发生异常。解决办法:在iterator时,添加synchronized关键字做同步,还可以使用并发容器。
HashMap-->Hashtable(key、value不能为空)
Collections.synchronizedXXX(List、Set、Map):工具同步类
安全共享对象策略
线程限制:一个被线程限制的对象,由线程独占,并且只能被占有他的线程修改。
共享只读:一个共享只读的对象,在没有额外同步的情况下,可以被多个线程并发访问,但是任何线程都不能修改他
线程安全对象:一个线程安全的对象或者容器,在内部通过同步机制来保证线程安全,所以其他线程无需额外的同步就可以通过公共接口随意访问它
被守护对象:被守护对象只能通过获取特定的锁来访问。