1、JUC是什么
java.util.concurrent
在并发编程中使用的工具类
1.1、进程和线程
进程:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
线程:通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程可以利用进程所拥有的资源,在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位,由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统多个程序间并发执行的程度。
1.2、进程状态
Thread.State
public enum State {
/**
* 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;(终结)
}
1.3、线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。
1、新建状态(New):新创建了一个线程对象。
2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
(三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)
5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
1.3、终止线程
方法一:stop()
方法(标注为废弃的方法)(不推荐,因为它会强制停止线程,可能会导致数据错乱。)
方法二:使用volatile
变量做标记
volatile boolean stopMe = false;
@Override
public void run() {
while (true) {
// test
if (stopMe) {
System.out.println("exit by me");
break;
}
synchronized (u) {
int v = (int) (System.currentTimeMillis() / 1000);
u.setId(v);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
u.setName(String.valueOf(v));
System.out.println(u.toString());
}
Thread.yield();
}
}
1.4、线程中断
Thread类中有三个与线程中断相关的方法:
public void interrupt();//中断线程
public boolean isInterrupted();//判断是否被中断
public static boolean interrupted();//判断是否被中断,并清除当前中断状态
1.5、等待(wait)和通知(notify)
1.6、挂起(suspend)和继续执行(resume)线程
2、Lock接口
Lock implementations provide more extensive locking operations than can be obtained using synchronized methods and statements. They allow more flexible structuring, may have quite different properties, and may support multiple associated Condition objects. \
锁实现提供了比使用同步方法和语句可以获得的更广泛的锁操作。它们允许更灵活的结构,可能具有非常不同的属性,并且可能支持多个关联的条件对象。
2.1、复习Synchronized
多线程编程模板上
1、线程 操作 资源类
2、高内聚低耦合
实现步骤
1、创建资源类
2、资源类里创建同步方法、同步代码块
2.2、Lock接口的实现ReentrantLock可重入锁
synchronized与Lock的区别
1.首先synchronized
是java
内置关键字,在jvm
层面,Lock
是个java类;
2.synchronized
无法判断是否获取锁的状态,Lock
可以判断是否获取到锁;
3.synchronized
会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock
需在finally
中手工释放锁(unlock()
方法释放锁),否则容易造成线程死锁;
4.用synchronized
关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock
锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
5.synchronized
的锁可重入、不可中断、非公平,而Lock
锁可重入、可判断、可公平(两者皆可)
6.Lock
锁适合大量同步的代码的同步问题,synchronized
锁适合代码少量的同步问题。
2.3、创建线程方式
- 继承Thread
public class SaleTicket extends Thread
java是单继承,资源宝贵,要用接口方式Thread(Runnable target, String name)
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
public class MyThread implements Runnable{
@Override
public void run() {
}
}
class MyThread2 implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("come in call method()");
// 暂停一会线程
try{
TimeUnit.SECONDS.sleep(4);
}catch (InterruptedException e){
e.printStackTrace();
}
return 1024;
}
}
/**
* 多线程中,第三种获得多线程的方式
*/
class CallableDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// public class FutureTask<V> implements RunnableFuture<V>
// public interface RunnableFuture<V> extends Runnable, Future<V>
FutureTask<Integer> futureTask = new FutureTask<>(new MyThread2());
new Thread(futureTask,"A").start();
new Thread(futureTask,"B").start();
// System.out.println(futureTask.get());
System.out.println(Thread.currentThread().getName()+"计算完成");
System.out.println(futureTask.get());
}
}
例子卖票程序
package com.hong.juc;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Ticket { // 资源类
private int number = 30;
Lock lock = new ReentrantLock();
public void sale(){
lock.lock();
try {
if (number > 0){
System.out.println(Thread.currentThread().getName()+"\t卖出第:"+(number--)+"\t还剩下:"+number);
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public synchronized void saleTicket(){
if (number > 0){
System.out.println(Thread.currentThread().getName()+"卖出第"+(number--)+"张票,"+"还剩"+number+"票");
}
}
}
/**
* 题目:三个售货员 卖出 30张票
* 多线程编程的企业级套路 + 模板
*
* 1. 高内聚低耦合的前提下,线程 操作(对外暴露的调用方法) 资源类
*
*/
class Saleticket{
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 40; i++) {
ticket.saleTicket();
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
},"A").start();
new Thread(() -> { for (int i = 1; i <= 40; i++) ticket.saleTicket(); },"B").start();
}
}
3、Java8之lambda表达式复习
3.1、lambda表达式
Lambda 是一个匿名函数,我们可以把 Lambda表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。
Lambda 表达式在Java 语言中引入了一个新的语法元素和操作符。这个操作符为 “->” , 该操作符被称为 Lambda 操作符或剪头操作符。它将 Lambda 分为两个部分:
左侧:指定了 Lambda 表达式需要的所有参数
右侧:指定了 Lambda 体,即 Lambda 表达式要执行的功能
接口里是否能有实现方法?
接口里在java8
后容许有接口的实现,default
方法默认实现
default int div(int x,int y) {
return x/y;
}
接口里default方法可以有几个? 静态方法实现:接口新增
public static int sub(int x,int y){
return x-y;
}
可以有几个? 注意静态的叫类方法,能用foo去调吗?要改成Foo
3.2、代码演示
package com.hong.lambda;
//@FunctionalInterface
interface Foo{
public void sayHello();
default int div(int x,int y){
System.out.println("default");
return x/y;
}
public static int mv(int x,int y){
return x*y;
}
}
/**
* 口诀: 拷贝小括号,写死右箭头,落地大括号
*
* java8之后:接口实现 default
*
* 静态方法实现
*/
public class LambdaExpessDemo {
public static void main(String[] args) {
Foo foo = new Foo() {
@Override
public void sayHello() {
System.out.println("lambda");
}
};
foo.sayHello();
foo = () -> {
System.out.println("lambda");
};
System.out.println(foo.div(10,5));
System.out.println(Foo.mv(3,5));
}
}
4、线程间通信
先看一道面试题
两个线程,一个线程打印1-52,另一个打印字母A-Z打印顺序为12A34B...5152Z, 要求用线程间通信
4.1、生产者、消费者问题
假设有一台空调,四个线程同时对空调操作,两个线程升温,两个线程降温,问如何保证最后温度为0
这里要用到进程的唤醒和等待
synchronized
package com.hong.juc;
// 资源类
class AirConditioner{
private int number = 0;
public synchronized void increment() throws InterruptedException {
// 1.判断
// if (number != 0){
// A C 两个生产者等待 B 吃完全部唤醒
// this.wait();
// }
while (number != 0){
this.wait();
// wait释放锁后 被唤醒的线程要再判断一次再执行后面操作
}
// 2.干活
number++;
System.out.println(Thread.currentThread().getName()+"\t"+number);
// 3. 通知(唤醒)
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException {
while (number == 0){
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"\t"+number);
this.notifyAll();
}
}
/**
* 题目:现在两个线程,可以操作初始值为零的一个变量,
* 实现一个线程对该变量加1,一个线程对该变量-1,
* 实现交替,来10轮,变量初始值为0.
* 1.高内聚低耦合前提下,线程操作资源类
* 2.判断/干活/通知
* 3.多线程交互中,防止虚假唤醒(判断只能用while,不能用if)
* 知识小总结:多线程编程套路+while判断+新版写法
* 多线程调度横向
*/
public class ThreadWaitNotifyDemo {
public static void main(String[] args) {
AirConditioner conditioner = new AirConditioner();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
conditioner.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
conditioner.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
conditioner.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
conditioner.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
注意:多线程交互中,防止虚假唤醒(判断只能用while,不能用if) 当一个条件满足时,很多线程都被唤醒了,但是只有其中部分是有用的唤醒,其它的唤醒都是无用功 知识小总结:多线程编程套路+while判断+新版写法
4.2、引入Condition改进生产者消费者模式
Condition
是在java 1.5中才出现的,它用来替代传统的Object的wait()、notify()
实现线程间的协作,相比使用Object的wait()、notify()
,使用Condition
的await()、signal()
这种方式实现线程间协作更加安全和高效。因此通常来说比较推荐使用Condition
,阻塞队列实际上是使用了Condition
来模拟线程间协作。
Condition
是个接口,基本的方法就是await()
和signal()
方法;
Condition
依赖于Lock
接口,生成一个Condition
的基本代码是lock.newCondition()
调用Condition
的await()
和signal()
方法,都必须在lock保护之内,就是说必须在lock.lock()
和lock.unlock
之间才可以使用
package com.hong.juc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Aircondition{
private int number = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void increment() throws Exception{
lock.lock();
try {
//1.判断
while (number != 0){
// this.wait();
condition.await();
}
//2.干活
number++;
System.out.println(Thread.currentThread().getName()+"\t"+number);
//3通知
// this.notifyAll();
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void decrement() throws Exception{
lock.lock();
try {
//1.判断
while (number == 0){
condition.await();
}
//2.干活
number--;
System.out.println(Thread.currentThread().getName()+"\t"+number);
//3通知
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class ThreadWaitNotifyDemo2 {
public static void main(String[] args) {
AirConditioner conditioner = new AirConditioner();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
conditioner.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
conditioner.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
conditioner.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
conditioner.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
5、线程间定制化调用通信
现在想解决上面那道面试题,怎么控制线程打印的顺序 假如要实现以下功能:
多线程之间按顺序调用,实现A->B->C
三个线程启动,要求如下:
AA打印5次,BB打印10次,CC打印15次
接着AA打印5次,BB打印10次,CC打印15次
来10轮
这里我们就不能使用上面那种方法了,要按照顺序执行,那么就要使用java.util.concurrent.locks.Condition
来精确通知
精确通知顺序访问
- Condition类把Object监视器方法(wait,nofify, notifyAll)分解为不同对象,通过与Lock实现类的合并使用,Condition可以产生每个object都有多个等待集的效果。在Lock实现类替代synchronized方法或语句块的地方,Condition可以替换Object监视器方法。
- Conditions(也称为条件队列或条件变量),为一个线程提供了挂起执行(等待)的方法,直到另一个线程通知它某些状态条件可能为真。
- 因为对共享状态信息的访问发生在不同线程,该共享信息必须被保护,因此某种形式的锁与condition条件相关联。等待条件提供的关键属性是它(条件-condition)可以原子性释放关联的锁并挂起当前线程,就像Object.wait() 方法那样。
- condition-条件实例本质上是绑定到锁Lock上的。要获取特定Lock实例的条件对象,使用newCondition() 方法;
package com.hong.juc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Aircondition{
private int number = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void increment() throws Exception{
lock.lock();
try {
//1.判断
while (number != 0){
// this.wait();
condition.await();
}
//2.干活
number++;
System.out.println(Thread.currentThread().getName()+"\t"+number);
//3通知
// this.notifyAll();
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void decrement() throws Exception{
lock.lock();
try {
//1.判断
while (number == 0){
condition.await();
}
//2.干活
number--;
System.out.println(Thread.currentThread().getName()+"\t"+number);
//3通知
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class ThreadWaitNotifyDemo2 {
public static void main(String[] args) {
AirConditioner conditioner = new AirConditioner();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
conditioner.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
conditioner.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
conditioner.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
conditioner.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
6、不安全集合
面试题:我们知道ArrayList是线程不安全,请编写一个不安全的案例并给出解决方案
由于此张内容在JUC上文中探讨过,这里就不过多讨论
4.1、List
ArrayList线程不安全,会报java.util.ConcurrentModificationException异常
for (int i = 0; i < 30; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(list);
}, String.valueOf(i)).start();
}
堆栈信息
java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at java.util.AbstractCollection.toString(AbstractCollection.java:461)
at java.lang.String.valueOf(String.java:2994)
at java.io.PrintStream.println(PrintStream.java:821)
at com.hong.collection.ArrayListNotSafeDemo.lambda$0(ArrayListNotSafeDemo.java:20)
at java.lang.Thread.run(Thread.java:748)
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
...
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;//modCount在AbstractList类声明
Itr() {}
...
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
...
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();//<---异常在此抛出
}
}
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;//添加时,修改了modCount的值
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
...
}
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
...
protected transient int modCount = 0;
...
}
综上所述,假设线程A将通过迭代器next()
获取下一元素时,从而将其打印出来。但之前,其他某线程添加新元素至list
,结构发生了改变,modCount
自增。当线程A运行到checkForComodification()
,expectedModCount
是modCount
之前自增的值,判定modCount != expectedModCount
为真,继而抛出ConcurrentModificationException
。
4.1.1、解决问题 ArrayList 线程不安全
- 使用 new Vector<>();(ArrayList所有方法加synchronized,太重)。
public synchronized void addElement(E obj) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = obj;
}
private void ensureCapacityHelper(int minCapacity) {
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
- 使用 Collections.synchronizedList(new ArrayList<>()); 转换成线程安全类。
public static <T> List<T> synchronizedList(List<T> list) {
return (list instanceof RandomAccess ?
new SynchronizedRandomAccessList<>(list) :
new SynchronizedList<>(list));
}
- 使用 new java.concurrent.CopyOnWriteArrayList<>();(推荐)。 CopyOnWriteArrayList 写时复制
- 写时复制:CopyOnWrite容器,即写时复制的容器。
- 往一个容器添加元素的时候,不直接往当前容器 Object[] 添加,而是先将当前 Object[] 进行Copy,复制出一个新的容器Object[] newElements,然后新的容器Object[] newElements里添加元素,添加完元素之后,再将原容器的引用指向新的容器setArray(newElements)
- 这样做的好处是可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。
- 所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。 部分源码:
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
/** The array, accessed only via getArray/setArray. */
private transient volatile Object[] array;
final Object[] getArray() {
return array;
}
final void setArray(Object[] a) {
array = a;
}
...
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
...
public String toString() {
return Arrays.toString(getArray());
}
...
}
CopyOnWrite
容器即写时复制的容器。待一个容器添加元素的时候,不直接往当前容器Object[]添加,而是先将当前容器Object[]进行copy,复制出一个新的容器Object[] newELements,然后新的容器Object[ ] newELements里添加元素,添加完元素之后,再将原容器的引用指向新的容器setArray (newELements)。
这样做的好处是可以对CopyOnWrite
容器进行并发的读,而不需要加锁(区别于Vector
和Collections.synchronizedList())
,因为当前容器不会添加任何元素。所以CopyOnWrite
容器也是一种读写分离的思想,读和写不同的容器。
4.2、Set
HashSet也是非线性安全的。(HashSet底层是包装了一个HashMap的)
4.2.1、解决办法
Collections.synchronizedSet(new HashSet<>())
CopyOnWriteArraySet<>()(推荐)
public class CopyOnWriteArraySet<E> extends AbstractSet<E>
implements java.io.Serializable {
private static final long serialVersionUID = 5457747651344034263L;
private final CopyOnWriteArrayList<E> al;
/**
* Creates an empty set.
*/
public CopyOnWriteArraySet() {
al = new CopyOnWriteArrayList<E>();
}
public CopyOnWriteArraySet(Collection<? extends E> c) {
if (c.getClass() == CopyOnWriteArraySet.class) {
@SuppressWarnings("unchecked") CopyOnWriteArraySet<E> cc =
(CopyOnWriteArraySet<E>)c;
al = new CopyOnWriteArrayList<E>(cc.al);
}
else {
al = new CopyOnWriteArrayList<E>();
al.addAllAbsent(c);
}
}
//可看出CopyOnWriteArraySet包装了一个CopyOnWriteArrayList
...
public boolean add(E e) {
return al.addIfAbsent(e);
}
public boolean addIfAbsent(E e) {
Object[] snapshot = getArray();
return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
addIfAbsent(e, snapshot);
}
//暴力查找
private static int indexOf(Object o, Object[] elements,
int index, int fence) {
if (o == null) {
for (int i = index; i < fence; i++)
if (elements[i] == null)
return i;
} else {
for (int i = index; i < fence; i++)
if (o.equals(elements[i]))
return i;
}
return -1;
}
private boolean addIfAbsent(E e, Object[] snapshot) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] current = getArray();
int len = current.length;
if (snapshot != current) {//还要检查多一次元素存在性,生怕别的线程已经插入了
// Optimize for lost race to another addXXX operation
int common = Math.min(snapshot.length, len);
for (int i = 0; i < common; i++)
if (current[i] != snapshot[i] && eq(e, current[i]))
return false;
if (indexOf(e, current, common, len) >= 0)
return false;
}
Object[] newElements = Arrays.copyOf(current, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
...
}
4.3、Map
4.3.1、解决办法
HashTable
HashTable与HashMap对比
(1)线程安全:HashMap是线程不安全的类,多线程下会造成并发冲突,但单线程下运行效率较高;HashTable是线程安全的类,很多方法都是用synchronized修饰,但同时因为加锁导致并发效率低下,单线程环境效率也十分低;
(2)插入null:HashMap允许有一个键为null,允许多个值为null;但HashTable不允许键或值为null;
(3)容量:HashMap底层数组长度必须为2的幂,这样做是为了hash准备,默认为16;而HashTable底层数组长度可以为任意值,这就造成了hash算法散射不均匀,容易造成hash冲突,默认为11;
(4)Hash映射:HashMap的hash算法通过非常规设计,将底层table长度设计为2的幂,使用位与运算代替取模运算,减少运算消耗;而HashTable的hash算法首先使得hash值小于整型数最大值,再通过取模进行散射运算;
Collections.synchronizedMap(new HashMap<>())
ConcurrencyMap<>()(推荐)
7.多线程锁(八锁问题)
假设有一个手机资源类,现在用线程来访问,请回答以下问题
- 1 标准访问,先打印短信还是邮件
- 2 停4秒在短信方法内,先打印短信还是邮件
- 3 普通的hello方法,是先打短信还是hello
- 4 现在有两部手机,先打印短信还是邮件
- 5 两个静态同步方法,1部手机,先打印短信还是邮件
- 6 两个静态同步方法,2部手机,先打印短信还是邮件
- 7 1个静态同步方法,1个普通同步方法,1部手机,先打印短信还是邮件
- 8 1个静态同步方法,1个普通同步方法,2部手机,先打印短信还是邮件
class Phone{
public synchronized void sendEmail() throws Exception{
// Thread.sleep(4000);
// try {
// // sleep不会释放锁,只释放时间片
// TimeUnit.SECONDS.sleep(4);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
System.out.println("sendEmail");
}
public synchronized void sendSMS() throws Exception{
System.out.println("sendSMS");
}
public void hello(){
System.out.println("hello");
}
}
public class Lock8 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
Phone phone2 = new Phone();
new Thread(() -> {
try {
phone.sendEmail();
} catch (Exception e) {
e.printStackTrace();
}
},"A").start();
// Thread.sleep(100);
new Thread(() -> {
try {
// phone.sendSMS();
phone.hello();
// phone2.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
},"B").start();
}
}
运行答案: 1、短信 2、短信 3、Hello 4、邮件 5、短信 6、短信 7、邮件 8、邮件 对象锁:
- 2、一个对象里面如果有多个synchronized方法,某一个时刻内,只要一个线程去调用其中的一个synchronized方法了,其它的线程都只能等待,换句话说,某一个时刻内,只能有唯一一个线程去访问这些synchronized方法锁的是当前对象this,被锁定后,其它的线程都不能进入到当前对象的其它的synchronized方法
- 3、加个普通方法后发现和同步锁无关
- 4、换成两个对象后,不是同一把锁了,情况立刻变化。 全局锁
- 6、synchronized实现同步的基础:Java中的每一个对象都可以作为锁。
- 具体表现为以下3种形式。
- 对于普通同步方法,锁是当前实例对象。
- 对于静态同步方法,锁是当前类的Class对象。
- 对于同步方法块,锁是Synchonized括号里配置的对象
- 8、当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。
- 也就是说如果一个实例对象的非静态同步方法获取锁后,该实例对象的其他非静态同步方法必须等待获取锁的方法释放锁后才能获取锁,可是别的实例对象的非静态同步方法因为跟该实例对象的非静态同步方法用的是不同的锁,所以毋须等待该实例对象已获取锁的非静态同步方法释放锁就可以获取他们自己的锁。
- 所有的静态同步方法用的也是同一把锁——类对象本身,这两把锁是两个不同的对象,所以静态同步方法与非静态同步方法之间是不会有竞态条件的。但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁,而不管是同一个实例对象的静态同步方法之间,还是不同的实例对象的静态同步方法之间,只要它们同一个类的实例对象