java 线程与线程池

Posted by 果然 on May 7, 2022

什么是线程安全和线程不安全

线程安全:多个线程在执行同一段代码时采用加锁机制,使每次的执行结果和单线程执行的结果都是一样的,不存在执行结果的二义性。
线程不安全:不提供加锁机制保护,有可能出现多个线程先后更改数据造成所得到的的数据是脏数据。

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可以提高多个线程进行读操作的效率。
在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。

线程池

为什么要用线程池?

new Thread(new Runable(){
		public void run(){
			longTest.countTest();	
	}
}).start()

不建议使用“直接new一个线程”方法。这是因为创建大量相同线程会消耗系统内存,甚至会导致系统内存耗尽;同时,大量的线程会竞争CPU的调度,导致CPU过度切换。

线程池的优点

  1. 减少在创建和销毁线程上所花费的时间和系统资源的开销
  2. 提高响应速度,当任务到达之后,任务可不需要等到线程创建就能被立即执行
  3. 提高线程的可管理性,线程是稀缺资源,若无限制的创建,不仅会消耗系统资源,还会降低系统性能,使用线程池可以进行统一分配,调优和监控。

创建线程池的几种方式

  1. 创建一个缓存线程池
    public static ExecutorService newCachedThreadPool(){
     return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,
                                   new SynchronousQueue<Runnable>())
    }
    

    synchronousQueue 队列不作为任务的缓冲方式,可以简单理解为队列长度为零,当任务过多时,大量任务堆积到队列可能发生OOM异常。

  2. 创建固定容量的线程池
    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异常。

  3. 创建单个线程的线程池
     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)时的饱和策略。

线程池中的线程创建流程图:

thread