Java核心技术卷I-14章并发

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Kaiyang_Shao/article/details/89605016

14.1 什么是线程

  • 进程与线程
    进程是资源分配的基本单位;线程是任务调度的基本单位;
    一个进程可以包含一个或多个线程,线程可以理解为是轻量级的进程;
    进程拥有自己一整套变量,而线程则共享数据,共享变量使得线程之间的通信变得更有效容易;
  • 创建线程的两种方式
    (1)实现Runnable接口(推荐使用,将线程和执行体解耦)
//实现接口
Runnable r = ()-> {task code};
//新建线程
Thread t = new Thread(r);
//启动线程
t.start(); 

(2)继承Thread类(不推荐,1.java中是单继承,继承了thread就无法继承别的类;2.执行体和线程紧耦合,如果有多个任务需要为每个任务都创建一个独立的线程,代价太大。)

//注意:不可直接调用run方法,这样只会执行同一个线程中的任务,不会启动新的线程;
class MyThread extend Thread{
	public void run(){
		task code;
		}
}

14.2 中断线程

  • 中断线程的条件或方式
    (1)run方法执行完最后一条语句并由return返回时;
    (2)出现了在方法中没有捕获的异常时;
    (3)当在一个被阻塞的线程(调用sleep或者wait)上调用interrupt方法时阻塞调用将会被异常中断;
//相关方法
//向线程发送中断请求并将中断状态置为true
void interrupt()
//测试当前线程是否被中断,将中断状态置为false
static boolean interrupted()
//测试线程是否被终止,不改变线程的中断状态
boolean isInterrupted()
//返回当前线程的Thread对象
static Thread currentThread()

14.3 线程状态

  • 新创建线程:使用new创建一个线程的状态;
  • 可运行:调用start方法后处于的状态,可能在运行也可能不在运行,这取决与线程调度(抢占式调度(基于时间片的方式轮流调度线程)和非抢占式调度)。yield方法可以让线程失去控制权。
  • 阻塞态:当线程试图获取一个已经被其他对象获取的锁时进入阻塞状态;
  • 等待态:等待另一个线程通知调度器一个条件时,进入等待态,例如:wait方法、join方法、等待Lock或者Condition。
  • 终止态:run方法正常退出而自然死亡;因为一个没有捕获的异常而意外死亡;

14.4 线程属性

  • 线程优先级:默认情况一个线程的优先级继承其父线程,数值在1–10之间,不要将程序构建为功能的正确性依赖于优先级。
  • 守护线程:使用t.setDaemon(true)设置为守护线程,需在线程启动之前调用。使用例如计时器线程,为其他线程提供服务,守护线程应该永远不会去访问固有的资源。
  • 未捕获异常处理器:可以为线程安装一个处理器来处理未捕获的异常。

14.5 同步

  • 原因:多线程对共享变量的竞争操作会引发错误;
  • 锁对象:
    (1)synchronized关键字:自动进行加锁和释放锁,可重入会维护一个持有锁计数器;
    (2)ReentrantLock类:需要人为加锁和释放,释放锁要放在finally中以保证释放锁;
  • 条件对象:线程进入临界区发现需要满足一定的条件才可以运行时就需要条件对象。例如生产者消费者模式中,对于生产者只有缓冲区不满的时候才能生产,如果缓冲区满就要await(等待并释放锁),同时生产完成后需要唤醒消费者来消费,需要调用signalAll或者signal。
  • synchronized关键字:每个对象都有一个内部锁,可以通过锁定方法或者设置一个客户端锁定来实现;其有一些限制:1不能中断一个正在试图获得锁的线程;2试图获得锁时不能设定超时;3每个锁仅有单一的条件;
  • 使用锁的建议:1使用concurrent包中的一些机制来实现同步;2尽量使用关键字方式,因为其代码简单,出错率低;3需要一些独有的特性,例如公平性、可中断等时使用Lock锁。
  • volatile:保证可见性和有序性,对变量的写直接刷进内存,放置指令重排序(A a = new A(); 这个语句涉及到三步1分配内存空间;2初始化;3设定指针,正常情况下这三步的顺序会发生变化),不保证原子性;
  • final变量:可以安全访问一个共享域
  • 原子性:保证所有相关操作要么都执行要么都不执行;可以通过CAS来实现,原子类中提供了很多这样的操作;
public static AtomicLong largest = new AtomicLong();
do{
	oldValue = largest.get();
	newValue = Math.max(oldValue, observed);
}while(!largest.compareAndSet(oldValue, newValue));

//lambda表达式版本
largest.updateAndGet(x -> Math.max(x, observed));
  • 死锁:条件:互斥条件;请求保持;非抢占;循环等待;解决死锁的方法:死锁检测(在进行资源分配时,如果可能会导致死锁则不分配);死锁避免(破坏前面四个条件之一)
  • 线程局部变量:为每个线程提供了一个副本,可以供不同线程使用,例如时间格式化处理类。在ThreadLocal中为每一个线程都维护了一个hashmap用于存储其副本信息。
public static final ThreadLocal<SimpleDataFormat> dateFormat = ThreadLocal.withInitial(()-> new SimpleDataFormat("yyyy-MM-dd"));
//调用
String dateStamp = dateFormat.get().format(new Data());
  • 锁测试与超时:tryLock试图申请一个锁,申请成功返回true,否则返回false去执行其他事情,在调用时也可以加入超时参数;
  • 读写锁:如果多线程从一个数据结构读取数据多,写入操作少可以使用读写锁;读与读不冲突,读与写和写与写冲突;

14.6 阻塞队列

  • 当做线程管理工具需要用到put和take,不满足条件时会阻塞,而add和element会抛出异常;
//阻塞队列的类型有
ArrayBlockingQueue(int capacity)
LinkedBlockingQueue(int capacity)
LinkedBlockingDeque(int capatity)
DelayQueue()
PriorityBlockingQueue()

14.7 线程安全集合

ConcurrentLinkedQueue<E>();
ConcurrentSkipListSet<E>();
ConcurrentHashMap<K,V>();
CopyOnWriteArrayList
CopyOnWriteArraySet

14.8 Callable 与 Future

  • Runnable没有返回值,Callable有返回值;
  • 可以通过FutureTask将Callable转化为Future和Runnable;

14.9 执行器

  • 构建一个新的线程有一定代价需要与操作系统交互,如果需要大量生命周期很短的线程,应该使用线程池;线程池可以控制线程数目,放置大量线程创建导致的崩溃;
//常用的线程池方法
//其都是对ThreadPoolExector对象的实现
//必要时会创建新线程,空闲线程保留60秒
newCachedThreadPool
//固定数据线程
newFixedThreadPool
//单个线程
newSingleThreadExecutor
//用于预定执行而构建的固定线程池
newScheduledThreadPool
//用于预定执行而构建的单线程池
newSingleThreadScheduledExector

14.10 同步器

  • 信号量:限制访问资源的线程总数,通过acquire请求许可,通过release释放许可。
  • 倒计时门栓(CountDownLatch):让一个线程等待直到计数变为0,只能使用一次。
  • 障栅(CyclicBarrier):实现了一个集结点,可以循环使用。
  • 交换器:当两个线程工作在同一个数据缓冲区的两个实例上时,可以使用交换器。一个线程向缓冲区填入数据,另一个线程消耗这些数据,当他们都完成以后,相互交换缓冲区。
  • 同步队列:是一种生产者与消费者线程配对的机制。当一个线程调用SynchronousQueue的put方法时,它会阻塞直到另一个线程调用take方法为止。其不是一个队列,它的size是0。

猜你喜欢

转载自blog.csdn.net/Kaiyang_Shao/article/details/89605016