1.创建多线程后,多线程执行过程中是否会影响到数据的一致性。
2.并发请求中:
1). 静态变量即类变量,位于方法区,为所有对象共享,共享一份内存,一旦静态变量被修改,其他对象均对修改可见,故线程非安全。
2). 实例变量(类变量)为对象实例私有,若在系统中只存在一个此对象的实例,在多线程环境下,“犹如”静态变量那样,被某个线程修改后,其他线程对修改均可见,故线程非安全;
注意:如果每个线程执行都是在不同的对象中,那对象与对象之间的实例变量的修改将互不影响,故线程安全。
3.案例:
1)方法中的局部变量:线程安全
public void someMethod(){
long threadSafeInt = 0;
threadSafeInt++;
}
2) 方法中的实例:线程安全
public void someMethod(){
LocalObject localObject = new LocalObject();
localObject.callMethod();
method2(localObject);
}
public void method2(LocalObject localObject){
localObject.setValue("value");
}
3)类中的变量(实例):线程不安全
public class NotThreadSafe{
StringBuilder builder = new StringBuilder();
public add(String text){
this.builder.append(text);
}
}
4.Java高并发的情况下如果出现共享变量,如何保证线程安全的解决方案
1)ThreadLocal : 当10个客户端同时请求同一个接口,这样就产生了10个线程,当这10个线程需要共享一个变量时,就可能出现脏读等线程安全问题。ThreadLocal便解决了这个问题。ThreadLocal会把每一个线程变量的值存储到本地,线程之间不共用数据,从而杜绝数据脏读等问题
private static ThreadLocal<String> t1=new ThreadLocal<String>();
//ThreadLocal通过set和get来存储和获取数据
if(t1 == null) {
t1.set("变量值")
}
t1.get();
2)InheritableThreadLocal : ThreadLocal确实从一定程度上解决了线程安全的问题,但也有缺点,那就是父子线程之间不能进行值传递。我们先了解一下父子线程,按照我对业务上的理解,那便是一个接口请求另一个接口,第二个接口的线程是由第一个接口的线程引发的,第一个接口的线程则为第二个接口线程的父线程。InheritableThreadLocal为ThreadLocal的子类,一定程度上解决了父子线程之间传值的问题,同样的,也是使用set和get来存储和获取数据
private static InheritableThreadLocal<String> t1=new InheritableThreadLocal<String>();
//父线程set值
if(t1 == null) {
t1.set("变量值")
}
//子线程get值
t1.get();
3)TransmittableThreadLocal : InheritableThreadLocal却没有完美解决父子线程间传值问题,当我们为了减小开销而使用线程池(作用:线程池会缓存已经使用过的线程)的时候,InheritableThreadLocal set的值没有清除,会导致第二次任务会得到第一次set的值,为了避免这种问题,阿里的TransmittableThreadLocal出现了
pom:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.2.0</version>
</dependency>
private static TransmittableThreadLocal <String> t1=new TransmittableThreadLocal <String>();
//父线程set值
if(t1 == null) {
t1.set("变量值")
}
//子线程get值
t1.get();
5.高并发下,如何做到安全的修改同一行数据
1)使用悲观锁 : 悲观锁本质是当前只有一个线程执行操作,排斥外部请求的修改。遇到加锁的状态,就必须等待。结束了唤醒其他线程进行处理。也就是说这样的修改请求多了,每个请求都需要等待“锁”,某些线程可能永远都没有机会抢到这个“锁”,这种请求就会死在那里。同时,这种请求多了,会增大系统的平均响应时间,结果是可用连接数被耗尽,系统陷入异常。
注意:需要注意的是如果where子句条件没有命中索引将导致锁表。并且查询和更新操作都需要在同一个事务里里面。
2)消息队列 : 将请求放入队列中,就不会导致某些请求永远获取不到锁。
3)使用乐观锁 : 乐观锁,是采用带版本号(数据库表增加字段:version)更新的方式。实现就是,所有请求都有资格去修改这条数据,但会获得一个该数据的版本号,只有版本号符合的才能更新成功,其他的返回失败。这样的话,我们就不需要考虑队列的问题,不过,它会增大CPU的计算开销。但是,综合来说,这是一个比较好的解决方案。
注意:版本号:当我们提交数据更新的时候,会判断当前数据库中对应记录的版本号与读取数据时的版本号进行比对,如果版本标识值相等,则更新,否则认为是过期数据。
表结构:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.xiaohao.test">
<class name="User" table="user" optimistic-lock="version" >
<id name="id">
<generator class="native" />
</id>
<!--version标签必须跟在id标签后面-->
<version column="version" name="version" />
<property name="userName"/>
<property name="password"/>
</class>
</hibernate-mapping>
4)CAS : 当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。
CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。
流程:如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。
6.MyBatis实现乐观锁和悲观锁:
https://blog.youkuaiyun.com/feiyangtianyao/article/details/98845945