前言
在 Java 5.0 提供了 java.util.concurrent (简称 JUC )包,在此包中增加了在并发编程中很常用的实用工具类,用于定义类似于线程的自定义子系统,包括线程池、异步 IO 和轻量级任务框架。 提供可调的、灵活的线程池。还提供了设计用于 多线程上下文中的 Collection 实现等
volatile 关键字-内存可见性
JVM为每一个线程提供一个独立的缓存,用于提高效率
内存可见性(Memory Visibility)是指当某个线程正在使用对象状态 而另一个线程在同时修改该状态,需要确保当一个线程修改了对象状态后,其他线程能够看到发生的状态变化
可见性错误是指当读操作与写操作在不同的线程中执行时,我们无法确保执行读操作的线程能实时地看到其他线程写入的值,有时甚至是不可能的事情
我们可以通过同步来保证对象被安全地发布。除此之外我们也可以使用一种更加轻量级的volatile变量
Java 提供了一种稍弱的同步机制,即 volatile 变量,用来确保将变量的更新操作通知到其他线程。可以将 volatile 看做一个轻量级的锁,但是又与锁有些不同:
- 对于多线程,不是一种互斥关系
- 不能保证变量状态的 “原子性操作”
public class VolatileTest {
public static void main(String[] args) {
ThreadDemo t = new ThreadDemo();
new Thread(t).start();
while (true) {
if(t.isFlag()) {
System.out.println("------");
break;
}
}
}
}
class ThreadDemo implements Runnable {
private boolean flag = false;
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true;
System.out.println("flag = " + isFlag());
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
mian线程读取到的flag 一直都是false,所以打印结果为 flag = true,然后程序没有结束
解决:
volatile,当多个线程进行操作共享数据时,可以保证内存中的数据可见
public class VolatileTest {
public static void main(String[] args) {
ThreadDemo t = new ThreadDemo();
new Thread(t).start();
while (true) {
if(t.isFlag()) {
System.out.println("------");
break;
}
}
}
}
class ThreadDemo implements Runnable {
private volatile boolean flag = false;
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true;
System.out.println("flag = " + isFlag());
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
原子变量-CAS算法
类的小工具包,支持在单个变量上解除锁的线程安全编程。事实上,此包中的类可 将 volatile 值、字段和数组元素的概念扩展到那些也提供原子条件更新操作的类。
类 AtomicBoolean、AtomicInteger、AtomicLong 和 AtomicReference 的实例各自提供对 相应类型单个变量的访问和更新。每个类也为该类型提供适当的实用工具方法。
AtomicIntegerArray、AtomicLongArray 和 AtomicReferenceArray 类进一步扩展了原子操 作,对这些类型的数组提供了支持。这些类在为其数组元素提供 volatile 访问语义方 面也引人注目,这对于普通数组来说是不受支持的。
核心方法:boolean compareAndSet(expectedValue, updateValue)
java.util.concurrent.atomic 包下提供了一些原子操作的常用类: AtomicBoolean 、AtomicInteger 、AtomicLong 、 AtomicReference AtomicIntegerArray 、AtomicLongArray ,AtomicMarkableReference ,AtomicReferenceArray ,AtomicStampedReference
具体的一些方法,可查看API文档
i++ 原子性问题,先读取到i 然后再 ++ ,操作被分开了,有同步安全问题
public class AtomicTest {
public static void main(String[] args) {
Atomic a = new Atomic();
for (int i = 0; i < 10; i++) {
new Thread(a).start();
}
}
}
class Atomic implements Runnable {
private volatile int serialNumber = 0;
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + getSerialNumber());
}
public int getSerialNumber() {
return serialNumber++;
}
}
原子变量 jdk1.5后 java.util.concurrent.atomic包下提供了常用的原子变量
-
1.volatile 保证内存可见性
-
2.CAS算法 保证数据的原子性
CAS算法是硬件对于并发操作共享数据的支持
包含了三个操作数:
内存值 V
预估值 A
更新值 B
当且仅当 V == A时,V = B,否则,将不做任何操作
模拟CAS
public class CompareAndSwapTest {
public static void main(String[] args) {
CompareAndSwap cas = new CompareAndSwap();
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
int expectedValue = cas.get();
boolean b = cas.compareAndSet(expectedValue, (int)(Math.random() * 101));
System.out.println(b);
}
}).start();
}
}
}
class CompareAndSwap {
private int value;
// 获取内存值
public synchronized int get(){
return value;
}
// 比较
public synchronized int compareAndSwap(int expectedValue, int newValue) {
int oldValue = value;
if(oldValue == expectedValue) {
this.value = newValue;
}
return oldValue;
}
// 设置
public synchronized boolean compareAndSet(int expectedValue, int newValue) {
return expectedValue == compareAndSwap(expectedValue, newValue);
}
}
解决 i++原子性问题
public class AtomicTest {
public static void main(String[] args) {
Atomic a = new Atomic();
for (int i = 0; i < 10; i++) {
new Thread(a).start();
}
}
}
class Atomic implements Runnable {
private AtomicInteger serialNumber = new AtomicInteger();
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getSerialNumber());
}
public int getSerialNumber() {
return serialNumber.getAndIncrement();
}
}
ConcurrentHashMap 锁分段机制
Hashtable效率非常低 复合操作可能线程不安全 一次只能一个线程进行操作
复合操作包括迭代(反复获取元素,直到容器中的最后一个元素)、导航(根据一定顺序查找下一元素)、条件运算(“若不存在则添加”,“若存在则删除”)
Java 5.0 在 java.util.concurrent 包中提供了多种并发容器类来改进同步容器 的性能
ConcurrentHashMap 同步容器类是Java 5 增加的一个线程安全的哈希表。对 与多线程的操作,介于 HashMap 与 Hashtable 之间。内部采用“锁分段” 机制替代 Hashtable 的独占锁。进而提高性能
此包还提供了设计用于多线程上下文中的 Collection 实现: ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet、 CopyOnWriteArrayList 和 CopyOnWriteArraySet。当期望许多线程访问一个给 定 collection 时,ConcurrentHashMap 通常优于同步的 HashMap, ConcurrentSkipListMap 通常优于同步的 TreeMap。当期望的读数和遍历远远 大于列表的更新数时,CopyOnWriteArrayList 优于同步的 ArrayList
ConcurrentHashMap 的 concurrentLevel 是16
每个段都是独立的锁,当多个线程并发访问时,在不同的段上进行操作,则可做到并行
copyOnWriteArrayList例子
public class CopyOnWriteArrayListTest {
public static void main(String[] args) {
HelloThread ht = new HelloThread();
for (int i = 0; i < 2; i++) {
new Thread(ht).start();
}
}
}
/**
* CopyOnWriteArrayList写入并复制,添加操作多时,效率低,因为每次添加时都会进行复制,开销很大
* 并发迭代操作多时可以选择
*/
class HelloThread implements Runnable {
// 这种会出现并发修改异常
// private static List<String> list = Collections.synchronizedList(new ArrayList<String>());
private static CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
static {
list.add("AA");
list.add("BB");
list.add("CC");
}
@Override
public void run() {
Iterator<String> it = list.iterator();
while(it.hasNext()){
System.out.println(it.next());
list.add("AA");
}
}
}
CountDownLatch 闭锁
Java 5.0 在 java.util.concurrent 包中提供了多种并发容器类来改进同步容器 的性能。
CountDownLatch 一个同步辅助类,在完成一组正在其他线程中执行的操作 之前,它允许一个或多个线程一直等待。
闭锁可以延迟线程的进度直到其到达终止状态,闭锁可以用来确保某些活 动直到其他活动都完成才继续执行:
确保某个计算在其需要的所有资源都被初始化之后才继续执行;
确保某个服务在其依赖的所有其他服务都已经启动之后才启动;
等待直到某个操作所有参与者都准备就绪再继续执行
闭锁:在完成某些运算时,只有其他所有线程的运算全部完成,当前运算才能继续执行
public class CountDownLatchTest {
public static void main(String[] args) {
// 5 表示其他线程的数量
CountDownLatch latch = new CountDownLatch(5);
LatchDemo ld = new LatchDemo(latch);
long start = System.currentTimeMillis();
for (int i = 0; i < 5; i++) {
new Thread(ld).start();
}
try {
// 此处要一直等到 latch的值为0 ,就能往下执行了
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("消耗时间为:" + (end - start));
}
}
class LatchDemo implements Runnable {
private CountDownLatch latch;
public LatchDemo(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
synchronized(this) {
try {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(i);
}
}
} finally {
latch.countDown();
}
}
}
}
Condition 控制线程通信
Condition 接口描述了可能会与锁有关联的条件变量。这些变量在用 法上与使用 Object.wait 访问的隐式监视器类似,但提供了更强大的 功能。需要特别指出的是,单个 Lock 可能与多个 Condition 对象关 联。为了避免兼容性问题,Condition 方法的名称与对应的 Object 版 本中的不同
在 Condition 对象中,与 wait、notify 和 notifyAll 方法对应的分别是 await、signal 和 signalAll
Condition 实例实质上被绑定到一个锁上。要为特定 Lock 实例获得 Condition 实例,请使用其 newCondition() 方法
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
// 三个方法的使用方法和 wait,notify 和 notifyAll一样
线程按序交替
编写一个程序,开启 3 个线程,这三个线程的 ID 分别为 A、B、C,每个线程将自己的 ID 在屏幕上打印 10 遍,要 求输出的结果必须按顺序显示。 如:ABCABCABC…… 依次递归
public class ABCAlternateTest {
public static void main(String[] args) {
AlternateDemo ad = new AlternateDemo();
new Thread(new Runnable() {
@Override
public void run(){
for (int i = 1; i <= 10; i++) {
ad.loopA(i);
}
}
},"A").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
ad.loopB(i);
}
}
},"B").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
ad.loopC(i);
}
}
},"C").start();
}
}
class AlternateDemo {
// 记录当前正在执行的线程的ID
private int number = 1;
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
public void loopA(int totalLoop) {
try {
lock.lock();
if(number != 1) {
try {
condition1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 打印
for (int i = 1; i <= 1; i++) {
System.out.print(Thread.currentThread().getName());
}
number = 2;
condition2.signal();
} finally {
lock.unlock();
}
}
public void loopB(int totalLoop) {
try {
lock.lock();
if(number != 2) {
try {
condition2.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 打印
for (int i = 1; i <= 1; i++) {
System.out.print(Thread.currentThread().getName());
}
number = 3;
condition3.signal();
} finally {
lock.unlock();
}
}
public void loopC(int totalLoop) {
try {
lock.lock();
if(number != 3) {
try {
condition3.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 打印
for (int i = 1; i <= 1; i++) {
System.out.print(Thread.currentThread().getName());
}
number = 1;
condition1.signal();
System.out.print(" ");
} finally {
lock.unlock();
}
}
}
ReadWriteLock 读写锁
ReadWriteLock是一个接口
ReadWriteLock 维护了一对相关的锁,一个用于只读操作, 另一个用于写入操作。只要没有 writer,读取锁可以由 多个 reader 线程同时保持。写入锁是独占的
ReadWriteLock 读取操作通常不会改变共享资源,但执行 写入操作时,必须独占方式来获取锁。 对于读取操作占 多数的数据结构。ReadWriteLock 能提供比独占锁更高 的并发性。而对于只读的数据结构,其中包含的不变性 可以完全不需要考虑加锁操作
- 写写 / 读写 都需要“互斥”
- 读读 不需要互斥
public class ReadWriteLockTest {
public static void main(String[] args) {
ReadWriteLockDemo rw = new ReadWriteLockDemo();
new Thread(new Runnable() {
@Override
public void run() {
rw.set((int)(Math.random() * 101));
}
},"write").start();
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
@Override
public void run() {
rw.get();
}
}).start();
}
}
}
class ReadWriteLockDemo {
private int number = 0;
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
// 读
public void get() {
try {
readWriteLock.readLock().lock();
System.out.println(Thread.currentThread().getName() + ":" + number);
} finally {
readWriteLock.readLock().unlock();
}
}
// 写
public void set(int number) {
try {
readWriteLock.writeLock().lock();
System.out.println(Thread.currentThread().getName());
this.number = number;
} finally {
readWriteLock.writeLock().unlock();
}
}
}
线程八锁
静态同步方法与非静态同步方法之间是不会有竞态条件的,非静态同步方法,看他们的锁是不是同一个number,是同一个number则,一个拿到,另一个要等待;否则,另一个不用等待
* 线程八锁的关键: 1.非静态方法的锁 this, 静态方法的锁 对应的Class实例
* 2.某一个时刻内,只能由一个线程持有锁,无论几个方法
* 1.两个同步方法,两个线程,打印 one two
* 2.新增Thread.sleep()给getOne 打印 one two
* 3.新增普通方法getThree,打印 three one two
* 4.注释getThree,number2.getTwo,打印 two one
* 5.修改getOne为静态同步方法,改为number.getTwo,打印 two one
* 6.两个方法都为静态同步方法,一个number对象,打印 one two
* 7.getOne为静态同步方法,getTwo为同步方法,改为number2.getTwo,打印two one
* 8.两个静态同步方法,两个number对象,打印 one two
*/
public class Thread8MonitorTest {
public static void main(String[] args) {
Number number = new Number();
// 4
// Number number2 = new Number();
// 7
Number number2 = new Number();
new Thread(new Runnable() {
@Override
public void run() {
number.getOne();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
// 1,2,3
// number.getTwo();
// 4
// number2.getTwo();
// 5
// number.getTwo();
// 7
number2.getTwo();
}
}).start();
// 3
/*new Thread(new Runnable() {
@Override
public void run() {
number.getThree();
}
}).start();*/
}
}
class Number{
public static synchronized void getOne() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("one");
}
public static synchronized void getTwo() {
System.out.println("two");
}
public void getThree() {
System.out.println("three");
}
}
线程池
第四种获取线程的方法:线程池,一个 ExecutorService,它使用可能的几个池线程之
一执行每个提交的任务,通常使用 Executors 工厂方法配置。
线程池可以解决两个不同问题:由于减少了每个任务调用的开销,它们通常可以在
执行大量异步任务时提供增强的性能,并且还可以提供绑定和管理资源(包括执行
任务集时使用的线程)的方法。每个 ThreadPoolExecutor 还维护着一些基本的统计数
据,如完成的任务数。
为了便于跨大量上下文使用,此类提供了很多可调整的参数和扩展钩子 (hook)。但
是,强烈建议程序员使用较为方便的 Executors 工厂方法 :
- Executors.newCachedThreadPool()(无界线程池,可以进行自动线程回收)
- Executors.newFixedThreadPool(int)(固定大小线程池)
- Executors.newSingleThreadExecutor()(单个后台线程)
它们均为大多数使用场景预定义了设置。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完 放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交 通工具。
好处: 1. 提高响应速度(减少了创建新线程的时间)
2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
3.便于线程管理 corePoolSize:核心池的大小 ,/maximumPoolSize:最大线程数,keepAliveTime:线程没有任务时最多保持多长时间后会终止 线程池:提供了一个线程队列,队列中保存着所有等待状态的线程,避免了创建于销毁的额外开销,提高了响应的速度
java.util.concurrent.Executor :负责线程的使用与调度的根接口
–ExecutorService 子接口:线程池的主要接口,继承Executor
–ThreadPoolExecutor 线程池的实现类
–ScheduledExecutorService 子接口:负责线程的调度,继承ExecutorService
–ScheduledThreadPoolExecutor 继承ThreadPoolExecutor 实现ScheduledExecutorService
public class ThreadPoolTest {
public static void main(String[] args) {
// 1.创建线程池 5个线程
ExecutorService pool = Executors.newFixedThreadPool(5);
ThreadPoolDemo tpd = new ThreadPoolDemo();
/*// 2.为线程池中的线程分配任务
for (int i = 0; i < 5; i++) {
pool.submit(tpd);
}
// 3.关闭线程池,保证所有线程的任务完成才会关闭
// shutdownNow() 立即关闭,不管任务做完没有
pool.shutdown();*/
List<Future<Integer>> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {
// Future得到Callable的返回值
Future<Integer> future = pool.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int j = 0; j <= 100; j++) {
sum += j;
}
return sum;
}
});
list.add(future);
}
pool.shutdown();
for(Future<Integer> future : list) {
try {
System.out.println(future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}
class ThreadPoolDemo implements Runnable{
private int i = 0;
@Override
public void run() {
while(i <= 100){
System.out.println(Thread.currentThread().getName() + " : " + i++);
}
}
}
线程调度
ScheduledExecutorService newScheduledThreadPool() 创建固定大小的线程,可以延迟或定时的执行任务
public class ScheduledThreadPoolTest {
public static void main(String[] args) {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(5);
for (int i = 0; i < 5; i++) {
Future<Integer> result = pool.schedule(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int num = new Random().nextInt(100);
System.out.println(Thread.currentThread().getName() + ":" + num);
return num;
}
}, 3, TimeUnit.SECONDS);// 延迟3s执行
try {
System.out.println(result.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
pool.shutdown();
}
}
TimeUnit的用法
ForkJoinPool 分支/合并框架 工作窃取
Fork/Join 框架:就是在必要的情况下,将一个大任务,进行拆分(fork)成 若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进 行 join 汇总
采用 “工作窃取”模式(work-stealing): 当执行新的任务时它可以将其拆分分成更小的任务执行,并将小任务加 到线程队列中,然后再从一个随机线程的队列中偷一个并把它放在自己的队 列中
相对于一般的线程池实现,fork/join框架的优势体现在对其中包含的任务 的处理方式上.在一般的线程池中,如果一个线程正在执行的任务由于某些 原因无法继续运行,那么该线程会处于等待状态。而在fork/join框架实现中, 如果某个子问题由于等待另外一个子问题的完成而无法继续运行。那么处理 该子问题的线程会主动寻找其他尚未运行的子问题来执行.这种方式减少了 线程的等待时间,提高了性能
Stream API 可以声明性地通过 parallel() 与 sequential() 在并行流与顺序流之间进行切换
public class ForkJoinPoolTest {
public static void main(String[] args) {
Instant start = Instant.now();
ForkJoinPool pool = new ForkJoinPool();
// RecursiveTask<V> extends ForkJoinTask<V>
ForkJoinTask<Long> task = new ForkJoinTest(0L, 50000000000L);
Long sum = pool.invoke(task);
System.out.println(sum);
Instant end = Instant.now();
System.out.println(Duration.between(start, end).toMillis());
}
@Test
public void test2() { // 18256
Instant start = Instant.now();
long sum = 0;
for (long i = 0; i <= 50000000000L; i++) {
sum += i;
}
Instant end = Instant.now();
System.out.println(Duration.between(start, end).toMillis());
}
@Test
public void test3() { // 14444
// 对ForkJoin的改进
Instant start = Instant.now();
// rangeClosed 生成连续的数
long sum1 = LongStream.rangeClosed(0, 50000000000L)
.parallel()
.reduce(0, Long::sum); // 第二个参数 是函数式接口LongBinaryOperator
Instant end = Instant.now();
System.out.println(Duration.between(start, end).toMillis());
}
}
class ForkJoinTest extends RecursiveTask<Long> {// Recursive 递归
// RecursiveAction 没有返回值
// RecursiveTask 有返回值
private long start;
private long end;
public ForkJoinTest(long start, long end) {
this.start = start;
this.end = end;
}
private static final long THRESHOLD = 10000;
@Override
protected Long compute() {
long length = end - start;
if(length <= THRESHOLD){
long sum = 0;
for (long i = start; i <= end; i++) {
sum += i;
}
return sum;
} else {
long middle = (start + end) / 2;
ForkJoinTest left = new ForkJoinTest(start, middle);
left.fork(); // 拆分子任务,同时压入线程队列
ForkJoinTest right = new ForkJoinTest(middle + 1, end);
right.fork();
return left.join() + right.join();
}
}
}
最后
感谢你看到这里,看完有什么的不懂的可以在评论区问我,觉得文章对你有帮助的话记得给我点个赞,每天都会分享java相关技术文章或行业资讯,欢迎大家关注和转发文章!
网友评论