Java高并发编程笔记:什么是并发,程序、进程与线程
什么是并发
- 并发就是指程序同时处理多个任务的能力。
- 并发编程的根源在于对多个任务情况下对访问资源的有效控制。
程序、进程与线程
- 程序是静态概念,windows下通常指exe文件。
- 进程是动态概念,是程序在运行状态,进程说明程序在内存中的边界。
- 线程是进程内的一个“基本任务”,每个线程都有一个自己的功能,是CPU分配与调度的基本单位。
临界区
- 临界区是用来表示一种公共资源与共享数据,可以被多个线程使用。
- 同一时间只能有一个线程访问临界区(阻塞状态),其他资源必须等待
线程安全
在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确执行,不会出现数据污染等意外情况。
Java内存模型
Java中创建线程的三种方式
- 继承Thread类创建线程
- 实现Runnable接口创建线程
- 使用Callable和Future创建线程
并发工具包-Concurrent
- Jdk5以后我们专门提供了一个并发工具包java.util.concurrent
- java.util.concurrent 包含许多线程安全的、测试良好、高性能的并发构建快,创建concurrent的目的就是要实现Collection框架对数据 结构所执行的并发操作。通过提供一组可靠的、高性能并发构建块,开发人员可以提高并发类的线程安全、可伸缩性、性能、可读性和可靠性。
代码中的同步机制
Synchronized(同步锁)关键字的作用就是利用一个特定的对象设置一个锁lock,在多线程并发访问的时候,同时只允许一个线程可以获得这个锁,执行特定的代码。执行后释放锁,继续由其他线程争抢。
Synchronized的使用场景
Synchronized可以使用在以下三种场景,对应不同的锁对象。
- Synchronized 代码块 – 任意对象即可
- Synchronized 方法 – this当前对象
- Synchronized 静态方法 – 该类的字节码对象
线程的五种状态
死锁
线程安全与不安全区别
线程安全线程不安全的类
Java.util.concurrent
并发是伴随着多核处理器的诞生而产生的,为了充分利用硬件资源,诞生了多线程技术。但是多线程又存在资源竞争的问题,引发了同步和互斥的问题JDK1.5 推出的java.util.concurren(并发工具包)来解决这些问题。
New Thread的弊端
- new Thread() 新建对象,性能差
- 线程缺乏统一管理,可能无限制的新建线程 ,相互竞争,严重时会占用过多系统资源导致死机或oom
ThreadPool – 线程池
- 重用存在的线程,减少对象对象、消亡的开销
- 线程总数可控,提高资源的利用率
- 避免过多资源竞争,避免阻塞
- 提供额外功能,定时执行、定期执行、监控等。
线程池的种类
在java.util.concurrent中,提供了工具类Executors(调度器)对象来创建线程池,可以创建的线程池有四种:
- CachedThreadPool – 可缓存线程池
- 可缓存线程的特点就是,无限大,如果线程池中没有可用的线程则创建,有空闲线程则利用起来
- Shutdown() 代表关闭线程池(等待所有线程完成)
- ShutdownNow() 代表立即终止线程池的运行,不等待线程,不推荐使用
2.FixedThreadPool – 定长线程池
定长线程池的特点就是固定线程总数,空闲线程用于执行任务,如果线程都在使用后续任务则处于等待状态在线程池中的线程如果任务处于等待状态,备选的等待算法默认 FIFO(先进先出),LIFO(后进先出)
3.SingleThreadExecutor – 单线程池
4.ScheduledThreadPool – 调度线程池
CountDownLatch – 倒计时锁
CountDownLatch 倒计时锁特别适合“总 - 分任务”,例如多线程计算后的数据汇总
CountDownLatch 类位于java.util.concurent(JUC)包下,利用它可以实现类似计数器的功能。比如有一个任务A , 他要等其他三个任务执行完毕后才能执行,此时就可以利用CountDownLatch来实现这种功能了。
Semephore信号量
public static void main(String[] args) {
ExecutorService threadPool = Executors.newCachedThreadPool();
Semaphore semaphore = new Semaphore(10); // 定义5个信号量,也就是说服务器只允许5个人在里面玩
for (int i = 1; i <= 20 ; i++) {
final int index = i;
threadPool.execute(new Runnable() {
@Override
public void run() {
try {
semaphore.acquire();//获取一个信号量,“占用一个跑道”
play();
semaphore.release();//执行完成后,释放这个信号量。 “从跑道出去”
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
threadPool.shutdown();
}
public static void play(){
try {
System.out.println(new Date() + " " + Thread.currentThread().getName() + ":获得紫禁之巅服务器进入资格");
Thread.sleep(2000);
System.out.println(new Date() + " " + Thread.currentThread().getName() + ":退出服务器");
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
CyclicBarrier循环屏障
private static CyclicBarrier cyclicBarrier = new CyclicBarrier(5);
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 1; i <=20 ; i++) {
final int index = i;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
executorService.execute(new Runnable() {
@Override
public void run() {
go();
}
});
}
executorService.shutdown();
}
public static void go(){
System.out.println(Thread.currentThread().getName() + "准备就绪");
try {
cyclicBarrier.await();//设置屏障点,当累计5个线程都准备好时,才运行后面代码
System.out.println(Thread.currentThread().getName()+ "开始运行");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
重入锁
- 重入锁是指任意线程在获取到锁之后,再次获取该锁而不会被 该锁阻塞
- ReentrantLock设计的目标是用来代替syncchronized关键字。
ReentrantLock与synchronized的区别
Condition条件唤醒
- 我们在并行程序中,避免不了某些线程要预先规定好执行顺序,例如:先新增在修改,先买在卖。先进后出。。。,对于类似场景,使用JUC的Condition对象在合适不过。
- JUC中提供了Condition对象,用于让指定线程等待与唤醒,按预期顺序执行。他必须和ReentrantLock重入锁配合使用。
- Condition用于替代wait()和notify()方法,notify()方法用于随机唤醒等待线程。而Condition可以唤醒指定线程,这有利于更好的控制并发程序。
Condition核心方法
- await() - 阻塞当前线程,直到singal唤醒
- singal() - 唤醒被await()的线程,从中断处继续执行
- singalAll() – 唤醒所有被await()阻塞的线程(不常用)
JUC之Callable&Future
- Callable和Runnable一样代表着任务,区别在于Callable有返回值并且可以抛出异常。
- Future是一个接口,它用于表示异步计算的结果。提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。
JUC之并发容器
JUC之Atomic包与CAS算法
原子性:是指一个操作或多个操作要么全部执行,且执行过程中不会被任何因素打断,要么就都不执行。
- Atomic包是java.util.concurrent下的另一个专门为线程安全设计的java包,包含多个原子操作类。
- Atomic常用类
- AtomicInteger
- AtomicIntegerArray
- AtomicBoolean
- AtomicLong
- AtomicLongArray
CAS算法
- 锁是用来做并发最简单的方式,当然其代价也是最高的。独占锁是一种悲观锁,synchronized就是一种独占锁,他假设最坏的情况,并且只有在确保其他线程不会干扰的情况下执行,会导致其他所有需要锁的线程挂起,等待持有锁的线程释放锁。
- 所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项工作,如果因为冲突失败就重试,知道成功为止。其中CAS(比较与交换,Compare And Swap)是一种有名的无锁算法。
Atomic的应用场景
虽然基于CAS的线程安全机制很好很高效,但是要说的是,并非所有线程安全都可以用这样的方法来实现,这只适合一些粒度比较小型,如计数器这样的需求用起来才有效,否则也不会有锁的存在了。