JUC
详情参考:https://www.bilibili.com/video/BV1Kw411Z7dF
一、JUC概述
1. 关于JUC
JUC即位于java.util.concurrent
包下用于并发编程的一些工具包。
2. 进程和线程
进程
是系统进行资源分配和调度的基本单位,是操作系统的基础。
是程序动态的执行过程,是有生命周期的。
线程
是操作系统能够进行运算调度的最小单位。同一个进程中,多个线程可以同时执行。
3. 线程的状态
/**
* Thread state for a thread which has not yet started.
*/
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
4. wait与sleep区别
- sleep是Thread的静态方法,wait是Object的方法,任何对象都可以调用。
- sleep不会释放锁,wait会释放锁。
- 它们都可以被interrupted方法中断。
5. 并发和并行
1). 串行模式
按照先后顺序,依次执行。串行是一次只能取得一个任务,并执行这个任务。
2). 并行模式
并行可以同时获取多个任务,并同时执行这些任务。
并发
多个程序可以"同时"运行的现象。
6. 管程
(Monitor)是一种同步机制,保证同一时间,只有一个线程访问资源。
jvm同步的进入和退出,是使用管程对象实现的。
7. 用户线程和守护线程
用户线程:自定义线程基本上都是用户线程。
守护线程:运行在后台,依赖于用户线程。
主线程结束,用户线程还在,jvm存活。没有用户线程,都是守护线程,jvm结束。
二、Lock接口
1. synchronized关键字
-
修饰代码块
同步代码块。作用范围{}之间,作用的对象是调用这个代码块的对象
-
修饰方法
同步方法。作用范围整个方法,作用的对象是调用这个方法的对象。
synchronized方法被继承后,不具有synchronized属性,需手动添加。
-
修饰静态方法
作用于整个静态方法,作用对象是这个类的所有对象。
-
修饰类
作用于整个类,作用对象是这个类的所有对象。
售票实例:
3个窗口最多售卖30张票
public class SynchronizedDemo {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(() -> {
for(int i = 0; i < 40; i ++){
ticket.sale();
}
},"A").start();
new Thread(() -> {
for(int i = 0; i < 40; i ++){
ticket.sale();
}
},"B").start();
new Thread(() -> {
for(int i = 0; i < 40; i ++){
ticket.sale();
}
},"C").start();
}
}
class Ticket{
private int count = 30;
public synchronized void sale(){
if (count > 0){
System.out.println(Thread.currentThread().getName() + "卖票:" + (count --));
System.out.println("剩余票数:" + count);
}
}
}
2. 多线程编程步骤
1). 编写资源类,并创建属性和方法。
2). 在资源类操作方法
- 判断
- 干活
- 通知
3). 编写多个线程,调用资源类的方法。
4). 防止虚假唤醒问题
3. lock
lock也可以实现锁的功能,比synchronized关键字的功能更多。
与synchronized的区别:
- Lock是一个接口,synchronized是一个关键字。
- synchronized发生异常时,会自动释放锁,不会导致死锁;而lock发生异常时,没有在finally中释放锁,则可能发生死锁。
- Lock可以让等待锁的线程响应中断,而synchronized不可以,使用synchronized时,等待的线程会一直等待下去,不能响应中断。
- 通过Lock可以知道获取锁是否成功,synchronized不可以。
- Lock可以提高多个线程进行读操作的效率。
通过lock实现售票问题
public class LockDemo {
public static void main(String[] args) {
LTicket ticket = new LTicket();
new Thread(() -> {
for (int i = 0; i < 40; i ++){
ticket.sale();
}
},"A").start();
new Thread(() -> {
for (int i = 0; i < 40; i ++){
ticket.sale();
}
},"B").start();
new Thread(() -> {
for (int i = 0; i < 40; i ++){
ticket.sale();
}
},"C").start();
}
}
class LTicket{
private int count = 30;
//创建可重入锁
private final ReentrantLock lock = new ReentrantLock();
public void sale(){
//上锁
lock.lock();
try{
if (count > 0)
System.out.println(Thread.currentThread().getName() + "售票:" + (count --) + ",剩余:" + count);
}finally {
//解锁
lock.unlock();
}
}
}
4. Condition
Lock代替synchronized,Condition用来代替Object监视器的相关方法(如等待,唤醒等)。
该对象通过Lock.newCondition()获取。
package java.util.concurrent.locks;
import java.util.concurrent.TimeUnit;
import java.util.Date;
/**
* {@code Condition} factors out the {@code Object} monitor
* methods ({@link Object#wait() wait}, {@link Object#notify notify}
* and {@link Object#notifyAll notifyAll}) into distinct objects to
* give the effect of having multiple wait-sets per object, by
* combining them with the use of arbitrary {@link Lock} implementations.
* Where a {@code Lock} replaces the use of {@code synchronized} methods
* and statements, a {@code Condition} replaces the use of the Object
* monitor methods.
*
* <p>Conditions (also known as <em>condition queues</em> or
* <em>condition variables</em>) provide a means for one thread to
* suspend execution (to "wait") until notified by another
* thread that some state condition may now be true. Because access
* to this shared state information occurs in different threads, it
* must be protected, so a lock of some form is associated with the
* condition. The key property that waiting for a condition provides
* is that it <em>atomically</em> releases the associated lock and
* suspends the current thread, just like {@code Object.wait}.
*
* <p>A {@code Condition} instance is intrinsically bound to a lock.
* To obtain a {@code Condition} instance for a particular {@link Lock}
* instance use its {@link Lock#newCondition newCondition()} method.
*
* <p>As an example, suppose we have a bounded buffer which supports
* {@code put} and {@code take} methods. If a
* {@code take} is attempted on an empty buffer, then the thread will block
* until an item becomes available; if a {@code put} is attempted on a
* full buffer, then the thread will block until a space becomes available.
* We would like to keep waiting {@code put} threads and {@code take}
* threads in separate wait-sets so that we can use the optimization of
* only notifying a single thread at a time when items or spaces become
* available in the buffer. This can be achieved using two
* {@link Condition} instances.
* <pre>
* class BoundedBuffer {
* <b>final Lock lock = new ReentrantLock();</b>
* final Condition notFull = <b>lock.newCondition(); </b>
* final Condition notEmpty = <b>lock.newCondition(); </b>
*
* final Object[] items = new Object[100];
* int putptr, takeptr, count;
*
* public void put(Object x) throws InterruptedException {
* <b>lock.lock();
* try {</b>
* while (count == items.length)
* <b>notFull.await();</b>
* items[putptr] = x;
* if (++putptr == items.length) putptr = 0;
* ++count;
* <b>notEmpty.signal();</b>
* <b>} finally {
* lock.unlock();
* }</b>
* }
*
* public Object take() throws InterruptedException {
* <b>lock.lock();
* try {</b>
* while (count == 0)
* <b>notEmpty.await();</b>
* Object x = items[takeptr];
* if (++takeptr == items.length) takeptr = 0;
* --count;
* <b>notFull.signal();</b>
* return x;
* <b>} finally {
* lock.unlock();
* }</b>
* }
* }
* </pre>
*
* (The {@link java.util.concurrent.ArrayBlockingQueue} class provides
* this functionality, so there is no reason to implement this
* sample usage class.)
*
* <p>A {@code Condition} implementation can provide behavior and semantics
* that is
* different from that of the {@code Object} monitor methods, such as
* guaranteed ordering for notifications, or not requiring a lock to be held
* when performing notifications.
* If an implementation provides such specialized semantics then the
* implementation must document those semantics.
*
* <p>Note that {@code Condition} instances are just normal objects and can
* themselves be used as the target in a {@code synchronized} statement,
* and can have their own monitor {@link Object#wait wait} and
* {@link Object#notify notification} methods invoked.
* Acquiring the monitor lock of a {@code Condition} instance, or using its
* monitor methods, has no specified relationship with acquiring the
* {@link Lock} associated with that {@code Condition} or the use of its
* {@linkplain #await waiting} and {@linkplain #signal signalling} methods.
* It is recommended that to avoid confusion you never use {@code Condition}
* instances in this way, except perhaps within their own implementation.
*
* <p>Except where noted, passing a {@code null} value for any parameter
* will result in a {@link NullPointerException} being thrown.
*
* <h3>Implementation Considerations</h3>
*
* <p>When waiting upon a {@code Condition}, a "<em>spurious
* wakeup</em>" is permitted to occur, in
* general, as a concession to the underlying platform semantics.
* This has little practical impact on most application programs as a
* {@code Condition} should always be waited upon in a loop, testing
* the state predicate that is being waited for. An implementation is
* free to remove the possibility of spurious wakeups but it is
* recommended that applications programmers always assume that they can
* occur and so always wait in a loop.
*
* <p>The three forms of condition waiting
* (interruptible, non-interruptible, and timed) may differ in their ease of
* implementation on some platforms and in their performance characteristics.
* In particular, it may be difficult to provide these features and maintain
* specific semantics such as ordering guarantees.
* Further, the ability to interrupt the actual suspension of the thread may
* not always be feasible to implement on all platforms.
*
* <p>Consequently, an implementation is not required to define exactly the
* same guarantees or semantics for all three forms of waiting, nor is it
* required to support interruption of the actual suspension of the thread.
*
* <p>An implementation is required to
* clearly document the semantics and guarantees provided by each of the
* waiting methods, and when an implementation does support interruption of
* thread suspension then it must obey the interruption semantics as defined
* in this interface.
*
* <p>As interruption generally implies cancellation, and checks for
* interruption are often infrequent, an implementation can favor responding
* to an interrupt over normal method return. This is true even if it can be
* shown that the interrupt occurred after another action that may have
* unblocked the thread. An implementation should document this behavior.
*
* @since 1.5
* @author Doug Lea
*/
public interface Condition {
/**
* Causes the current thread to wait until it is signalled or
* {@linkplain Thread#interrupt interrupted}.
*
* <p>The lock associated with this {@code Condition} is atomically
* released and the current thread becomes disabled for thread scheduling
* purposes and lies dormant until <em>one</em> of four things happens:
* <ul>
* <li>Some other thread invokes the {@link #signal} method for this
* {@code Condition} and the current thread happens to be chosen as the
* thread to be awakened; or
* <li>Some other thread invokes the {@link #signalAll} method for this
* {@code Condition}; or
* <li>Some other thread {@linkplain Thread#interrupt interrupts} the
* current thread, and interruption of thread suspension is supported; or
* <li>A "<em>spurious wakeup</em>" occurs.
* </ul>
*
* <p>In all cases, before this method can return the current thread must
* re-acquire the lock associated with this condition. When the
* thread returns it is <em>guaranteed</em> to hold this lock.
*
* <p>If the current thread:
* <ul>
* <li>has its interrupted status set on entry to this method; or
* <li>is {@linkplain Thread#interrupt interrupted} while waiting
* and interruption of thread suspension is supported,
* </ul>
* then {@link InterruptedException} is thrown and the current thread's
* interrupted status is cleared. It is not specified, in the first
* case, whether or not the test for interruption occurs before the lock
* is released.
*
* <p><b>Implementation Considerations</b>
*
* <p>The current thread is assumed to hold the lock associated with this
* {@code Condition} when this method is called.
* It is up to the implementation to determine if this is
* the case and if not, how to respond. Typically, an exception will be
* thrown (such as {@link IllegalMonitorStateException}) and the
* implementation must document that fact.
*
* <p>An implementation can favor responding to an interrupt over normal
* method return in response to a signal. In that case the implementation
* must ensure that the signal is redirected to another waiting thread, if
* there is one.
*
* @throws InterruptedException if the current thread is interrupted
* (and interruption of thread suspension is supported)
*/
void await() throws InterruptedException;
/**
* Causes the current thread to wait until it is signalled.
*
* <p>The lock associated with this condition is atomically
* released and the current thread becomes disabled for thread scheduling
* purposes and lies dormant until <em>one</em> of three things happens:
* <ul>
* <li>Some other thread invokes the {@link #signal} method for this
* {@code Condition} and the current thread happens to be chosen as the
* thread to be awakened; or
* <li>Some other thread invokes the {@link #signalAll} method for this
* {@code Condition}; or
* <li>A "<em>spurious wakeup</em>" occurs.
* </ul>
*
* <p>In all cases, before this method can return the current thread must
* re-acquire the lock associated with this condition. When the
* thread returns it is <em>guaranteed</em> to hold this lock.
*
* <p>If the current thread's interrupted status is set when it enters
* this method, or it is {@linkplain Thread#interrupt interrupted}
* while waiting, it will continue to wait until signalled. When it finally
* returns from this method its interrupted status will still
* be set.
*
* <p><b>Implementation Considerations</b>
*
* <p>The current thread is assumed to hold the lock associated with this
* {@code Condition} when this method is called.
* It is up to the implementation to determine if this is
* the case and if not, how to respond. Typically, an exception will be
* thrown (such as {@link IllegalMonitorStateException}) and the
* implementation must document that fact.
*/
void awaitUninterruptibly();
/**
* Causes the current thread to wait until it is signalled or interrupted,
* or the specified waiting time elapses.
*
* <p>The lock associated with this condition is atomically
* released and the current thread becomes disabled for thread scheduling
* purposes and lies dormant until <em>one</em> of five things happens:
* <ul>
* <li>Some other thread invokes the {@link #signal} method for this
* {@code Condition} and the current thread happens to be chosen as the
* thread to be awakened; or
* <li>Some other thread invokes the {@link #signalAll} method for this
* {@code Condition}; or
* <li>Some other thread {@linkplain Thread#interrupt interrupts} the
* current thread, and interruption of thread suspension is supported; or
* <li>The specified waiting time elapses; or
* <li>A "<em>spurious wakeup</em>" occurs.
* </ul>
*
* <p>In all cases, before this method can return the current thread must
* re-acquire the lock associated with this condition. When the
* thread returns it is <em>guaranteed</em> to hold this lock.
*
* <p>If the current thread:
* <ul>
* <li>has its interrupted status set on entry to this method; or
* <li>is {@linkplain Thread#interrupt interrupted} while waiting
* and interruption of thread suspension is supported,
* </ul>
* then {@link InterruptedException} is thrown and the current thread's
* interrupted status is cleared. It is not specified, in the first
* case, whether or not the test for interruption occurs before the lock
* is released.
*
* <p>The method returns an estimate of the number of nanoseconds
* remaining to wait given the supplied {@code nanosTimeout}
* value upon return, or a value less than or equal to zero if it
* timed out. This value can be used to determine whether and how
* long to re-wait in cases where the wait returns but an awaited
* condition still does not hold. Typical uses of this method take
* the following form:
*
* <pre> {@code
* boolean aMethod(long timeout, TimeUnit unit) {
* long nanos = unit.toNanos(timeout);
* lock.lock();
* try {
* while (!conditionBeingWaitedFor()) {
* if (nanos <= 0L)
* return false;
* nanos = theCondition.awaitNanos(nanos);
* }
* // ...
* } finally {
* lock.unlock();
* }
* }}</pre>
*
* <p>Design note: This method requires a nanosecond argument so
* as to avoid truncation errors in reporting remaining times.
* Such precision loss would make it difficult for programmers to
* ensure that total waiting times are not systematically shorter
* than specified when re-waits occur.
*
* <p><b>Implementation Considerations</b>
*
* <p>The current thread is assumed to hold the lock associated with this
* {@code Condition} when this method is called.
* It is up to the implementation to determine if this is
* the case and if not, how to respond. Typically, an exception will be
* thrown (such as {@link IllegalMonitorStateException}) and the
* implementation must document that fact.
*
* <p>An implementation can favor responding to an interrupt over normal
* method return in response to a signal, or over indicating the elapse
* of the specified waiting time. In either case the implementation
* must ensure that the signal is redirected to another waiting thread, if
* there is one.
*
* @param nanosTimeout the maximum time to wait, in nanoseconds
* @return an estimate of the {@code nanosTimeout} value minus
* the time spent waiting upon return from this method.
* A positive value may be used as the argument to a
* subsequent call to this method to finish waiting out
* the desired time. A value less than or equal to zero
* indicates that no time remains.
* @throws InterruptedException if the current thread is interrupted
* (and interruption of thread suspension is supported)
*/
long awaitNanos(long nanosTimeout) throws InterruptedException;
/**
* Causes the current thread to wait until it is signalled or interrupted,
* or the specified waiting time elapses. This method is behaviorally
* equivalent to:
* <pre> {@code awaitNanos(unit.toNanos(time)) > 0}</pre>
*
* @param time the maximum time to wait
* @param unit the time unit of the {@code time} argument
* @return {@code false} if the waiting time detectably elapsed
* before return from the method, else {@code true}
* @throws InterruptedException if the current thread is interrupted
* (and interruption of thread suspension is supported)
*/
boolean await(long time, TimeUnit unit) throws InterruptedException;
/**
* Causes the current thread to wait until it is signalled or interrupted,
* or the specified deadline elapses.
*
* <p>The lock associated with this condition is atomically
* released and the current thread becomes disabled for thread scheduling
* purposes and lies dormant until <em>one</em> of five things happens:
* <ul>
* <li>Some other thread invokes the {@link #signal} method for this
* {@code Condition} and the current thread happens to be chosen as the
* thread to be awakened; or
* <li>Some other thread invokes the {@link #signalAll} method for this
* {@code Condition}; or
* <li>Some other thread {@linkplain Thread#interrupt interrupts} the
* current thread, and interruption of thread suspension is supported; or
* <li>The specified deadline elapses; or
* <li>A "<em>spurious wakeup</em>" occurs.
* </ul>
*
* <p>In all cases, before this method can return the current thread must
* re-acquire the lock associated with this condition. When the
* thread returns it is <em>guaranteed</em> to hold this lock.
*
*
* <p>If the current thread:
* <ul>
* <li>has its interrupted status set on entry to this method; or
* <li>is {@linkplain Thread#interrupt interrupted} while waiting
* and interruption of thread suspension is supported,
* </ul>
* then {@link InterruptedException} is thrown and the current thread's
* interrupted status is cleared. It is not specified, in the first
* case, whether or not the test for interruption occurs before the lock
* is released.
*
*
* <p>The return value indicates whether the deadline has elapsed,
* which can be used as follows:
* <pre> {@code
* boolean aMethod(Date deadline) {
* boolean stillWaiting = true;
* lock.lock();
* try {
* while (!conditionBeingWaitedFor()) {
* if (!stillWaiting)
* return false;
* stillWaiting = theCondition.awaitUntil(deadline);
* }
* // ...
* } finally {
* lock.unlock();
* }
* }}</pre>
*
* <p><b>Implementation Considerations</b>
*
* <p>The current thread is assumed to hold the lock associated with this
* {@code Condition} when this method is called.
* It is up to the implementation to determine if this is
* the case and if not, how to respond. Typically, an exception will be
* thrown (such as {@link IllegalMonitorStateException}) and the
* implementation must document that fact.
*
* <p>An implementation can favor responding to an interrupt over normal
* method return in response to a signal, or over indicating the passing
* of the specified deadline. In either case the implementation
* must ensure that the signal is redirected to another waiting thread, if
* there is one.
*
* @param deadline the absolute time to wait until
* @return {@code false} if the deadline has elapsed upon return, else
* {@code true}
* @throws InterruptedException if the current thread is interrupted
* (and interruption of thread suspension is supported)
*/
boolean awaitUntil(Date deadline) throws InterruptedException;
/**
* Wakes up one waiting thread.
*
* <p>If any threads are waiting on this condition then one
* is selected for waking up. That thread must then re-acquire the
* lock before returning from {@code await}.
*
* <p><b>Implementation Considerations</b>
*
* <p>An implementation may (and typically does) require that the
* current thread hold the lock associated with this {@code
* Condition} when this method is called. Implementations must
* document this precondition and any actions taken if the lock is
* not held. Typically, an exception such as {@link
* IllegalMonitorStateException} will be thrown.
*/
void signal();
/**
* Wakes up all waiting threads.
*
* <p>If any threads are waiting on this condition then they are
* all woken up. Each thread must re-acquire the lock before it can
* return from {@code await}.
*
* <p><b>Implementation Considerations</b>
*
* <p>An implementation may (and typically does) require that the
* current thread hold the lock associated with this {@code
* Condition} when this method is called. Implementations must
* document this precondition and any actions taken if the lock is
* not held. Typically, an exception such as {@link
* IllegalMonitorStateException} will be thrown.
*/
void signalAll();
}
三、线程间的通信
案例:
有两个线程,对初始值为0的变量进行操作。一个线程对变量+1,一个线程对变量-1
public class Communicate {
public static void main(String[] args) {
Share share = new Share();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
share.increase();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "A").start();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
share.deincrease();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
class Share {
private int number = 0;
//+1方法
public synchronized void increase() throws InterruptedException {
if (number != 0) {
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName() + ":" + number);
this.notifyAll();
}
//-1方法
public synchronized void deincrease() throws InterruptedException {
if (number != 1) {
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName() + ":" + number);
this.notifyAll();
}
}
在上述代码中再加入两个线程,一个线程+1,另一个则-1。再次运行程序,会发现除了1和0之外,还有其他值,这种情况被称为虚假唤醒。
wait()特点:在哪里睡,就在哪里醒,醒之后不会再进行if的条件判断,就会出现异常情况。
有4个线程:A,B,C,D。A +1,B -1,C +1,D -1。设初始值为0,若A线程先抢到锁,则对值+1。释放锁,若C抢到锁,发现值为1,执行wait(),唤醒其他线程。若A在此抢到锁,则发现值为1,执行wait(),并唤醒其他线程。若C再次抢到线程,从wait()处被唤醒,继续执行后续的number ++,就会产生虚假唤醒的情况。我们可以通过将判断条件放在while循环中,即使从wait()处被唤醒,也会再次进行判断。
As in the one argument version, interrupts and spurious wakeups are possible, and this method should always be used in a loop:
synchronized (obj) {
while (<condition does not hold>)
obj.wait();
... // Perform action appropriate to condition
}
通过lock方式实现上述案例
public class CommunicateByLock {
public static void main(String[] args) {
LShare lShare = new LShare();
new Thread(()-> {
for (int i = 1; i <= 10; i ++){
try {
lShare.incr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"AA").start();
new Thread(() -> {
for(int i = 1; i <= 10; i ++){
try {
lShare.decr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"BB").start();
new Thread(()-> {
for (int i = 1; i <= 10; i ++){
try {
lShare.incr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"CC").start();
new Thread(() -> {
for(int i = 1; i <= 10; i ++){
try {
lShare.decr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"DD").start();
}
}
class LShare {
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
private int number = 0;
public void incr() throws InterruptedException {
//加锁
lock.lock();
try {
while (number != 0){
condition.await();//线程wait
}
number ++;
System.out.println(Thread.currentThread().getName() + ":" + number);
condition.signalAll();//唤醒其他线程
} finally {
//解锁
lock.unlock();
}
}
public void decr() throws InterruptedException {
lock.lock();
try {
while (number != 1){
condition.await();
}
number --;
System.out.println(Thread.currentThread().getName() + ":" + number);
condition.signalAll();
} finally {
lock.unlock();
}
}
}
四、线程间的定制化通信
案例:
有3个线程,AA,BB,CC。首先AA打印2次,然后BB打印3次,然后CC打印4次,然后循环上述顺序10次。
package cn.nl.juc.custom_communication;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 有3个线程,AA,BB,CC。首先AA打印2次,然后BB打印3次,然后CC打印4次,然后循环上述顺序10次。
*/
public class CusCommunication {
public static void main(String[] args) {
Resource resource = new Resource();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
resource.print2(i + 1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "AA").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
resource.print3(i + 1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "BB").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
resource.print4(i+1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "CC").start();
}
}
class Resource {
//定义标志位 AA 1 BB 2 CC 3
private int flag = 1;
//重入锁
private Lock lock = new ReentrantLock();
//创建三个condition
private Condition ca = lock.newCondition();
private Condition cb = lock.newCondition();
private Condition cc = lock.newCondition();
/**
* 打印2次
*
* @param count 第几次打印
*/
public void print2(int count) throws InterruptedException {
lock.lock();
try {
while (flag != 1) {
ca.await();
}
for (int i = 0; i < 2; i++) {
System.out.println(Thread.currentThread().getName() + ",第" + count + "轮次打印,打印值:" + i);
}
flag = 2;//修改标志位
cb.signal();//唤醒BB线程
} finally {
lock.unlock();
}
}
public void print3(int count) throws InterruptedException {
lock.lock();
try {
while (flag != 2) {
cb.await();
}
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + ",第" + count + "轮次打印,打印值:" + i);
}
flag = 3;
cc.signal();
} finally {
lock.unlock();
}
}
public void print4(int count) throws InterruptedException {
lock.lock();
try {
while (flag != 3) {
cc.await();
}
for (int i = 0; i < 4; i++) {
System.out.println(Thread.currentThread().getName() + ",第" + count + "轮次打印,打印值:" + i);
}
flag = 1;
ca.signal();
} finally {
lock.unlock();
}
}
}
五、集合的线程安全
list线程不安全
1. list线程不安全
List<String> list = new ArrayList<>();
for(int i = 0; i < 90; i ++){
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0,4));
System.out.println("线程" + Thread.currentThread().getName() + ",集合:" + list);
},Integer.toString(i)).start();
}
会出现java.util.ConcurrentModificationException
异常,原因是由于add()没有加synchronized关键字。
2. Vector
通过Vector集合解决上述问题
Vector<String> collection = new Vector<>();
for (int i = 0; i < 60; i++) {
new Thread(() ->{
collection.add(UUID.randomUUID().toString().substring(0,3));
System.out.println("线程:" + Thread.currentThread().getName() + ",集合:" + collection);
},Integer.toString(i)).start();
}
3. Collections
List<String> list = new ArrayList<>();
List<String> collection = Collections.synchronizedList(list);
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
collection.add(UUID.randomUUID().toString().substring(0,4));
}).start();
}
4. CopyOnWriteArrayList
java.util.concurrent.CopyOnWriteArrayList
Collection collection = new CopyOnWriteArrayList<String>();
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
collection.add(UUID.randomUUID().toString().substring(0,4));
}).start();
}
写时复制技术
写之前,复制一份数据,然后往赋值的内容中写入新的数据,最终合并复制数据和原始数据。
HashSet和HashMap线程不安全
HashSet
Set<String> set = new HashSet<>();
for(int i = 0; i < 10000; i ++){
new Thread(() -> {
set.add(UUID.randomUUID().toString().substring(0,4));
System.out.println(Thread.currentThread().getName() + ",集合:" + set);
}).start();
}
同样也会发生java.util.ConcurrentModificationException
的问题。
解决方式
使用CopyOnWriteArraySet
.
Set<String> set = new CopyOnWriteArraySet<>();//将上述中set创建改为CopyOnWriteArraySet
HashMap
Map<String, String> map = new HashMap<>();
for (int i = 0; i < 10000; i++) {
String key = String.valueOf(i);
new Thread(() -> {
map.put(key, UUID.randomUUID().toString().substring(0, 4));
System.out.println(Thread.currentThread().getName() + ",map:" + map);
}).start();
}
同样的问题:java.util.ConcurrentModificationException
解决方式
Map<String, String> map = new ConcurrentHashMap<>();
六、多线程锁
1. 不同锁的范围
案例:
class Phone {
public synchronized void sendSMS() throws Exception {
System.out.println("---sendSMS---");
}
public synchronized void sendEmail() throws Exception {
System.out.println("---sendEmail---");
}
public void getHello() throws Exception {
System.out.println("---getHello---");
}
}
1.标准访问,先打印短信还是邮件。
Phone phone = new Phone();
new Thread(() -> {
try {
phone.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
},"A").start();
Thread.sleep(100);
new Thread(()->{
try {
phone.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
},"B").start();
//结果
---sendSMS---
---sendEmail---
2.停4秒在短信方法内,先打印短信还是邮件
class Phone {
public synchronized void sendSMS() throws Exception {
TimeUnit.SECONDS.sleep(4);
System.out.println("---sendSMS---");
}
public synchronized void sendEmail() throws Exception {
System.out.println("---sendEmail---");
}
public void getHello() throws Exception {
System.out.println("---getHello---");
}
}
public class EightLock {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(() -> {
try {
phone.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
},"A").start();
Thread.sleep(100);
new Thread(()->{
try {
phone.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
},"B").start();
}
}
//结果
---sendSMS---
---sendEmail---
同步方法,锁的是当前对象也,就是this。在上述例子中,this就是phone,当一个同步方法操作时,另一个方法就只能等待。
3.新增普通的hello方法,先打印短信还是hello
class Phone {
public synchronized void sendSMS() throws Exception {
TimeUnit.SECONDS.sleep(4);
System.out.println("---sendSMS---");
}
public synchronized void sendEmail() throws Exception {
System.out.println("---sendEmail---");
}
public void getHello() throws Exception {
System.out.println("---getHello---");
}
}
public class EightLock {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(() -> {
try {
phone.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
},"A").start();
Thread.sleep(100);
new Thread(()->{
try {
// phone.sendEmail();
phone.getHello();
} catch (Exception e) {
e.printStackTrace();
}
},"B").start();
}
}
//结果
---getHello---
---sendSMS---`
普通方法没有加锁,而同步方法又等待了4秒,所以普通方法先输出
4.有两部手机,先打印短信还是邮件
class Phone {
public synchronized void sendSMS() throws Exception {
TimeUnit.SECONDS.sleep(4);
System.out.println("---sendSMS---");
}
public synchronized void sendEmail() throws Exception {
System.out.println("---sendEmail---");
}
public void getHello() throws Exception {
System.out.println("---getHello---");
}
}
public class EightLock {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
Phone phone1 = new Phone();
new Thread(() -> {
try {
phone.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
},"A").start();
Thread.sleep(100);
new Thread(()->{
try {
// phone.sendEmail();
// phone.getHello();
phone1.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
},"B").start();
}
}
//结果
---sendEmail---
---sendSMS---
两个手机即两把锁,两个同步方法执行时,不影响,但由于sendSMS() sleep4秒,所以sendEmail()先执行。
5.两个静态同步方法,1部手机,先打印短信还是邮件
class Phone {
public static synchronized void sendSMS() throws Exception {
TimeUnit.SECONDS.sleep(4);
System.out.println("---sendSMS---");
}
public static synchronized void sendEmail() throws Exception {
System.out.println("---sendEmail---");
}
public void getHello() throws Exception {
System.out.println("---getHello---");
}
}
public class EightLock {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
Phone phone1 = new Phone();
new Thread(() -> {
try {
phone.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
},"A").start();
Thread.sleep(100);
new Thread(()->{
try {
phone.sendEmail();
// phone.getHello();
// phone1.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
},"B").start();
}
}
//结果
---sendSMS---
---sendEmail---
6.两个静态同步方法,2部手机,先打印短信还是邮件
public class EightLock {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
Phone phone1 = new Phone();
new Thread(() -> {
try {
phone.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
},"A").start();
Thread.sleep(100);
new Thread(()->{
try {
// phone.sendEmail();
// phone.getHello();
phone1.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
},"B").start();
}
}
//结果
---sendSMS---
---sendEmail---
而当加入关键字static时,锁的对象是当前类的Class对象。
7.1个静态同步方法,1个普通同步方法,1部手机,先打印短信还是邮件
class Phone {
public static synchronized void sendSMS() throws Exception {
TimeUnit.SECONDS.sleep(4);
System.out.println("---sendSMS---");
}
public /*static*/ synchronized void sendEmail() throws Exception {
System.out.println("---sendEmail---");
}
public void getHello() throws Exception {
System.out.println("---getHello---");
}
}
public class EightLock {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
Phone phone1 = new Phone();
new Thread(() -> {
try {
phone.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
},"A").start();
Thread.sleep(100);
new Thread(()->{
try {
phone.sendEmail();
// phone.getHello();
// phone1.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
},"B").start();
}
}
//结果
---sendEmail---
---sendSMS---
8.1个静态同步方法,1个普通同步方法,2部手机,先打印短信还是邮件
class Phone {
public static synchronized void sendSMS() throws Exception {
TimeUnit.SECONDS.sleep(4);
System.out.println("---sendSMS---");
}
public /*static*/ synchronized void sendEmail() throws Exception {
System.out.println("---sendEmail---");
}
public void getHello() throws Exception {
System.out.println("---getHello---");
}
}
public class EightLock {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
Phone phone1 = new Phone();
new Thread(() -> {
try {
phone.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
},"A").start();
Thread.sleep(100);
new Thread(()->{
try {
// phone.sendEmail();
// phone.getHello();
phone1.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
},"B").start();
}
}
//结果
---sendEmail---
---sendSMS---
静态同步方法和普通同步方法锁的对象分别是当前类的Class对象和当前对象this.
synchronized实现同步,Java中的每一个对象都可以作为锁。
总结:
- 普通方法,锁的是当前实例对象
- 静态方法,锁的是当前类的Class对象
- 同步代码块,锁的是synchronized括号中的对象
2. 公平锁和非公平锁
在之前卖票案例中,创建ReentrantLock时,
若使用无参构造器,则创建非公平锁。
若使用有参构造器,参数boolean fair
为false时,为非公平锁;若参数为true时,为公平锁。
使用非公平锁时,我们发现一个线程执行了所有任务。使用公平锁时,每个线程都可以获得执行任务的机会。
private final ReentrantLock lock = new ReentrantLock(true);
非公平锁:线程“饿死”,效率高
公平锁:所有线程都能得到资源,效率相对较低
3. 可重入锁
synchronized关键字和Lock都是可重入锁。synchronized为隐式的可重入锁,Lock为显式的可重入锁。
可重入锁又被称为递归锁。
当一个线程获取对象锁之后,这个线程可以再次获取本对象上的锁,而其他的线程是不可以的。
synchronized
public static void main(String[] args) {
new RecursionLock().add();//StackOverflowError
Object o = new Object();
new Thread(() -> {
synchronized (o){
System.out.println(Thread.currentThread().getName() + "外层");
synchronized (o){
System.out.println(Thread.currentThread().getName() + "中层");
synchronized(o){
System.out.println(Thread.currentThread().getName() + "内层");
}
}
}
},"x").start();
}
public synchronized void add(){
add();
}
Lock
Lock lock = new ReentrantLock();
new Thread(() -> {
try {
lock.lock();//加锁
System.out.println(Thread.currentThread().getName() + ",外层");
try {
lock.lock();//加锁
System.out.println(Thread.currentThread().getName() + ",内层");
} finally {
lock.unlock();//解锁
}
} finally {
lock.unlock();//解锁
}
}).start();
上述代码中,如果注释掉第一次解锁,然后再加一个线程,则会发现另外一个线程的任务无法执行。
4. 死锁
两个或两个以上进程在执行过程中,因为争夺资源而造成一种互相等待的现象。
线程A持有锁A,线程B持有锁B,而这时线程A尝试获取锁B,线程B尝试获取锁A,由于两个线程没有释放对应的锁,所以就无法获取到对应锁,导致等待。
原因:
1). 系统资源不足
2). 程序运行中,推进顺序不合适
3). 资源分配不当
验证死锁:
i. 命令jps
ii. 命令jstack(JVM自带的堆栈跟踪工具)
步骤:
使用
jps -l
命令,找到我们自己的类,并几下前面的进程号
16035 org.jetbrains.jps.cmdline.Launcher
31061 org.jetbrains.kotlin.daemon.KotlinCompileDaemon
16038 cn.nl.juc.multiple_thread_lock.DeadLock
16200 jdk.jcmd/sun.tools.jps.Jps
7180 com.intellij.idea.Main
使用
jstack
+进程号,查看堆栈信息
# ...
"Thread-1" #12 prio=5 os_prio=0 tid=0x00007fcec42ff000 nid=0x3ed2 waiting for monitor entry [0x00007fcea86c4000]
java.lang.Thread.State: BLOCKED (on object monitor)
at cn.nl.juc.multiple_thread_lock.DeadLock.lambda$main$1(DeadLock.java:34)
- waiting to lock <0x00000000dbcff0c8> (a java.lang.Object)
- locked <0x00000000dbcff0d8> (a java.lang.Object)
at cn.nl.juc.multiple_thread_lock.DeadLock$$Lambda$2/1747585824.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
"Thread-0" #11 prio=5 os_prio=0 tid=0x00007fcec42fd800 nid=0x3ed1 waiting for monitor entry [0x00007fcea87c5000]
java.lang.Thread.State: BLOCKED (on object monitor)
at cn.nl.juc.multiple_thread_lock.DeadLock.lambda$main$0(DeadLock.java:20)
- waiting to lock <0x00000000dbcff0d8> (a java.lang.Object)
- locked <0x00000000dbcff0c8> (a java.lang.Object)
at cn.nl.juc.multiple_thread_lock.DeadLock$$Lambda$1/1096979270.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
# ...
Found 1 deadlock.
首先我们在最后一行可以看到,发现1个死锁。
然后,在上面可以看到"Thread-1" -waiting to lock <0x00000000dbcff0c8>,locked <0x00000000dbcff0d8>,
“Thread-2” - waiting to lock <0x00000000dbcff0d8>,- locked <0x00000000dbcff0c8>。可以发现,它们持有对方将要获取的锁。
七、Callable接口
线程创建的方式:
1.继承Thread
2.实现Runnable
3.实现Callabe
4.线程池
/**
* A task that returns a result and may throw an exception.
* Implementors define a single method with no arguments called
* {@code call}.
*
* <p>The {@code Callable} interface is similar to {@link
* java.lang.Runnable}, in that both are designed for classes whose
* instances are potentially executed by another thread. A
* {@code Runnable}, however, does not return a result and cannot
* throw a checked exception.
*
* <p>The {@link Executors} class contains utility methods to
* convert from other common forms to {@code Callable} classes.
*
* @see Executor
* @since 1.5
* @author Doug Lea
* @param <V> the result type of method {@code call}
*/
@FunctionalInterface
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
Runnable和Callable接口区别:
Runnable | Callable |
---|---|
无返回值 | 有返回值 |
不能抛出异常 | 能抛出异常 |
调用方法run | 调用call() |
通过FutureTask
来传递Callable接口。futureTask的get()可用来获取执行完成后的结果,isDone()可用来查询任务是否执行完成。
当有多个线程执行任务时,某个线程任务量大,FutureTask会先获取其他线程执行结果,最后再来获取该线程的结果,最终进行汇总。
通过Callable常见线程
FutureTask futureTask1 = new FutureTask(()-> 2009);
new Thread(futureTask1,"f").start();
System.out.println(futureTask1.get());
八、JUC辅助类
1. CountDownLatch
CountDownLatch
可以设置一个计数器,然后通过countDown
方法来进行减1的操作,使用await()等待计数器不大于0,然后继续执行await()之后的语句。
CountDownLatch
主要有两个方法,当一个或多个线程调用await()方法时,这些线程会阻塞。- 其他线程调用
countDown
方法会将计数器减1(调用countDown()的线程不会阻塞) - 当计数器的值变为0时,因await()阻塞的线程会被唤醒,继续执行。
案例:
CountDownLatch countDownLatch = new CountDownLatch(6);
//6个同学陆续离开教室,班长锁门
for (int i = 1; i <= 6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName() + "号同学离开教室");
countDownLatch.countDown();
},String.valueOf(i)).start();
}
countDownLatch.await();
System.out.println(Thread.currentThread().getName() + "完成");
2. CyclicBarrier
在使用CyclicBarrier
的构造方法第一个参数是目标障碍数,每次执行CyclicBarrier
一次障碍数就会加一,如果达到了目标障碍数,才会执行cyclicBarrier.await()
之后的语句。
private static final int NUMBER = 7;
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(NUMBER,() -> {
System.out.println("召唤神龙");
});
for(int i =1; i <= 7; i ++){
new Thread(() -> {
try {
System.out.println("龙珠" + Thread.currentThread().getName() + "收集完成。");
cyclicBarrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
}
3. Semaphore
A counting semaphore. Conceptually, a semaphore maintains a set of
permits. Each acquire blocks if necessary until a permit is
available, and then takes it. Each release adds a permit,
potentially releasing a blocking acquirer.
However, no actual permit objects are used; the Semaphore just
keeps a count of the number available and acts accordingly.
semaphore维护着许多许可证,线程想要获取锁,则必须获得许可证。
案例:
import java.util.Random;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
* Semaphore辅助类
*
* 案例:6辆车,停在3个停车位上。
*/
public class SemaphoreDemo {
public static void main(String[] args) {
//模拟3个停车位
Semaphore semaphore = new Semaphore(3);
//模拟6辆车
for (int i = 0; i <= 6; i++) {
new Thread(() -> {
try {
//抢占,获取许可后,其他线程只能等待
semaphore.acquire();
System.out.println("==>" + Thread.currentThread().getName() + "号车抢占成功!");
//设置随机停车时间
TimeUnit.SECONDS.sleep(new Random().nextInt(5));
System.out.println("<==" + Thread.currentThread().getName() + "号车离开车位!");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();//释放
}
},String.valueOf(i)).start();
}
}
}
九、ReentrantReadWriteLock读写锁
悲观锁与乐观锁
悲观锁:
每次对数据操作时,都认为数据会被修改,所以在获取数据时会对数据进行加锁操作。
共享资源一次只能给一个线程使用,其他线程阻塞,使用结束后,再将资源转给其他线程。
乐观锁:
每次对数据操作时,都认为数据不会被修改,所以不会进行加锁操作。但在更新的时候会判断一下此期间数据有没有被修改。可以使用版本号机制和CAS算法实现。
表锁和行锁
表锁:
对数据表中的记录进行操作时,会对整张表进行加锁操作。
行锁:
对数据表中的记录进行操作时,只对需要更新的记录进行加锁操作。
行锁可能发生死锁。
读锁与写锁
读写锁:一个资源可以被多个读线程访问,或者一个写线程访问。但不能同时存在读写线程,读写互斥,读读共享。
读锁:共享锁 写锁:独占锁
都会发生死锁。
共享锁死锁:
读写锁案例:模拟缓存
public class ReadWriteLockDemo {
public static void main(String[] args) {
CacheDemo cache = new CacheDemo();
for (int i = 1; i <= 5; i++) {
final int num = i;
new Thread(() -> {
cache.put(num + "", num + "");
},String.valueOf(i)).start();
}
for (int i = 1; i <= 5; i++) {
final int num = i;
new Thread(() -> {
cache.get(num + "");
},String.valueOf(i)).start();
}
}
}
class CacheDemo {
private Map<String, Object> map = new HashMap<>();
//创建读写锁
private ReadWriteLock lock = new ReentrantReadWriteLock();
//放数据
public void put(String key, Object value) {
lock.writeLock().lock();
System.out.println(Thread.currentThread().getName() + "正在写操作,key:" + key);
try {
TimeUnit.MICROSECONDS.sleep(300);
//放数据
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "线程写入完成。");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.writeLock().unlock();
}
}
//取数据
public Object get(String key) {
lock.readLock().lock();
Object res = null;
System.out.println(Thread.currentThread().getName() + "线程正在读取数据,key:" + key);
try {
TimeUnit.MICROSECONDS.sleep(300);
res = map.get(key);
System.out.println(Thread.currentThread().getName() + "线程读取数据完成!");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.readLock().unlock();
}
return res;
}
}
读写锁的缺点:
-
造成锁饥饿,一直读,没有写操作。
-
读时,不能写;只有读完之后,才可以写,写操作可以读。
读写锁降级
将写入锁降级为读锁。
获取写锁 --> 获取读锁 -> 释放写锁 --> 释放读锁
ReadWriteLock lock = new ReentrantReadWriteLock();
//读锁
Lock readLock = lock.readLock();
//写锁
Lock writeLock = lock.writeLock();
//锁降级
//1.获取到读锁
writeLock.lock();
System.out.println("--写锁--");
//2.获取读锁
readLock.lock();
System.out.println("--读锁--");
//3.释放写锁
writeLock.unlock();
//4.释放读锁
readLock.unlock();
读锁不能升级为写锁
十、BlockingQueue阻塞队列
1. 阻塞队列概述
队列:先进先出
栈:后进先出
阻塞队列:通过一个共享队列,可以使得数据由队列的一端输入,一端输出。
特点:
当队列是空的,从队列中获取元素的操作将会被阻塞。
当队列是满的,从队列添加元素的操作将会被阻塞
试图从空队列中获取元素的线程将会被阻塞,直到其他线程往空的队列插入新的元素。
试图向已满的队列中添加新元素的线程将会被阻塞,直到其他线程从队列中移除一个或多个或完全情况,使队列变得空闲起来后续新增。
使用BlockingQueue的原因:不用关心阻塞线程和唤醒线程的时机。
2. 阻塞队列的架构
package java.util.concurrent;
import java.util.Collection;
import java.util.Queue;
/**
* A {@link java.util.Queue} that additionally supports operations
* that wait for the queue to become non-empty when retrieving an
* element, and wait for space to become available in the queue when
* storing an element.
*
* <p>{@code BlockingQueue} methods come in four forms, with different ways
* of handling operations that cannot be satisfied immediately, but may be
* satisfied at some point in the future:
* one throws an exception, the second returns a special value (either
* {@code null} or {@code false}, depending on the operation), the third
* blocks the current thread indefinitely until the operation can succeed,
* and the fourth blocks for only a given maximum time limit before giving
* up. These methods are summarized in the following table:
*
* <table BORDER CELLPADDING=3 CELLSPACING=1>
* <caption>Summary of BlockingQueue methods</caption>
* <tr>
* <td></td>
* <td ALIGN=CENTER><em>Throws exception</em></td>
* <td ALIGN=CENTER><em>Special value</em></td>
* <td ALIGN=CENTER><em>Blocks</em></td>
* <td ALIGN=CENTER><em>Times out</em></td>
* </tr>
* <tr>
* <td><b>Insert</b></td>
* <td>{@link #add add(e)}</td>
* <td>{@link #offer offer(e)}</td>
* <td>{@link #put put(e)}</td>
* <td>{@link #offer(Object, long, TimeUnit) offer(e, time, unit)}</td>
* </tr>
* <tr>
* <td><b>Remove</b></td>
* <td>{@link #remove remove()}</td>
* <td>{@link #poll poll()}</td>
* <td>{@link #take take()}</td>
* <td>{@link #poll(long, TimeUnit) poll(time, unit)}</td>
* </tr>
* <tr>
* <td><b>Examine</b></td>
* <td>{@link #element element()}</td>
* <td>{@link #peek peek()}</td>
* <td><em>not applicable</em></td>
* <td><em>not applicable</em></td>
* </tr>
* </table>
*
* <p>A {@code BlockingQueue} does not accept {@code null} elements.
* Implementations throw {@code NullPointerException} on attempts
* to {@code add}, {@code put} or {@code offer} a {@code null}. A
* {@code null} is used as a sentinel value to indicate failure of
* {@code poll} operations.
*
* <p>A {@code BlockingQueue} may be capacity bounded. At any given
* time it may have a {@code remainingCapacity} beyond which no
* additional elements can be {@code put} without blocking.
* A {@code BlockingQueue} without any intrinsic capacity constraints always
* reports a remaining capacity of {@code Integer.MAX_VALUE}.
*
* <p>{@code BlockingQueue} implementations are designed to be used
* primarily for producer-consumer queues, but additionally support
* the {@link java.util.Collection} interface. So, for example, it is
* possible to remove an arbitrary element from a queue using
* {@code remove(x)}. However, such operations are in general
* <em>not</em> performed very efficiently, and are intended for only
* occasional use, such as when a queued message is cancelled.
*
* <p>{@code BlockingQueue} implementations are thread-safe. All
* queuing methods achieve their effects atomically using internal
* locks or other forms of concurrency control. However, the
* <em>bulk</em> Collection operations {@code addAll},
* {@code containsAll}, {@code retainAll} and {@code removeAll} are
* <em>not</em> necessarily performed atomically unless specified
* otherwise in an implementation. So it is possible, for example, for
* {@code addAll(c)} to fail (throwing an exception) after adding
* only some of the elements in {@code c}.
*
* <p>A {@code BlockingQueue} does <em>not</em> intrinsically support
* any kind of "close" or "shutdown" operation to
* indicate that no more items will be added. The needs and usage of
* such features tend to be implementation-dependent. For example, a
* common tactic is for producers to insert special
* <em>end-of-stream</em> or <em>poison</em> objects, that are
* interpreted accordingly when taken by consumers.
*
* <p>
* Usage example, based on a typical producer-consumer scenario.
* Note that a {@code BlockingQueue} can safely be used with multiple
* producers and multiple consumers.
* <pre> {@code
* class Producer implements Runnable {
* private final BlockingQueue queue;
* Producer(BlockingQueue q) { queue = q; }
* public void run() {
* try {
* while (true) { queue.put(produce()); }
* } catch (InterruptedException ex) { ... handle ...}
* }
* Object produce() { ... }
* }
*
* class Consumer implements Runnable {
* private final BlockingQueue queue;
* Consumer(BlockingQueue q) { queue = q; }
* public void run() {
* try {
* while (true) { consume(queue.take()); }
* } catch (InterruptedException ex) { ... handle ...}
* }
* void consume(Object x) { ... }
* }
*
* class Setup {
* void main() {
* BlockingQueue q = new SomeQueueImplementation();
* Producer p = new Producer(q);
* Consumer c1 = new Consumer(q);
* Consumer c2 = new Consumer(q);
* new Thread(p).start();
* new Thread(c1).start();
* new Thread(c2).start();
* }
* }}</pre>
*
* <p>Memory consistency effects: As with other concurrent
* collections, actions in a thread prior to placing an object into a
* {@code BlockingQueue}
* <a href="package-summary.html#MemoryVisibility"><i>happen-before</i></a>
* actions subsequent to the access or removal of that element from
* the {@code BlockingQueue} in another thread.
*
* <p>This interface is a member of the
* <a href="{@docRoot}/../technotes/guides/collections/index.html">
* Java Collections Framework</a>.
*
* @since 1.5
* @author Doug Lea
* @param <E> the type of elements held in this collection
*/
public interface BlockingQueue<E> extends Queue<E> {
/**
* Inserts the specified element into this queue if it is possible to do
* so immediately without violating capacity restrictions, returning
* {@code true} upon success and throwing an
* {@code IllegalStateException} if no space is currently available.
* When using a capacity-restricted queue, it is generally preferable to
* use {@link #offer(Object) offer}.
*
* @param e the element to add
* @return {@code true} (as specified by {@link Collection#add})
* @throws IllegalStateException if the element cannot be added at this
* time due to capacity restrictions
* @throws ClassCastException if the class of the specified element
* prevents it from being added to this queue
* @throws NullPointerException if the specified element is null
* @throws IllegalArgumentException if some property of the specified
* element prevents it from being added to this queue
*/
boolean add(E e);
/**
* Inserts the specified element into this queue if it is possible to do
* so immediately without violating capacity restrictions, returning
* {@code true} upon success and {@code false} if no space is currently
* available. When using a capacity-restricted queue, this method is
* generally preferable to {@link #add}, which can fail to insert an
* element only by throwing an exception.
*
* @param e the element to add
* @return {@code true} if the element was added to this queue, else
* {@code false}
* @throws ClassCastException if the class of the specified element
* prevents it from being added to this queue
* @throws NullPointerException if the specified element is null
* @throws IllegalArgumentException if some property of the specified
* element prevents it from being added to this queue
*/
boolean offer(E e);
/**
* Inserts the specified element into this queue, waiting if necessary
* for space to become available.
*
* @param e the element to add
* @throws InterruptedException if interrupted while waiting
* @throws ClassCastException if the class of the specified element
* prevents it from being added to this queue
* @throws NullPointerException if the specified element is null
* @throws IllegalArgumentException if some property of the specified
* element prevents it from being added to this queue
*/
void put(E e) throws InterruptedException;
/**
* Inserts the specified element into this queue, waiting up to the
* specified wait time if necessary for space to become available.
*
* @param e the element to add
* @param timeout how long to wait before giving up, in units of
* {@code unit}
* @param unit a {@code TimeUnit} determining how to interpret the
* {@code timeout} parameter
* @return {@code true} if successful, or {@code false} if
* the specified waiting time elapses before space is available
* @throws InterruptedException if interrupted while waiting
* @throws ClassCastException if the class of the specified element
* prevents it from being added to this queue
* @throws NullPointerException if the specified element is null
* @throws IllegalArgumentException if some property of the specified
* element prevents it from being added to this queue
*/
boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException;
/**
* Retrieves and removes the head of this queue, waiting if necessary
* until an element becomes available.
*
* @return the head of this queue
* @throws InterruptedException if interrupted while waiting
*/
E take() throws InterruptedException;
/**
* Retrieves and removes the head of this queue, waiting up to the
* specified wait time if necessary for an element to become available.
*
* @param timeout how long to wait before giving up, in units of
* {@code unit}
* @param unit a {@code TimeUnit} determining how to interpret the
* {@code timeout} parameter
* @return the head of this queue, or {@code null} if the
* specified waiting time elapses before an element is available
* @throws InterruptedException if interrupted while waiting
*/
E poll(long timeout, TimeUnit unit)
throws InterruptedException;
/**
* Returns the number of additional elements that this queue can ideally
* (in the absence of memory or resource constraints) accept without
* blocking, or {@code Integer.MAX_VALUE} if there is no intrinsic
* limit.
*
* <p>Note that you <em>cannot</em> always tell if an attempt to insert
* an element will succeed by inspecting {@code remainingCapacity}
* because it may be the case that another thread is about to
* insert or remove an element.
*
* @return the remaining capacity
*/
int remainingCapacity();
/**
* Removes a single instance of the specified element from this queue,
* if it is present. More formally, removes an element {@code e} such
* that {@code o.equals(e)}, if this queue contains one or more such
* elements.
* Returns {@code true} if this queue contained the specified element
* (or equivalently, if this queue changed as a result of the call).
*
* @param o element to be removed from this queue, if present
* @return {@code true} if this queue changed as a result of the call
* @throws ClassCastException if the class of the specified element
* is incompatible with this queue
* (<a href="../Collection.html#optional-restrictions">optional</a>)
* @throws NullPointerException if the specified element is null
* (<a href="../Collection.html#optional-restrictions">optional</a>)
*/
boolean remove(Object o);
/**
* Returns {@code true} if this queue contains the specified element.
* More formally, returns {@code true} if and only if this queue contains
* at least one element {@code e} such that {@code o.equals(e)}.
*
* @param o object to be checked for containment in this queue
* @return {@code true} if this queue contains the specified element
* @throws ClassCastException if the class of the specified element
* is incompatible with this queue
* (<a href="../Collection.html#optional-restrictions">optional</a>)
* @throws NullPointerException if the specified element is null
* (<a href="../Collection.html#optional-restrictions">optional</a>)
*/
public boolean contains(Object o);
/**
* Removes all available elements from this queue and adds them
* to the given collection. This operation may be more
* efficient than repeatedly polling this queue. A failure
* encountered while attempting to add elements to
* collection {@code c} may result in elements being in neither,
* either or both collections when the associated exception is
* thrown. Attempts to drain a queue to itself result in
* {@code IllegalArgumentException}. Further, the behavior of
* this operation is undefined if the specified collection is
* modified while the operation is in progress.
*
* @param c the collection to transfer elements into
* @return the number of elements transferred
* @throws UnsupportedOperationException if addition of elements
* is not supported by the specified collection
* @throws ClassCastException if the class of an element of this queue
* prevents it from being added to the specified collection
* @throws NullPointerException if the specified collection is null
* @throws IllegalArgumentException if the specified collection is this
* queue, or some property of an element of this queue prevents
* it from being added to the specified collection
*/
int drainTo(Collection<? super E> c);
/**
* Removes at most the given number of available elements from
* this queue and adds them to the given collection. A failure
* encountered while attempting to add elements to
* collection {@code c} may result in elements being in neither,
* either or both collections when the associated exception is
* thrown. Attempts to drain a queue to itself result in
* {@code IllegalArgumentException}. Further, the behavior of
* this operation is undefined if the specified collection is
* modified while the operation is in progress.
*
* @param c the collection to transfer elements into
* @param maxElements the maximum number of elements to transfer
* @return the number of elements transferred
* @throws UnsupportedOperationException if addition of elements
* is not supported by the specified collection
* @throws ClassCastException if the class of an element of this queue
* prevents it from being added to the specified collection
* @throws NullPointerException if the specified collection is null
* @throws IllegalArgumentException if the specified collection is this
* queue, or some property of an element of this queue prevents
* it from being added to the specified collection
*/
int drainTo(Collection<? super E> c, int maxElements);
}
3. 常见的BlockingQueue
1). ArrayBlockingQueue
常用的BlockingQueue,由数组结构组成的有界阻塞队列。
2). LinkedBlockingQueue
常用。由链表结构组成的有界(但大小默认值为Intger.MAX_VALUE)阻塞队列
3). DelayQueue
使用优先级队列实现的延迟无界阻塞队列。
4). PriorityBlockingQueue
支持优先级排序的无界阻塞队列
5). SynchronousQueue
不存储元素的阻塞队列,也即单个元素的队列。
6). LinkedTransferQueue
由链表组成的无界阻塞队列
7). LinkedBlockingDeque
由链表组成的双向阻塞队列
4. 核心方法
方法类型 | 抛出异常 | 特殊值 | 阻塞 | 超时 |
---|---|---|---|---|
插入 | add(e) | offer(e) | put(e) | offer(e,time,unit) |
移除 | remove() | poll() | take() | poll(tiem,unit) |
检查 | element() | peek() | 不可用 | 不可用 |
抛出异常 | 当阻塞队列满时,再往队列里add插入元素会抛出IllegalStateException:Queue full 当阻塞队列空时,再往队列里remove移除元素会抛出 NoSuchElementException |
---|---|
特殊值 | 插入方法,成功true失败false 移除方法,成功返回出队列的元素,队列里没有就返回null |
一直阻塞 | 当阻塞队列满时,生产者线程继续往队列里put元素,队列会一直阻塞生产者线程直到put数据或响应中断退出 当阻塞队列空时,消费者线程试图从队列里take元素,队列会一直阻塞消费者线程直到队列可用 |
超时退出 | 当阻塞队列满时,队列会阻塞生产者线程一定时间,超过限时后生产者线程会退出 |
add() remove()
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
//add(e)
boolean addRes = blockingQueue.add("first");
boolean addRes1 = blockingQueue.add("second");
boolean addRes2 = blockingQueue.add("third");
//element()
String element = blockingQueue.element();
System.out.println(element);
// blockingQueue.add("fourth"); //java.lang.IllegalStateException: Queue full
blockingQueue.remove();
blockingQueue.remove();
blockingQueue.remove();
// blockingQueue.remove(); //java.util.NoSuchElementException
offer() poll()
BlockingQueue<String> blockingQueue1 = new ArrayBlockingQueue<>(3);
blockingQueue1.offer("aaa");//添加元素
blockingQueue1.offer("bbb");
blockingQueue1.offer("ccc");
boolean offerRes = blockingQueue1.offer("ddd"); //false
blockingQueue1.poll();//取出元素
blockingQueue1.poll();
blockingQueue1.poll();
String poll = blockingQueue1.poll(); //null
put() take()
blockingQueue2.put("111");
blockingQueue2.put("222");
blockingQueue2.put("333");
//blockingQueue2.put("444"); // 线程一直阻塞
blockingQueue2.take();
blockingQueue2.take();
blockingQueue2.take();
//blockingQueue2.take(); //线程一直阻塞
超时
BlockingQueue<String> blockingQueue3 = new ArrayBlockingQueue<>(3);
blockingQueue3.offer("a");
blockingQueue3.offer("b");
blockingQueue3.offer("c");
blockingQueue3.offer("d", 3L, TimeUnit.SECONDS); //阻塞3秒后,退出
十一、线程池
1. 概述
线程池:维护着多个线程,等待着监督管理者分配可并发执行的任务。
线程池做的工作是控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出数量的线程排队等候,其他线程执行完毕,再从队列中取出任务来执行。
特点:
降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
提高响应速度:当任务到达时,任务可以不需要等待线程创建就能立即执行。
提高线程的可靠性:线程是稀缺资源,如果无限的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
Java中的线程池通过Executor框架实现,用到了Executor,Executors,ExecutorService,ThreadPoolExecutor
2. 线程池的使用
一池N线程 Executors.newFixedThreadPool(int)
特点:
线程池中的线程处于一定的量,可以很好的控制线程的并发量。
线程可以被重复利用,在显式关闭之前,都将一直存在。
超出一定量的线程被提交时需在队列中等待。
ExecutorService es = Executors.newFixedThreadPool(6);
try {
for (int i = 1; i <= 10; i++) {
es.execute(() -> {
System.out.println("线程" + Thread.currentThread().getName() + "正在处理---");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
es.shutdown();
}
一个任务一个任务执行,一池一线程 Executors.newSingleThreadExecutor()
ExecutorService es1 = Executors.newSingleThreadExecutor();
try {
for (int i = 1; i <= 10; i++) {
es1.execute(() -> {
System.out.println("一池一线程:线程" + Thread.currentThread().getName() + "正在处理---");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
es1.shutdown();
}
线程池根据需求创建线程,可扩容 Executors.newCachedThreadPool()
ExecutorService es2 = Executors.newCachedThreadPool();
try {
for (int i = 1; i <= 20; i++) {
es2.execute(() -> {
System.out.println("可扩容线程池:线程" + Thread.currentThread().getName() + "正在处理---");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
es2.shutdown();
}
3. 线程池参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize
核心线程数量(常驻)
maximumPoolSize
最大线程数量
keepAliveTime
unit
线程存活时间
workQueue
阻塞队列。当常驻线程池满了,就会把新的请求放在队列中。
threadFactory
线程工厂,创建线程。
handler
拒绝策略
4. 工作流程和拒绝策略
当执行execute()方法时,才会创建线程。
当常驻线程池满了,阻塞队列也满了,又有一请求,则会创建一个新线程,优先处理该请求。
拒绝策略:
AbortPolicy
(默认):直接抛出RejectedExecutionException异常阻止系统正常运行
CallerRunsPolicy
:“调用者运行”,一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量
DiscardOldestPolicy
:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务。
DiscardPolicy
:该策略默默地丢弃无法处理的任务,不予任何处理也不抛出异常,如果允许任务丢失,这是最好的一种策略。
5. 自定义线程池
ThreadPoolExecutor service = new ThreadPoolExecutor(2, 5, 2L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
try {
for (int i = 1; i <= 20; i++) {
service.execute(() -> {
System.out.println("线程" + Thread.currentThread().getName() + "正在处理---");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
service.shutdown();
}
十二、fork/join分支合并框架
1. 概述
Fork/Join可以将一个大的任务拆分成多个子任务进行并行处理,最后将子任务结果合并成最后的计算结果,并进行输出。
Fork:把复杂任务进行拆分
Join:把拆分任务的结果进行合并
任务分割:首先Fork/Join框架需要把大的任务分割成足够小的子任务,如果子任务比较大的话,还要对子任务进行继续分割。
执行任务并合并结果:分割的子任务分别放到双端队列中,然后几个启动线程分别从双端队列里获取任务执行。子任务执行完的结果都放在另外一个队列中,启动一个线程从队列里取数据,然后合并这些数据。
java.util.concurrent.ForkJoinPool
java.util.concurrent.ForkJoinTask
java.util.concurrent.RecursiveTask
2. 代码演示
案例:1+2+3…+100
相加两数,差值不超过10
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
/**
* 1+2+3...+100
*/
public class ForkJoinDemo {
public static void main(String[] args) {
CustomTask task = new CustomTask(1,100);
ForkJoinPool pool = new ForkJoinPool();
ForkJoinTask<Integer> forkJoinTask = pool.submit(task);
Integer res = null;
try {
res = forkJoinTask.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
System.out.println(res);
pool.shutdown();
}
}
class CustomTask extends RecursiveTask<Integer> {
//拆分值不超过10
private static final Integer DEFAULT_VALUE = 10;
private int begin;
private int end;
private int result;
public CustomTask(int begin, int end) {
this.begin = begin;
this.end = end;
}
@Override
protected Integer compute() {
if ((end - begin) <= DEFAULT_VALUE) {
for (int i = begin; i <= end; i++) {
result = i + result;
}
} else {
//拆分
//左侧拆分
int middle = (begin + end) / 2;
CustomTask task1 = new CustomTask(begin, middle);
//右侧拆分
CustomTask task2 = new CustomTask(middle + 1, end);
task1.fork();
task2.fork();
result = task1.join() + task2.join();
}
return result;
}
}
十三、CompletableFuture异步回调
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class CompletableFutureDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//同步
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
System.out.println(Thread.currentThread().getName());
//ForkJoinPool.commonPool-worker-1
});
completableFuture.get();
//异步
CompletableFuture<Integer> cf = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName());
//ForkJoinPool.commonPool-worker-1
// int i = 10 / 0;
return 100;
});
cf.whenComplete((T, U) -> {
System.out.println("T:" + T + ",U:" + U); //T:100,U:null
}).get();
}
}