springsingleton实例中的变量怎么保证线程安全
pring中管理的bean实例默认情况下是单例的[sigleton类型],就还有prototype类型
按其作⽤域来讲有sigleton,prototype,request,session,global session。
spring中的单例与设计模式⾥⾯的单例略有不同,设计模式的单例是在整个应⽤中只有⼀个实例,⽽spring中的单例是在⼀个IoC容器中就只有⼀个实例。
但spring中的单例也不会影响应⽤的并发访问,【不会出现各个线程之间的等待问题,或是死锁问题】因为⼤多数时候客户端都在访问我们应⽤中的业务对象,⽽这些业务对象并
没有做线程的并发限制,只是在这个时候我们不应该在业务对象中设置那些容易造成出错的成员变量,在并发访问时候这些成员变量将会是并发线程中的共享对象,那么这个时候
就会出现意外情况。
那么我们的Eic-server的所有的业务对象中的成员变量如,在Dao中的xxxDao,或controller中的xxxService,都会被多个线程共享,那么这些对象不会出现同步问题吗,⽐如会造
成数据库的插⼊,更新异常?
还有我们的实体bean,从客户端传递到后台的controller-->service-->Dao,这⼀个流程中,他们这些对象都是单例的,那么这些单例的对象在处理我们的传递到后台的实体bean不
会出问题吗?
答:[实体bean不是单例的],并没有交给spring来管理,每次我们都⼿动的New出来的【如EMakeType et = new EMakeType();】,所以即使是那些处理我们提交数据的业务处理类
是被多线程共享的,但是他们处理的数据并不是共享的,数据时每⼀个线程都有⾃⼰的⼀份,所以在数据这个⽅⾯是不会出现⽅⾯的问题的。但是那些的在Dao中的
xxxDao,或controller中的xxxService,这些对象都是单例那么就会出现的问题。但是话⼜说回来了,这些对象虽然会被多个进程并发访问,可我们访问的是他们⾥⾯的⽅
法,这些类⾥⾯通常不会含有成员变量,那个Dao⾥⾯的ibatisDao是框架⾥⾯封装好的,已经被测试,不会出现问题了。所以出问题的地⽅就是我们⾃⼰系统⾥⾯的业务
对象,所以我们⼀定要注意这些业务对象⾥⾯千万不能要独⽴成员变量,否则会出错。
所以我们在应⽤中的业务对象如下例⼦;
controller中的成员变量List和paperService:
public class TestPaperController extends BaseController {
private static final int List = 0;
@Autowired
@Qualifier("papersService")
private TestPaperService papersService ;
public Page queryPaper(int pageSize, int page,TestPaper paper) throws EicException{
RowSelection localRowSelection = getRowSelection(pageSize, page);
List<TestPaper> paperList = papersService.queryPaper(paper,localRowSelection);
Page localPage = new Page(page, TotalRows(),
paperList);spring roll怎么读
return localPage;
}
service⾥⾯的成员变量ibatisEntityDao:
@SuppressWarnings("unchecked")
@Service("papersService")
@Transactional(rollbackFor = { Exception.class })
public class TestPaperServiceImpl implements TestPaperService {
@Autowired
@Qualifier("ibatisEntityDao")
private IbatisEntityDao ibatisEntityDao;
private static final String NAMESPACE_TESTPAPER = "del.TestPaper";
private static final String BO_NAME[] = { "试卷仓库" };
private static final String BO_NAME2[] = { "试卷配置试题" };
private static final String BO_NAME1[] = { "试卷试题类型" };
private static final String NAMESPACE_TESTQUESTION="del.TestQuestion";
public List<TestPaper> queryPaper(TestPaper paper,RowSelection paramRowSelection) throws EicException{
try {
return (List<TestPaper>) ibatisEntityDao.queryForListWithPage(
NAMESPACE_TESTPAPER, "queryPaper", paper,paramRowSelection);
} catch (Exception exception) {
exception.printStackTrace();
throw new EicException(exception, "eic", "0001", BO_NAME);
}
}
由上⾯可以看出,虽然我们这个应⽤⾥⾯含有成员变量,但是并不会出现线程同步⽅⾯的问题,因为,controller⾥⾯的成员变量private TestPaperService papersService ;之
所以会成为成员变量,我们的⽬的是注⼊,将其实例化进⽽访问⾥⾯的⽅法,private static final int List = 0;是final的不会被改变。
service⾥⾯的private IbatisEntityDao ibatisEntityDao;是框架本⾝的线程同步问题已解决【其解决⽅案很有可能就是使⽤ThreadLocal,见下⾯】。
这下⾯的bean ⼀个是通过BeanFactory getBean得到,⼀个是业务对象Class(),得到,通过不同客户端的浏览器访问,可得到下⾯结论,
springIoC容器管理的bean就是单例,因为不同的访问均得到相同的对象【在应⽤开启的状态下,不重新启动应⽤下,即在同⼀次的应⽤运⾏中】
-------------------------spring 中的sigleton ,这才是真正的整个应⽤下⾯就⼀个实例:class
am.testpaper.service.impl.TestPaperServiceImpl$$EnhancerByCGLIB$$584b889d
-------------------------spring 中的sigleton ,这才是真正的整个应⽤下⾯就⼀个实例:class
am.testpaper.service.impl.TestPaperServiceImpl$$EnhancerByCGLIB$$584b889d
-------------------------spring 中的sigleton ,这才是真正的整个应⽤下⾯就⼀个实例:class
am.testpaper.service.impl.TestPaperServiceImpl$$EnhancerByCGLIB$$584b889d
-------------------------spring 中的sigleton ,这才是真正的整个应⽤下⾯就⼀个实例:class
am.testpaper.service.impl.TestPaperServiceImpl$$EnhancerByCGLIB$$584b889d
Spring框架对单例的⽀持是采⽤单例注册表的⽅式进⾏实现的,详见“Spring设计模式——”这篇⽂章。
⾄于spring如何实现那些个有状态bean[如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder]的,如下原理:详见“ThreadLocal”,还可以参考⽹上这篇⽂章:“浅谈Spring管理ThreadLocal和JDKProxy”。
  虽然代码清单9‑3这个ThreadLocal实现版本显得⽐较幼稚,但它和JDK所提供的ThreadLocal类在实现思路上是相近的。
在Java的多线程编程中,为保证多个线程对共享变量的安全访问,通常会使⽤synchronized来保证同⼀时刻只有⼀个线程对共享变量进⾏操作。但在有些情况下,synchronized不能保证多线程对共享变量的正确读写。例如类有⼀个类变量,该类变量会被多个类⽅法读写,当多线程操作该类的实例对象时,如果线程对类变量有读取、写⼊操作就会发⽣类变量读写错误,即便是在类⽅法前加上synchronized也⽆效,因为同⼀个线程在两次调⽤⽅法之间时锁是被释放的,这时其它线程可以访问对象的类⽅法,读取或修改类变量。这种情况下可以将类变量放到ThreadLocal类型的对象中,使变量在每个线程中都有独⽴拷贝,不会出现⼀个线程读取变量时⽽被另⼀个线程修改的现象。
  为了说明多线程访问对于类变量和ThreadLocal变量的影响,QuerySvc中分别设置了类变量sql和ThreadLocal变量,使⽤时先创建QuerySvc的⼀个实例对象,然后产⽣多个线程,分别设置不同的sql实例对象,然后再调⽤execute⽅法,读取sql的值,看是否是set⽅法中写⼊的值。这种场景类似web应⽤中多个请求线程携带不同查询条件对⼀个servlet实例的访问,然后servlet调⽤业务对象,并传⼊不同查询条件,最后要保证每个请求得到的结果是对应的查询条件的结果。
  先创建⼀个QuerySvc实例对象,然后创建若⼲线程来调⽤QuerySvc的set和execute⽅法,每个线程传
⼊的sql都不⼀样,从运⾏结果可以看出sql变量中值不能保证在execute中值和set设置的值⼀样,在 web应⽤中就表现为⼀个⽤户查询的结果不是⾃⼰的查询条件返回的结果,⽽是另⼀个⽤户查询条件的结果;⽽ThreadLocal中的值总是和set中设置的值⼀样,这样通过使⽤ThreadLocal获得了性。
  如果⼀个对象要被多个线程访问,⽽该对象存在类变量被不同类⽅法读写,为获得,可以⽤ThreadLocal来替代类变量。
同步机制的⽐较  ThreadLocal和线程同步机制相⽐有什么优势呢?ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。
  在同步机制中,通过对象的锁机制保证同⼀时间只有⼀个线程访问变量。这时该变量是多个线程共享的,使⽤同步机制要求程序慎密地分析什么时候对变量进⾏读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较⼤。
  ⽽ThreadLocal则从另⼀个⾓度来解决多线程的并发访问。ThreadLocal会为每⼀个线程提供⼀个独⽴的变量副本,从⽽隔离了多个线程
对数据的访问冲突。因为每⼀个线程都拥有⾃⼰的变量副本,从⽽也就没有必要对该变量进⾏同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。
  由于ThreadLocal中可以持有任何类型的对象,低版本JDK所提供的get()返回的是Object对象,需要强制类型转换。但JDK 5.0通过泛型很好的解决了这个问题,在⼀定程度地简化ThreadLocal的使⽤,代码清单 9 2就使⽤了JDK 5.0新的ThreadLocal版本。
  概括起来说,对于多线程资源共享的问题,同步机制采⽤了“”的⽅式,⽽ThreadLocal采⽤了“”的⽅式。前者仅提供⼀份变量,让不同的线程排队访问,⽽后者为每⼀个线程都提供了⼀份变量,因此可以同时访问⽽互不影响。
  Spring使⽤ThreadLocal解决线程安全问题
  我们知道在⼀般情况下,只有⽆状态的Bean才可以在多线程环境下共享,在Spring中,绝⼤部分Bean都可以声明为singleton作⽤域。就是因为Spring对⼀些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中⾮线程安全状态采⽤ThreadLocal进⾏处理,让它们也成为线程安全的状态,因为有状态的Bean就可以在多线程中共享了。
  ⼀般的Web应⽤划分为展现层、服务层和持久层三个层次,在不同的层中编写对应的逻辑,下层通过接⼝向上层开放功能调⽤。在⼀般情况下,从接收请求到返回响应所经过的所有程序调⽤都同属于⼀个线程,如图9‑2所⽰:
  这样你就可以根据需要,将⼀些⾮线程安全的变量以ThreadLocal存放,在同⼀次请求响应的调⽤线程中,所有关联的到的都是同⼀个变量。
  由于①处的conn是成员变量,因为addTopic()⽅法是⾮线程安全的,必须在使⽤时创建⼀个新TopicDao实例(⾮singleton)。下⾯使⽤ThreadLocal对conn这个⾮线程安全的“状态”进⾏改造:
  代码清单4 TopicDao:线程安全
  import java.sql.Connection;
  import java.sql.Statement;
  public class TopicDao {
  ①使⽤ThreadLocal保存Connection变量
  private static ThreadLocal connThreadLocal = new ThreadLocal();
  public static Connection getConnection(){
  ②如果connThreadLocal没有本线程对应的Connection创建⼀个新的Connection,
  并将其保存到线程本地变量中。
  if (connThreadLocal. get() == null) {
  Connection conn = Connection();
  connThreadLocal.set(conn);
  return conn;
  }else{
  return connThreadLocal. get();③直接返回线程本地变量
  }
  }
  public void addTopic() {
  ④从ThreadLocal中获取线程对应的Connection
  Statement stat = getConnection().createStatement();
  }
  }
  不同的线程在使⽤TopicDao时,先判断connThreadLocal.是否是null,如果是null,则说明当前线程还没有对应的Connection对象,这时创建⼀个Connection对象并添加到本地线程变量中;如果不为null,则说明当前的线程已经拥有了Connection对象,直接使⽤就可以了。这样,就保证了不同的线程使⽤线程相关的Connection,⽽不会使⽤其它线程的Connection。因此,这个TopicDao就可以做到singleton共享了。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。