什么是线程安全和线程不安全
线程安全:多个线程在执行同一段代码时采用加锁机制,使每次的执行结果和单线程执行的结果都是一样的,不存在执行结果的二义性。
线程不安全:不提供加锁机制保护,有可能出现多个线程先后更改数据造成所得到的的数据是脏数据。
Java中Synchronized的用法解析
synchronized 是java中的关键字,是一种同步锁。它修饰的对象有以下几种:
- 修饰一个**代码块**,被修饰的代码块称为同步语句块,其作用范围是大括号{}括起来的代码,作用对象是调用这个代码块的对象;
- 修饰一个**方法**,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
- 修饰一个**静态的方法**,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
- 修饰一个**类**,其作用的范围是synchronized后面括号括起来的部分,作用的对象是这个类的所有对象。
在flink_parse_java 解析代码中,获取解析协议时(sqlsession Factory),使用了同步锁(修饰静态方法):
public class MybatisSessionFactory {
private static final Logger LOG = LoggerFactory.getLogger(MybatisSessionFactory.class);
private static SqlSessionFactory sqlSessionFactory;
private MybatisSessionFactory(){
super();
}
public synchronized static SqlSessionFactory getSqlSessionFactory(){
if(null==sqlSessionFactory){
InputStream inputStream=null;
try{
inputStream = MybatisSessionFactory.class.getClassLoader().getResourceAsStream("mybatis-config.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
catch (Exception e){
LOG.error("create MybatisSessionFactory read mybatis-config.xml cause Exception",e);
}
if(null!=sqlSessionFactory){
LOG.info("get Mybatis sqlsession sucessed....");
}
else {
LOG.info("get Mybatis sqlsession failed....");
}
}
return sqlSessionFactory;
}
}
线程同步的几种方式
进程有自己的独立地址空间,因此进程之间重点关注通信;
线程除了线程栈外其他数据都是共享的,如果同时读写数据可能造成数据不一致甚至程序崩溃的后果,因此线程之间重点关注同步。
线程同步概念同其他“同步”不太一致,指的是线程之间“协同”,即线程之间按照规定的先后次序运行。
Java学习路线分享MyBatis之线程优化
前言:
我们的项目中存在大量用户同时访问的情况,那么就会出现大量线程并发访问数据库,这样会带来线程同步问题。本章将讨论mybatis的线程同步问题和优化方法。
mybatis 的线程同步问题
mybatis 需要通过sqlsession实现数据库操作,而SQLsession 内部的实现需要使用JDBC的connection连接对象,而connection对象是非线程安全的,当多个线程同时访问时,就可能出现线程同步问题。
线程同步的解决方法
- 锁机制,给所有数据库相关方法或代码添加synchronized关键字(会带来执行效率的降低,若大量用户访问时会导致长时间的等待)
- ThreadLocal(线程局部变量),可以为每个线程创建对象的副本,以空间换时间,效率高,速度快,但内存空间消耗更大。
ThreadLocal<SqlSession> threadLocal = new ThreadLocal<SqlSession>();
Lock
Lock 对比 synchronized有以下几点不同:
- Lock 是一个接口,而synchronized是java中的关键字,synchronized是内置的语言实现的;
- synchronized 在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
- Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
- 通过Lock可以知道有没有成功获取锁,而synchronized却无法办到;
- Lock可以提高多个线程进行读操作的效率。
线程池
为什么要用线程池?
new Thread(new Runable(){
public void run(){
longTest.countTest();
}
}).start()
不建议使用“直接new一个线程”方法。这是因为创建大量相同线程会消耗系统内存,甚至会导致系统内存耗尽;同时,大量的线程会竞争CPU的调度,导致CPU过度切换。
线程池的优点
- 减少在创建和销毁线程上所花费的时间和系统资源的开销
- 提高响应速度,当任务到达之后,任务可不需要等到线程创建就能被立即执行
- 提高线程的可管理性,线程是稀缺资源,若无限制的创建,不仅会消耗系统资源,还会降低系统性能,使用线程池可以进行统一分配,调优和监控。
创建线程池的几种方式
- 创建一个缓存线程池
public static ExecutorService newCachedThreadPool(){ return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()) }
synchronousQueue 队列不作为任务的缓冲方式,可以简单理解为队列长度为零,当任务过多时,大量任务堆积到队列可能发生OOM异常。
- 创建固定容量的线程池
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory); }
LinkedBlockingQueue 没有指定队列的容量,队列的最大容量可达Integer.MAX_VALUE。当有大量请求时,可能造成任务的大量堆积,发生OOM异常。
- 创建单个线程的线程池
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
综上,线程池不允许使用 Executors 去创建,而是通过ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
ThreadPoolExecutor 类
参数详解:
corePoolSize:核心线程数,线程池初始化时默认是没有线程的,当任务来临时才开始创建线程去执行任务。
maximumPoolSize:最大线程数,只有当workQueue队列填满时才会创建多于corePoolsize的线程。
keepAliveTime:非核心线程空闲时间超过keepAliveTime就会被自动终止回收掉。
unit:keepAliveTime的时间单位。
workQueue:用于保存任务的队列,可以为无界、有界、同步移交三种队列类型之一,当池子中工作线程数大于corePoolSize时,新进来的任务会被放入队列中。
threadFactory:创建线程的工厂类,默认使用Executors.defaultThreadFactory()。
handler:线程池无法继续接受任务(队列已满且线程数达到maximumPoolSize)时的饱和策略。
线程池中的线程创建流程图: