一、List集合线程安全
概述
- 线程安全集合:多线程并发的基础上修改一个集合,不会发生
ConcurrentModificationException并发修改异常 - CopyOnWriteArrayList是线程安全的集合,ArrayList是线程不安全的集合,Vector是线程安全的集合
1.先看不安全的集合ArrayList举例
package safe_list;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* ArrayList是不安全的集合
*/
public class Demo1_ArrayList {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
/*
注意 如果没问题增加线程数在试,结果是抛出 java.util.ConcurrentModificationException(并发修改异常)
JDK版本是1.8.0_251
*/
for (int i = 1; i <= 10; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
输出:
"C:\Program Files\Java\jdk1.8.0_251\bin\java.exe" "-javaagent:E:\IDEA\IntelliJ IDEA 2020.1.2\lib\idea_rt.jar=1035:E:\IDEA\IntelliJ IDEA 2020.1.2\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_251\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\rt.jar;E:\项目\study_juc\out\production\study_juc" safe_list.Demo1_ArrayList
[null, c0eee]
[null, c0eee, d1b42, 300b6]
[null, c0eee, d1b42, 300b6, 88d14, ffe2e]
[null, c0eee, d1b42]
[null, c0eee]
[null, c0eee, d1b42, 300b6, 88d14, ffe2e, f7a46]
[null, c0eee, d1b42, 300b6, 88d14]
[null, c0eee, d1b42, 300b6, 88d14, ffe2e, f7a46, 7ea2c]
[null, c0eee, d1b42, 300b6, 88d14, ffe2e, f7a46, 7ea2c, df07a, 24fbf]
Exception in thread "8" 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 safe_list.Demo1_ArrayList.lambda$main$0(Demo1_ArrayList.java:17)
at java.lang.Thread.run(Thread.java:748)
Process finished with exit code 0
2.再看线程安全的Vector集合举栗
package safe_list;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.Vector;
public class Demo2_safe_Vector {
public static void main(String[] args) {
List<String> list = new Vector<>();
/*
注意 这里的i 从0 开始就不会有 java.util.ConcurrentModificationException(并发修改异常)
JDK版本是1.8.0_251
*/
for (int i = 1; i <= 10; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(list);
}, String.valueOf(i)).start();
}
}
}
输出:
"C:\Program Files\Java\jdk1.8.0_251\bin\java.exe" "-javaagent:E:\IDEA\IntelliJ IDEA 2020.1.2\lib\idea_rt.jar=1069:E:\IDEA\IntelliJ IDEA 2020.1.2\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_251\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\rt.jar;E:\项目\study_juc\out\production\study_juc" safe_list.Demo2_safe_Vector
[1c91b, a14f8]
[1c91b, a14f8, 8f1e6, bdbcc, c0fec, c95d4]
[1c91b, a14f8, 8f1e6, bdbcc, c0fec]
[1c91b, a14f8, 8f1e6, bdbcc, c0fec, c95d4, 8b9f8, 0fda2]
[1c91b, a14f8, 8f1e6, bdbcc, c0fec]
[1c91b, a14f8, 8f1e6]
[1c91b, a14f8, 8f1e6]
[1c91b, a14f8, 8f1e6, bdbcc, c0fec, c95d4, 8b9f8, 0fda2, 26bfd]
[1c91b, a14f8, 8f1e6, bdbcc, c0fec, c95d4, 8b9f8, 0fda2, 26bfd, 87b60]
[1c91b, a14f8, 8f1e6, bdbcc, c0fec, c95d4, 8b9f8]
Process finished with exit code 0
3.再看Conllections集合工具类提供的线程安全集合方法
package safe_list;
import java.util.*;
public class Demo2_safe_Collections {
public static void main(String[] args) {
List<String> list = Collections.synchronizedList(new ArrayList<>());
/*
注意 这里的i 从0 开始就不会有 java.util.ConcurrentModificationException(并发修改异常)
JDK版本是1.8.0_251
*/
for (int i = 1; i <= 10; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(list);
}, String.valueOf(i)).start();
}
}
}
输出:
[244e4, 3da9b]
[244e4, 3da9b, 910ca, b4ce4, 8f61b]
[244e4, 3da9b, 910ca, b4ce4, 8f61b, 86355]
[244e4, 3da9b, 910ca, b4ce4]
[244e4, 3da9b, 910ca]
[244e4, 3da9b]
[244e4, 3da9b, 910ca, b4ce4, 8f61b, 86355, 62cd9, 5a89d]
[244e4, 3da9b, 910ca, b4ce4, 8f61b, 86355, 62cd9, 5a89d, dd08c, f386b]
[244e4, 3da9b, 910ca, b4ce4, 8f61b, 86355, 62cd9]
[244e4, 3da9b, 910ca, b4ce4, 8f61b, 86355, 62cd9, 5a89d, dd08c]
4.再看JUC下的线程安全集合CopyOnWriteArrayList
package safe_list;
import java.util.List;
import java.util.UUID;
import java.util.Vector;
import java.util.concurrent.CopyOnWriteArrayList;
public class Demo4_safe_CopyOnWriteArrayList {
public static void main(String[] args) {
List<String> list = new CopyOnWriteArrayList<>();
/*
注意
JDK版本是1.8.0_251
*/
for (int i = 1; i <= 10; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(list);
}, String.valueOf(i)).start();
}
}
}
输出:
"C:\Program Files\Java\jdk1.8.0_251\bin\java.exe" "-javaagent:E:\IDEA\IntelliJ IDEA 2020.1.2\lib\idea_rt.jar=3085:E:\IDEA\IntelliJ IDEA 2020.1.2\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_251\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\rt.jar;E:\项目\study_juc\out\production\study_juc" safe_list.Demo4_safe_CopyOnWriteArrayList
[d6d31, 20a3c, e34ae]
[d6d31, 20a3c, e34ae, fc492]
[d6d31, 20a3c, e34ae, fc492, 95f5c, 7dc1b]
[d6d31, 20a3c, e34ae]
[d6d31, 20a3c, e34ae]
[d6d31, 20a3c, e34ae, fc492, 95f5c, 7dc1b, 3941b]
[d6d31, 20a3c, e34ae, fc492, 95f5c]
[d6d31, 20a3c, e34ae, fc492, 95f5c, 7dc1b, 3941b, 7f912]
[d6d31, 20a3c, e34ae, fc492, 95f5c, 7dc1b, 3941b, 7f912, f46a3]
[d6d31, 20a3c, e34ae, fc492, 95f5c, 7dc1b, 3941b, 7f912, f46a3, 90a1a]
Process finished with exit code 0
- 解决ArrarList线程不安全的方案
- 使用Vector:查看Vector的add()方法,发现相比于ArrayList的add()方法前面加了synchronized修饰,因此是线程安全的
- 使用Collectiobns.synchronizedlist(new ArrayList):Collections集合工具类提供一些列线程安全的集合构造方法。
- 使用 JUC包下的 CopyOnWriteArrayList集合:一种线程安全的集合,CopyOnWrite的意思是写入时复制,是一种计算机程序设计优化策略,解决多线程写入覆盖问题
分析CopyOnWriteArrayList和Vector的add方法
=================== CopyOnWriteArrarList ========================
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
final ReentrantLock lock = this.lock;
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();
}
}
=================== Vector ============================
/**
* Appends the specified element to the end of this Vector.
*
* @param e element to be appended to this Vector
* @return {@code true} (as specified by {@link Collection#add})
* @since 1.2
*/
public synchronized boolean add(E e) { // synchronized同步方法
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
比较
- Vector的 synchronized同步方法的效率没有CopyOnWriteArrayList的lock锁的效率高
- CopyOnWriteArrayList的核心是 读写分离 Object[] newElements = Arrays.copyOf(elements, len + 1); // 写入前先复制之前的
二、Set、Map集合线程安全
Set和List类似,HashSet、是线程不安全的,解决方案有两种:
- Collections.synchronizedSet(): Set set = Collections.synchronizedSet(new HashSet<>());
- CopyOnWriteArraySet: Set set = new CopyOnWriteArraySet<>();
1.HashSet的底层是什么?
/**
* Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
* default initial capacity (16) and load factor (0.75).
*/
public HashSet() {
map = new HashMap<>();
}
再看add()方法,调用的还是HashMap的底层put()方法
/**
* Adds the specified element to this set if it is not already present.
* More formally, adds the specified element <tt>e</tt> to this set if
* this set contains no element <tt>e2</tt> such that
* <tt>(e==null ? e2==null : e.equals(e2))</tt>.
* If this set already contains the element, the call leaves the set
* unchanged and returns <tt>false</tt>.
*
* @param e element to be added to this set
* @return <tt>true</tt> if this set did not already contain the specified
* element
*/
public boolean add(E e) {
return map.put(e, PRESENT)==null;
//PRESENT 是 常量
// Dummy value to associate with an Object in the backing Map
// private static final Object PRESENT = new Object();
}
Map是Set的底层实现,也是线程不安全的,解决方案有两种:
- Collections.synchronizedMap(): Map<String,String> map = Collections.synchronizedMap(new HashMap<>());
- ConcurrentHashMap:Map<String,String> map = new ConcurrentHashMap<>();
package safe_map;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class Demo1_safe_ConcurrentHashMap {
public static void main(String[] args) {
Map<String,String> map = new ConcurrentHashMap<>();
for (int i = 0; i < 10; i++) {
new Thread(()->{
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,3));
System.out.println(map);
},String.valueOf(i)).start();
}
}
}
1.Map的容量大小和负载因子
HashMap的构造函数有三个,可以带参数
- 初始容量:使用默认初始容量 (16)
- 负载因子:默认负载因子 (0.75)
============ 使用指定的初始容量和负载因子构造一个空的 <tt>HashMap<tt>。
/**
* Constructs an empty <tt>HashMap</tt> with the specified initial
* capacity and load factor.
*
* @param initialCapacity the initial capacity
* @param loadFactor the load factor
* @throws IllegalArgumentException if the initial capacity is negative
* or the load factor is nonpositive
*/
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
============= 使用指定的初始容量和默认加载因子 (0.75) 构造一个空的 <tt>HashMap<tt>。
/**
* Constructs an empty <tt>HashMap</tt> with the specified initial
* capacity and the default load factor (0.75).
*
* @param initialCapacity the initial capacity.
* @throws IllegalArgumentException if the initial capacity is negative.
*/
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
================ 空参构造
/**
* Constructs an empty <tt>HashMap</tt> with the default initial capacity
* (16) and the default load factor (0.75).
*/
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
======================= 默认常量 ===========
/**
* The default initial capacity - MUST be a power of two.
* 默认初始容量 - 必须是 2 的幂。
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
/**
* The maximum capacity, used if a higher value is implicitly specified
* by either of the constructors with arguments.
* MUST be a power of two <= 1<<30.
* 大容量,在两个带参数的构造函数隐式指定更高值时使用。必须是 2 的幂 <= 1<<30
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* The load factor used when none specified in constructor.
* 在构造函数中未指定时使用的负载因子。
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* The bin count threshold for using a tree rather than list for a
* bin. Bins are converted to trees when adding an element to a
* bin with at least this many nodes. The value must be greater
* than 2 and should be at least 8 to mesh with assumptions in
* tree removal about conversion back to plain bins upon
* shrinkage.
*/
static final int TREEIFY_THRESHOLD = 8;
/**
* The bin count threshold for untreeifying a (split) bin during a
* resize operation. Should be less than TREEIFY_THRESHOLD, and at
* most 6 to mesh with shrinkage detection under removal.
*/
static final int UNTREEIFY_THRESHOLD = 6;
/**
* The smallest table capacity for which bins may be treeified.
* (Otherwise the table is resized if too many nodes in a bin.)
* Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts
* between resizing and treeification thresholds.
*/
static final int MIN_TREEIFY_CAPACITY = 64;
2.ConcurrentHashMap的原理
概述
All Implemented Interfaces: Serializable , ConcurrentMap <K,V>, Map <K,V> 主要构造方法有三个,可以指定负载因子、容量 负载因子:
-
主要是为了为了处理哈希冲突
-
当存在太多的冲突(即,具有不同的哈希码但是以表的大小为模数落入相同的时隙的密钥)时,该表被动态扩展,并且每个映射保持大致两个bin的预期平均效果(对应于0.75负载因素阈值调整大小)。
-
由于映射被添加和删除,这个平均值可能会有很大差异,但是总的来说,这为哈希表保留了普遍接受的时间/空间权衡。
-
然而,调整这个或任何其他类型的散列表可能是相对较慢的操作。 如果可能,
-
最好提供一个尺寸估计作为可选的initialCapacity构造函数参数。(初始容量)
-
附加的可选的loadFactor构造函数参数提供了另外的手段,通过指定在计算给定数量的元素时要分配的空间量时使用的表密度来定制初始表容量。 此外,为了与此类的先前版本兼容,构造函数可以可选地指定预期的concurrencyLevel作为内部大小调整的附加提示。 请注意,使用完全相同的许多键hashCode()是降低任何哈希表的hashCode()的一种可靠的方法。 为了改善影响,当按键为Comparable时,该类可以使用键之间的比较顺序来帮助打破关系。
三、多线程第三种创建方式Callable
- JUC下的Callable接口,前面开启线程的第三种方法就是使用Callable接口。
- Callable接口类似于Runnable ,因为它们都是为其实例可能由另一个线程执行的类设计的。 然而,A Runnable不返回结果,也不能抛出被检查的异常。
- 对比Runnable,可以有返回值、有参数、重写call方法而不是run方法,而且需要指定返回值 class MyThread implements Callable 而且重写call方法的返回值也应该与其保持一致。
- 使用步骤:资源类实现Callable接口,重写 T call() 方法,创建资源类实例mt ,然后new Thread(new FutureTask(mt)) .start()开启一个线程
======================== 资源类实现Runnable接口,未使用Lambda解耦 ===================
package callable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo1_Callable {
public static void main(String[] args) throws InterruptedException {
MyThread mt = new MyThread();
for (int i = 0; i < 10; i++) {
new Thread(mt).start();
}
}
}
/**
* 未解耦,资源类实现Runnable接口
*/
class MyThread implements Runnable{
Lock lock = new ReentrantLock();
@Override
public void run() {
//lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "====> test" );
// 唤醒
//Condition condition = lock.newCondition();
//condition.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
//lock.unlock();
}
}
}
================== 资源类实现Callable接口,未使用Lambda解耦 ============================
package callable;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo1_Callable {
public static void main(String[] args) throws InterruptedException, ExecutionException {
/**
* 开启线程需要 中间类 FutureTask 类
* 该类的·构造函数 可以 将 Callable 实例绑定,并且 该类实现 Runnable 接口
*/
MyThread mt = new MyThread();
for (int i = 0; i < 10; i++) {
FutureTask target = new FutureTask(mt);
new Thread(target).start();
// 返回值
System.out.println(target.get()); // 这个get方法可能会产生阻塞
}
}
}
/**
* 未解耦,资源类实现Callable接口
*/
class MyThread implements Callable<String> {
Lock lock = new ReentrantLock();
@Override
public String call() {
//lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "====> test" );
return "带返回值的Callable接口";
// 唤醒
//Condition condition = lock.newCondition();
//condition.signal();
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
//lock.unlock();
}
}
}
输出:
Thread-0====> test
带返回值的Callable接口
Thread-1====> test
带返回值的Callable接口
Thread-2====> test
带返回值的Callable接口
Thread-3====> test
带返回值的Callable接口
Thread-4====> test
带返回值的Callable接口
Thread-5====> test
带返回值的Callable接口
Thread-6====> test
带返回值的Callable接口
Thread-7====> test
带返回值的Callable接口
Thread-8====> test
带返回值的Callable接口
Thread-9====> test
带返回值的Callable接口
1.FutureTask类
- 该类是 连接 Runnable 和 Callable 接口的 中间类,new Thread(Runnable).start()开启线程的时候,因为资源类实现的是Callable接口,所以不能直接传。
- FutureTask类实现Runnable接口,而且的一个构造函数 可以将 Callable实例传递绑定到 该类的实例ft上,然后将ft 传入,开启线程。
- 利用ft.get()可以获得返回值,该方法可能阻塞,耗时,因此常放在最后或者使用异步。结果会被缓存,提高效率。
FutureTask target = new FutureTask(mt);
new Thread(target).start();
// 返回值
System.out.println(target.get()); // 这个get方法可能会产生阻塞
四、三大辅助工具类
1.CountDownLatch减法计数器类
- 允许一个或多个线程等待直到在其他线程中执行的一组操作完成的同步辅助。简单理解就是 计数,一个场景就是全部线程执行完毕之后才执行后边的代码,常用于必须执行的任务。
- A CountDownLatch用给定的计数初始化。 await方法阻塞,直到由于countDown()方法的调用而导致当前计数达到零,之后所有等待线程被释放,并且任何后续的await 调用立即返回。 这是一个一次性的现象 - 计数无法重置。 如果您需要重置计数的版本,请考虑使用CyclicBarrier 。
- A CountDownLatch是一种通用的同步工具,可用于多种用途。 一个CountDownLatch为一个计数的CountDownLatch用作一个简单的开/关锁存器,或者门:所有线程调用await在门口等待,直到被调用countDown()的线程打开。 一个CountDownLatch初始化N可以用来做一个线程等待,直到N个线程完成某项操作,或某些动作已经完成N次。
- CountDownLatch一个有用的属性是,它不要求调用countDown线程等待计数到达零之前继续,它只是阻止任何线程通过await ,直到所有线程结束可以通过。
package three_help_classes;
import java.util.concurrent.CountDownLatch;
public class Demo1_CountDownLatch {
public static void main(String[] args) throws InterruptedException {
// 创建计数器类
CountDownLatch countDownLatch = new CountDownLatch(4);
for (int i = 0; i < 4; i++) {
new Thread(()-> {
System.out.println(Thread.currentThread().getName() +"===>执行完毕");
countDownLatch.countDown();
},String.valueOf(i)).start();
}
// 等待上面线程全部执行完毕,才执行性下面代码
countDownLatch.await();
System.out.println("我是最后执行的~");
}
}
输出:
1===>执行完毕
2===>执行完毕
3===>执行完毕
0===>执行完毕
我是最后执行的~
- countDownLatch.countDown() :数量减1
- countDownLatch.await():等待计数器归0,然后在向下执行。
2.CyclicBarrier加法计数器类
- 允许一组线程全部等待彼此达到共同屏障点的同步辅助。 循环阻塞在涉及固定大小的线程方的程序中很有用,这些线程必须偶尔等待彼此。 屏障被称为循环 ,因为它可以在等待的线程被释放之后重新使用。
- 构造函数有两个
-
- public CyclicBarrier(int parties, Runnable barrierAction) 绑定达到技计数目标的任务
-
- public CyclicBarrier(int parties)
- 简单理解就是 达到指定目标个数线程执行完毕,才执行绑定的任务
package three_help_classes;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class Demo2_CyclicBarrier {
public static void main(String[] args) {
/**
* 构造函数有两个
* - public CyclicBarrier(int parties, Runnable barrierAction) 绑定达到技计数目标的任务
* - public CyclicBarrier(int parties)
*
*
*/
CyclicBarrier cyclicBarrier = new CyclicBarrier(6,()->{
System.out.println("6个线程全部执行完才输出!!");
});
for (int i = 1; i <= 8; i++) {
final int temp = i;
new Thread(()->{
System.out.println(Thread.currentThread().getName() + "===>执行完毕~~");
try {
// 等待设置目标线程数达标,执行绑定的方法
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
},String.valueOf(temp)).start();
}
}
}
3.Semaphore计数信号量
- 一个计数信号量。 在概念上,信号量维持一组许可证。 如果有必要,每个acquire()都会阻塞,直到许可证可用,然后才能使用它。 每个release()添加许可证,潜在地释放阻塞获取方。 但是,没有使用实际的许可证对象; Semaphore只保留可用数量的计数,并相应地执行。
- 简单理解类似限流(有限资源保持有秩序)思想,假如只有3个停车位,6辆车,无空车位之前其他三辆车需要等待。
package three_help_classes;
import java.util.Timer;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class Demo3_Semaphore {
public static void main(String[] args) {
// 可以理解为只有4个车位
Semaphore semaphore = new Semaphore(4);
for (int i = 1; i <= 10; i++) {
new Thread(()->{
try {
// 占位
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "===>拿到了车位!");
// 停车时间
TimeUnit.SECONDS.sleep(3);
// 离开车位
System.out.println(Thread.currentThread().getName() + "====>离开了!");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
// 最后告诉等待的人有1个空位了
semaphore.release();
}
},String.valueOf(i)).start();
}
}
}
输出:
1===>拿到了车位!
4===>拿到了车位!
3===>拿到了车位!
2===>拿到了车位!
3====>离开了!
2====>离开了!
4====>离开了!
1====>离开了!
6===>拿到了车位!
5===>拿到了车位!
7===>拿到了车位!
8===>拿到了车位!
8====>离开了!
7====>离开了!
5====>离开了!
6====>离开了!
10===>拿到了车位!
9===>拿到了车位!
9====>离开了!
10====>离开了!
- semaphore.acquire():获得,如果资源已经没了,等待被释放为止
- semaphore.release():释放,资源量加1,唤醒等待的线程