不可变对象java
不可变对象(Immutable Objects)是指对象一旦被建立它的状态(对象的数据,也即对象属性值)就不能改变,任何对它的改变都应该产生一个新的对象。spring
不可变对象须要知足的条件:数据库
除了使用final自行封装不可变对象以外,还能够经过如下两种方式定义不可变对象api
线程封闭数组
当访问共享的可变数据时,一般须要同步。一种避免同步的方式就是不共享数据。若是仅在单线程内访问数据,就不须要同步,这种技术称为线程封闭。安全
常见线程封闭手段:数据结构
spring中必定要在拦截器afterCompletion中,执行threadlocal的remove函数,线程池中使用同理。多线程
同步容器架构
stringbuilder:线程不安全(能够在函数中定义,利用堆栈封闭避免了线程不安全,同时节省了加锁的消耗,性能更好)并发
stringbuffer:线程安全(每一个函数都是用synchronized修饰),能够作全局变量。
SimpleDateFormat:JDK中的工具类,线程不安全。使用方法能够参考stringbuilder。
JodaTime:线程安全,功能更丰富。
ArrayList/HashSet/HashMap等Collections:都是线程不安全的
Vector/Stack/HashTable:都是线程安全的
先检查再执行:if(condition(a)){handle(a)},这种形式若是没有加锁的话,就不是原子性,也是线程不安全的
并发容器
线程安全的容器除了上文提到的同步容器一些外,在Java的J.U.C(java.utils.concurrent的缩写)下,一样提供了线程安全的并发容器。
注意:并发容器的批量操做都不是线程安全的,例如调用removeAll,containsAll等,须要自行加锁。
CopyOnWriteArrayList、CopyOnWriteArraySet,这种利用cow特性的数据结构,须要copy消耗内存,可能引起gc。
想免费学习(Java工程化、分布式架构、高并发、高性能、深刻浅出、微服务架构、Spring、MyBatis、Netty、源码分析)等技术的朋友,能够加群:834962734,群里有阿里大牛直播讲解技术,以及Java大型互联网技术的视频免费分享给你们,欢迎进群一块儿深刻交流学习,无论你是转行,仍是工做中想提高本身能力均可以!
线程死锁是指因为两个或者多个线程互相持有对方所须要的资源,致使这些线程处于等待状态,没法前往执行。
死锁的必要条件
死锁示例代码:
@Slf4j public class DeadLock implements Runnable { public int flag = 1; //静态对象是类的全部对象共享的 private static Object o1 = new Object(), o2 = new Object(); @Override public void run() { log.info("flag:{}", flag); if (flag == 1) { synchronized (o1) { try { Thread.sleep(500); } catch (Exception e) { e.printStackTrace(); } synchronized (o2) { log.info("1"); } } } if (flag == 0) { synchronized (o2) { try { Thread.sleep(500); } catch (Exception e) { e.printStackTrace(); } synchronized (o1) { log.info("0"); } } } } public static void main(String[] args) { DeadLock td1 = new DeadLock(); DeadLock td2 = new DeadLock(); td1.flag = 1; td2.flag = 0; //td1,td2都处于可执行状态,但JVM线程调度先执行哪一个线程是不肯定的。 //td2的run()可能在td1的run()以前运行 new Thread(td1).start(); new Thread(td2).start(); } }
避免死锁的方法
死锁排查方法
虽然形成死锁的缘由是由于咱们设计得不够好,可是可能写代码的时候不知道哪里发生了死锁。
JDK提供了两种方式来给咱们检测:
检测出死锁时的解决方案
一个可行的作法是释放全部锁,回退,而且等待一段随机的时间后重试。这个和简单的加锁超时相似,不同的是只有死锁已经发生了才回退,而不会是由于加锁的请求超时了。虽然有回退和等待,可是若是有大量的线程竞争同一批锁,它们仍是会重复地死锁(编者注:缘由同超时相似,不能从根本上减轻竞争)。
一个更好的方案是给这些线程设置优先级,让一个(或几个)线程回退,剩下的线程就像没发生死锁同样继续保持着它们须要的锁。若是赋予这些线程的优先级是固定不变的,同一批线程老是会拥有更高的优先级。为避免这个问题,能够在死锁发生的时候设置随机的优先级。
1. 使用本地变量
尽可能使用本地变量,而不是建立一个类或实例的变量。
class concurrentTask { private static List temp = new ArrayList<>(); public void execute(Message message) { // 使用本地变量保证线程安全 // List temp = new ArrayList<>(); temp.add(message.getId()); temp.add(message.getCode()); // ...省略各类业务逻辑 temp.clear(); } }
2. 使用不可变类
不可变类好比String 、Integer等一旦建立,再也不改变,不可变类能够下降代码中须要的同步数量。
3. 最小化锁的做用域范围
"阿姆达尔定律",又称"安达尔定理": S=1/(1-a+a/n)
a:并行计算部分所占比例
n:并行处理结点个数
S:加速比
当1-a等于0时,没有串行只有并行,最大加速比 S=n
当a=0时,只有串行没有并行,最小加速比 S = 1
当n→∞时,极限加速比 s→ 1/(1-a)
例如,若串行代码占整个代码的25%,则并行处理的整体性能不可能超过4。
4. 使用线程池,而不是直接使用new Thread执行
避免new Thread建立线程。经过线程池的管理,可提升线程的复用性(避免新建线程的昂贵的资源消耗),简化线程生命周期的管理。JDK提供了各类ThreadPool线程池和Executor。
5. 宁肯使用同步工具类也不要使用线程的wait和notify
同步工具类包括:countdownlaunch/Semaphore/Semaphore。应当优先使用这些同步工具,而不是去思考如何使用线程的wait和notify。此外,使用BlockingQueue实现生产消费的设计比使用wait和notify要好。
6. 使用blockingqueue实现生产消费模式
阻塞队列是生产者-消费者模式的最好的实现方式,不只包括单个生产者单个消费者,还支持多个生产者多个消费者状况。
7. 使用并发集合而不是加了锁的同步集合
JDK提供了ConcurrentHashMap、CopyOnWriteArrayList、CopyOnWriteArraySet、BlockingQueue中的Deque和BlockingDeque五大并发集合,他们有着较好性能;尽可能使用该并发集合,而避免使用synchronizedXXX的锁同步集合。
8. 使用semaphore建立有界的访问
为了创建稳定可靠的系统,对于数据库、文件系统和socket等资源必需要作有界的访问,Semaphone能够限制这些资源开销的选择,Semaphone能够以最低的代价阻塞线程等待,能够经过Semaphone来控制同时访问指定资源的线程数。
9. 宁肯使用同步代码块,也不使用同步的方法
主要针对synchronized关键字。使用synchronized关键字同步代码块只会锁定一个对象,而不会将整个方法锁定(当类不是单例的时候)。若是更改共同的变量或类的字段,首先应该选择的是原子型变量,而后使用volatile。若是须要互斥锁,能够考虑使用ReentrantLock。
10. 避免使用静态变量
静态变量在多线程并发环境中会形成较多的问题。当使用静态变量时,优先将其指定为final变量,若用其来保存集合Collection变量,则考虑使用只读集合。详见上文的不可变对象,同步容器和并发容器。
咱们老是想得太多,作的太少!