共计 3258 个字符,预计需要花费 9 分钟才能阅读完成。
共计 3258 个字符,预计需要花费 9 分钟才能阅读完成。
为何要应用 Java 线程同步?Java 容许多线程并发管制,当多个线程同时操作一个可共享的资源变量时,将会导致数据不精确,相互之间产生抵触,因而退出同步锁以防止在该线程没有实现操作之前,被其余线程的调用,从而保障了该变量的唯一性和准确性。
但其并发编程的基本,就是使线程间进行正确的通信。其中两个比拟重要的关键点,如下:
Java 中提供了很多线程同步操作,比方:synchronized 关键字、waitnotifyAll、ReentrantLock、Condition、一些并发包下的工具类、
自 JDK5 开始,新增了 Lock 接口以及它的一个实现类 ReentrantLock。ReentrantLock 可重入锁是 J.U.C 包内置的一个锁对象,能够用来实现同步,根本应用办法如下:
下面例子示意 同一时间段只能有 1 个线程执行 execute 办法,输入如下:
可重入锁中可重入示意的意义在于 对于同一个线程,能够持续调用加锁的办法,而不会被挂起。可重入锁外部保护一个计数器,对于同一个线程调用 lock 办法,计数器 +1,调用 unlock 办法,计数器 -1。
举个例子再次阐明一下可重入的意思:在一个加锁办法 execute 中调用另外一个加锁办法 anotherLock 并不会被挂起,能够间接调用(调用 execute 办法时计数器 +1,而后外部又调用了 anotherLock 办法,计数器 +1,变成了 2):
输入:
synchronized 跟 ReentrantLock 一样,也反对可重入锁 。然而它是 一个关键字,是一种语法级别的同步形式,称为内置锁:
输入后果跟 ReentrantLock 一样,这个例子阐明内置锁能够作用在办法上。synchronized 关键字也能够润饰静态方法,此时如果调用该静态方法,将会锁住整个类。
同步是一种高开销的操作,因而应该尽量减少同步的内容。通常没有必要同步整个办法,应用 synchronized 代码块同步要害代码即可。
synchronized 跟 ReentrantLock 相比,有几点局限性:
所以,Lock 的操作与 synchronized 相比,灵活性更高,而且 Lock 提供多种形式获取锁,有 Lock、ReadWriteLock 接口,以及实现这两个接口的 ReentrantLock 类、ReentrantReadWriteLock 类。
对于 Lock 对象和 synchronized 关键字抉择的考量:
在性能考量上来说,如果竞争资源不强烈,两者的性能是差不多的,而当竞争资源十分强烈时(即有大量线程同时竞争),此时 Lock 的性能要远远优于 synchronized。所以说,在具体应用时要依据适当状况抉择。
Condition 条件对象的意义在于 对于一个曾经获取 Lock 锁的线程,如果还须要期待其余条件能力继续执行的状况下,才会应用 Condition 条件对象。
Condition 能够代替传统的线程间通信 ,用 await() 替换 wait(),用 signal()替换 notify(),用 signalAll()替换 notifyAll()。
这个例子中 thread1 执行到 condition.await()时,以后线程会被挂起,直到 thread2 调用了 condition.signalAll()办法之后,thread1 才会从新被激活执行。
传统线程的通信形式,Condition 都能够实现。Condition 的弱小之处在于它能够为多个线程间建设不同的 Condition。
Java 线程的状态转换图与相干办法,如下:
线程状态转换图
在图中,红框标识的局部办法,能够认为已过期,不再应用。上图中的办法可能参加到线程同步中的办法,如下:
1.wait、notify、notifyAll 办法 :线程中通信能够应用的办法。 线程中调用了 wait 办法,则进入阻塞状态,只有等另一个线程调用与 wait 同一个对象的 notify 办法。这里有个非凡的中央,调用 wait 或者 notify,前提是须要获取锁,也就是说,须要在同步块中做以上操作。
这里须要留神的是 调用 waitnotifyAll 办法的时候肯定要取得以后线程的锁,否则会产生 IllegalMonitorStateException 异样。
2.join 办法:该办法次要作用是在该线程中的 run 办法完结后,才往下执行。
3.yield 办法:线程自身的调度办法,应用时线程能够在 run 办法执行结束时,调用该办法,告知线程已能够出让 CPU 资源。
4.sleep 办法 :通过 sleep(millis) 使线程进入休眠一段时间,该办法在指定的工夫内无奈被唤醒,同时也不会开释对象锁;
sleep 办法通知操作系统 至多在指定工夫内不需为线程调度器为该线程调配执行工夫片,并不开释锁(如果以后曾经持有锁)。实际上,调用 sleep 办法时并不要求持有任何锁。
所以,sleep 办法并不需要持有任何模式的锁,也就不须要包裹在 synchronized 中。
ThreadLocal 是一种把变量放到线程本地的形式来实现线程同步的。比方:SimpleDateFormat 不是一个线程平安的类,能够应用 ThreadLocal 实现同步,如下:
ThreadLocal 与同步机制的比照抉择:
volatile 关键字为域变量的拜访提供了一种免锁机制,应用 volatile 润饰域相当于通知虚拟机该域可能会被其余线程更新,因而每次应用该域就要从新计算,而不是应用寄存器中的值,volatile 不会提供任何原子操作,它也不能用来润饰 final 类型的变量。
多线程中的非同步问题次要呈现在对域的读写上 ,如果让域本身防止这个问题,则就不须要批改操作该域的办法。 用 final 域,有锁爱护的域和 volatile 域能够防止非同步的问题。
Semaphore 信号量被用于管制特定资源在同一个工夫被拜访的个数。相似连接池的概念,保障资源能够被正当的应用。能够应用结构器初始化资源个数:
输入:
CountDownLatch 是一个计数器,它的构造方法中须要设置一个数值,用来设定计数的次数 。每次调用 countDown() 办法之后,这个计数器都会减去 1,CountDownLatch 会始终阻塞着调用 await()办法的线程,直到计数器的值变为 0 。
输入:
CyclicBarrier 阻塞调用的线程,直到条件满足时,阻塞的线程同时被关上。
相比 CountDownLatch,CyclicBarrier 是能够被循环应用的,而且遇到线程中断等状况时,还能够利用 reset()办法,重置计数器,从这些方面来说,CyclicBarrier 会比 CountDownLatch 更加灵便一些。
有时须要应用线程同步的根本原因在于 对一般变量的操作不是原子的。那么什么是原子操作呢?
在 java.util.concurrent.atomic 包中提供了创立原子类型变量的工具类,应用该类能够简化线程同步。比方:其中 AtomicInteger 以原子形式更新 int 的值:
AQS 是很多同步工具类的根底,比方:ReentrantLock 里的偏心锁和非偏心锁,Semaphore 里的偏心锁和非偏心锁,CountDownLatch 里的锁等他们的底层都是应用 AbstractQueuedSynchronizer 实现的。
基于 AbstractQueuedSynchronizer 自定义实现一个独占锁:
后面几种同步形式都是基于底层实现的线程同步,然而在理论开发当中,该当尽量远离底层构造。本节次要是应用 LinkedBlockingQueue<E> 来实现线程的同步。
0 人点赞
日记本