线程安全-当多个线程在操作同一个共享数据时,就可能发生线程并发问题。
如何确保线程安全?
第一种:保证数据在同一时间只能被一个线程访问。如:使用synchronism锁定数据、使用Lock类锁定数据等。
第二种:保证数据为一个线程私有。如:将数据变量放入ThreadLocal,并在其它任何其它线程可访问地方去除数据引用。
第三种:数据为只读。如:对数据变量使用final修饰或在编码中保证不对数据进行写操作。
*** synchronized
synchronized关键字,就是用来控制线程同步的,保证我们的线程在多线程环境下,不被多个线程同时执行,确保我们数据的完整性,使用方法一般是加在方法上。
这样就可以确保我们的线程同步了,同时这里需要注意一个大家平时忽略的问题,首先synchronized锁的是括号里的对象,而不是代码,其次,对于非静态的synchronized方法,锁的是对象本身也就是this。
当synchronized锁住一个对象之后,别的线程如果想要获取锁对象,那么就必须等这个线程执行完释放锁对象之后才可以,否则一直处于等待状态。
注意点:虽然加synchronized关键字,可以让我们的线程变得安全,但是我们在用的时候,也要注意缩小synchronized的使用范围,如果随意使用时很影响程序的性能,别的对象想拿到锁,结果你没用锁还一直把锁占用,这样就有点浪费资源。
*** Lock
private Lock lock = new ReentrantLock(); // ReentrantLock是Lock的子类
private void method(Thread thread){
lock.lock(); // 获取锁对象
try {
System.out.println("线程名:"+thread.getName() + "获得了锁");
// Thread.sleep(2000);
}catch(Exception e){
e.printStackTrace();
} finally {
System.out.println("线程名:"+thread.getName() + "释放了锁");
lock.unlock(); // 释放锁对象}}
进入方法我们首先要获取到锁,然后去执行我们业务代码,这里跟synchronized不同的是,Lock获取的锁对象需要我们亲自去进行释放,为了防止我们代码出现异常,所以我们的释放锁操作放在finally中,因为finally中的代码无论如何都是会执行的。
*** 悲观锁(就是不管是否发生多线程冲突,只要存在这种可能,就每次访问都加锁,加锁就会导致锁之间的争夺,有争夺就会有输赢,输者等待。)
读锁(共享锁):语法-lock in share mode
写锁(排他锁):语法:for update
*** 乐观锁(获得锁后一直持有锁以防本线程再次申请该锁造成无谓的解锁再加锁开销。
乐观锁需要自己实现Hibernate中支持乐观锁)
闭锁:可以延迟线程的进度直到线程到达某个终点状态
什么是线程池?
JDK5中增加了Doug Lea的并法库,这一引进给Java线程的管理和使用提供了强大的便利性。
java.util.current包中提供了对线程优化、管理的各项操作,使得线程的使用变的得心应手。
该包提供了线程的运行、线程池的创建以及线程生命周期的控制。
常见线程池:
①newSingleThreadExecutor
单个线程的线程池,即线程池中每次只有一个线程工作,单线程串行执行任务
②newFixedThreadExecutor(n)
固定数量的线程池,每提交一个任务就是一个线程,直到达到线程池的最大数量,然后后面进入等待队列直到前面的任务完成才继续执行
③newCacheThreadExecutor(推荐使用)
可缓存线程池,当线程池大小超过了处理任务所需的线程,那么就会回收部分空闲(一般是60秒无执行)的线程,当有任务来时,又智能的添加新线程来执行。
④newScheduleThreadExecutor
大小无限制的线程池,支持定时和周期性的执行线程
线程池的应用域?
多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力。
假设一个服务器完成一项任务所需时间为:T1 创建线程时间,T2 在线程中执行任务的时间,T3 销毁线程时间。
如果:T1 + T3 远大于 T2,则可以采用线程池,以提高服务器性能。