给你比个心,渴望留住你 ^ V ^
文章目录
- JavaEE & Callable接口(NO.6线程创建方法) & JUC的常见组件 & 与线程安全有关类和集合类
- 1. JUC的常见组件
- 1.1 Callable接口的用法
- 1.1.1 Callable的构建
- 1.1.2 FutureTask对象包装Callable对象
- 1.1.3 依照“未来的任务”去构造和启动线程
- 1.1.4 根据线程引用获得返回值
- 1.1.5 测试
- 1.1.6 知识点补充
- 1.1.7 一个简单的例子
- 1.2 ReentrantLock可重入锁
- 1.3 原子类 AtomicXXX
- 1.4 线程池 ExecutorService、Executors
- 1.5 信号量 Semaphore
- 1.5.1 背景小例子
- 1.5.2 Semaphore本质
- 1.6 CountDownLatch
- 2. JUC里的线程安全有关集合类
- 2.1 线程安全的表
- 2.1.1 线程安全的顺序表 --- Vector
- 2.1.2 Collections.synchronizedList 套壳方法
- 2.1.3 CopyOnWriteArrayList “顺序表写时拷贝”
- 2.1.4 应用测试
- 2.1.5 多语句线程不安全
- 2.2 线程安全的队列
- 2.3 线程安全的哈希表
- 2.3.1 HashTable & ConcurrentHashMap
- 2.3.2 ==HashTable 和 ConcurrentHashMap 的区别==
- 2.4 对比测试
- 3.StringBuffer 和 StringBuilder
JUC ==> java.util.concurrent
与前五种方式不一样的是
例如:我们需要用一个线程去算 1 + 2 + 3 + ······ + 1000,前五种线程创建方式的话应该将此结果赋值给捕获到的变量才行~
而Callable对象是不能直接传给Thread构造方法的,“Callable” ===> 仅仅只能“可召唤的”
将这个任务传给Thread的构造方法就OK了~
Thread thread = new Thread(new FutureTask<Integer>(() -> {
int sum = 0;
for (int i = 1; i <= 1000; i++) {
sum += i;
}
return sum;
}));
thread.start();
那么就不符合我们要获得返回值的需求
ReentrantLock是一个重要的补充!
顺便提一嘴:
一些公司有一些编程规范,要求不能使用Executors去构造线程池,得用ThreadPoolExecutor去构造
你以后开公司也可以规范:“打工人们,你们只能用Executors去构造线程池!”
如果信号牌显示剩余为0,我就无法进入停车场,只能阻塞等待~
Semaphore本质其实就是个特殊的计数器,描述“可用资源”个数
所谓的“锁”本质上就是个计数器为1的信号量 Semaphore对象
取值要么是0,要么是1,===> 二元信号量
所以,信号量 Semaphore是更广义的锁~
CountDownLatch ==> 倒数弹簧锁
指的是多个线程运行的倒数一名线程结束,锁才能解除~
- 是特别针对特有的场景组件!
比如下图的下载任务1 2 3 4
显然,下载完毕应该是所有的线程,都把对应的资源下载好才算下载完毕~
使用方式:
static int count = 0;
static Object object = new Object();
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(10); // 代表有十个线程参加下载任务~
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(() -> {
for (int j = 0; j < 10_0000; j++) {
synchronized (object) {
count++;
}
}
countDownLatch.countDown();//线程安全的~
});
thread.start();
}
countDownLatch.await();//涉及阻塞都会抛出这个,被中断异常
System.out.println(count);
}
在此对象所在的线程里,创建十个线程
然后构造CountDownLatch对象,---- 传入竞赛的线程数
然后调用countDownLatch.await()方法
如果参赛选手不够,main线程就会一直等第10个人下场,那当然等不到呀~
这种只有顺序表有~
这个是不通过加锁实现的线程安全
而是通过 — 多个线程修改不同变量~
顾名思义:修改的时候 就 拷贝一份
一个例子:
public static void main(String[] args) throws InterruptedException {
List<Integer> arrayList = new ArrayList<>();
arrayList.add(1);
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
arrayList.add(1);
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
arrayList.add(1);
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(arrayList.size());
}
结果:
手动加锁:
public static void main(String[] args) throws InterruptedException {
Vector<Integer> vector = new Vector<>();
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
vector.add(1);
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
vector.add(1);
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(vector.size());
}
public static void main(String[] args) throws InterruptedException {
Vector<Integer> vector = new Vector<>();
vector.add(0);
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
vector.set(0, vector.get(0) + 1);
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
vector.set(0, vector.get(0) + 1);
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(vector);
}
必须手动套锁这些操作:
其他方式也一样:
Queue ==> BlockingQueue
Deque ==> BlockingDeque
PriorityQueue ==> PriorityBlockingQueue
传送门:
JavaEE & 线程案例 & 定时器 & 线程池 and 工厂模式
JavaEE & 线程案例 & 单例模式 and 阻塞队列
HashTable很简单粗暴,给关键方法加synchronized,针对整个表加锁
ConcurrentHashMap对局部进行加锁
HashTable是针对整个哈希表加锁,任何增删改查都会触发锁竞争!
ConcurrentHashMap则很好的解决这一点,它降低了锁的粒度,让每一个链表都分配一把锁,这样线程修改不同链表,不会触发锁的阻塞等待
补充:java1.7之前ConcurrentHashMap用的是分段锁,java1.8之后则是每个链表都有一把锁
分段锁:
有的操作,例如获取 / 更新元素个数,就可以直接使用CAS完成,不必加锁~
哈希表由于负载因子的原因,元素达到一定个数就会触发扩容机制
HashMap以及HashTable都没有解决这个问题
而ConcurrentHashMap很好解决了这个问题
也就是说当我们 put 触发扩容的时候,就会创建一个更大的内存空间,并不会把所有元素都搬运过去,也不会删除原表
并且在每次操作过程中,哈希表都会再运一些过去~
补充:Set本身就是个Map,只不过key对应value不重要~
public static void main(String[] args) throws InterruptedException {
Map<Integer, Integer> map1 = new HashMap<>();
Map<Integer, Integer> map2 = new Hashtable<>();
Map<Integer, Integer> map3 = new ConcurrentHashMap<>();
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1000000; i++) {
map1.put(i % 1000, i);
map2.put(i % 1000, i);
map3.put(i % 1000, i);
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000000; i++) {
map1.put(i % 1000, i);
map2.put(i % 1000, i);
map3.put(i % 1000, i);
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(map1.size());
System.out.println(map2.size());
System.out.println(map3.size());
System.out.println(map1);
System.out.println(map2);
System.out.println(map3);
//ConcurrentHashMap和HashTable打印方法不同,刚好相反~
}
测试结果:
测试:
public static void main(String[] args) throws InterruptedException {
StringBuilder stringBuilder = new StringBuilder();
StringBuffer stringBuffer = new StringBuffer();
Thread t1 = new Thread(() -> {
for (int i = 0; i< 5000; i++) {
stringBuffer.append(1);
stringBuilder.append(1);
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
stringBuffer.append(1);
stringBuilder.append(1);
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(stringBuilder.length());
System.out.println(stringBuffer.length());
}
测试结果:
文章到此结束!谢谢观看 可以叫我 小马,
我可能写的不好或者有错误,但是一起加油鸭!
多线程的讲解完结撒花✿✿ヽ(°▽°)ノ✿
但这是结束也是开始,多线程是JavaEE编程的重要手段!
敬请期待后续内容!