线程池、线程通信代码,记忆:信号量,Lock等
线程通讯: wait - notify -notifyAll
信号量:Semaphore
线程池:
死锁:
线程池调度器: Executors ExecutorService Executor
Lock :ReentrantLock
ThreadLocal
原子操作类等
一:线程通信:wait - notify -notifyAll
两个线程:一个收鸡蛋,一个母鸡下蛋
public class Test {
private boolean hasEggs = false;// 现在没有鸡蛋
private byte[] lock = new byte[0];// 最小的锁
/*
* 有个人的线程
*/
Thread manT = new Thread(new Runnable() {
public void run() {
while (true) {
if (hasEggs) {
System.out.println("可以收鸡蛋了。(^∀^)");
hasEggs = false;
} else {
System.out.println("等待,收取鸡蛋");
try {
//通过锁住这个资源来
synchronized (lock) {
lock.wait();
//wait,把资源放开,等待线程的唤醒
//sleep不放开资源,只是睡多少时间的问题
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
});
Thread geGeda = new Thread(new Runnable() {
public void run() {
while (true) {
try {
Thread.sleep(200);
System.out.println("母鸡下蛋,=v=");
hasEggs = true;
// 锁住资源
synchronized (lock) {
lock.notify();
//唤醒被沉睡的资源,也就是lock.wait的资源
//还有一个notifyall的提醒机制,提示所有被wait 的来获取锁
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
});
/*
* 睡几秒,把Thread.sleep睡觉这个方法提出来
*/
private void sleep(int m) {
try {
TimeUnit.MILLISECONDS.sleep(m);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void startall() {
geGeda.start();
manT.start();
}
public static void main(String[] args) {
new Test().startall();
}}
笔记提取:
//wait,把资源放开,等待线程的唤醒
//sleep不放开资源,只是睡多少时间的问题
private byte[] lock = new byte[0];// 最小的锁
synchronized (lock) { 被锁住的资源 }:这是一个锁
lock.notify(); //唤醒被沉睡的资源,也就是lock.wait的资源
//还有一个notifyall的提醒机制,提示所有被wait 的来获取锁(容易又引起资源的争抢)
线程池:
public class Task {
public static void main(String[] args) {
try {
fix();// 重用固定线程数的线程池
sch();// 安排在给定延迟后运行命令或者定期地执行
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
/*
* 安排在给定延迟后运行命令或者定期地执行
*/
private static void sch() throws InterruptedException, ExecutionException {
ExecutorService newPool = Executors.newScheduledThreadPool(5);
// 安排在给定延迟后运行命令或者定期地执行
Set<Runnable> taskSet = new HashSet();
taskSet.add(() -> {
for (int i = 0; i < 5; i++) {
String name = Thread.currentThread().getName();
System.out.println(name + "干活yi" + i);
}
});
taskSet.add(() -> {
for (int i = 0; i < 5; i++) {
String name = Thread.currentThread().getName();
System.out.println(name + "干活er" + i);
}
});
taskSet.add(() -> {
for (int i = 0; i < 5; i++) {
String name = Thread.currentThread().getName();
System.out.println(name + "干活san" + i);
}
});
newPool.shutdown();
}
/*
* 重用固定线程数的线程池
*/
private static void fix() throws InterruptedException {
ExecutorService newPool = Executors.newFixedThreadPool(5);// 重用固定线程数的线程池
// 五个任务
Set<Callable<Integer>> taskSet = new HashSet<>();
taskSet.add(() -> {
String name = Thread.currentThread().getName();
for (int i = 0; i < 10; i++) {
System.out.println(name + "做任务yi" + i);
}
return 1;
});
taskSet.add(() -> {
String name = Thread.currentThread().getName();
for (int i = 0; i < 10; i++) {
System.out.println(name + "做任务er" + i);
}
return 2;
});
taskSet.add(() -> {
String name = Thread.currentThread().getName();
for (int i = 0; i < 10; i++) {
System.out.println(name + "做任务san" + i);
}
return 3;
});
List<Future<Integer>> invokeAll = newPool.invokeAll(taskSet);
for (Future<Integer> future : invokeAll) {
System.out.println(future);
}
newPool.shutdown();
}
}
笔记提取:
newScheduledThreadPool,安排在给定延迟后运行命令或者定期地执行
newFixedThreadPool重用固定线程数的线程池
/*
* 通过上面的程序过程(他们的区别),知道callable是有参数返回的,而runnable是没有参数返回
* callable实现call方法,runnable实现run方法
* 最好不要重复的去使用runnable,容易什么都不出来,现在他已经死了,用不起了(╥╯^╰╥),其实以前还能用的
*/
invokeAll():
会调用存在于参数集合中的所有 Callable 对象,并且返回一个包含Future对象的集合,可以通过这个返回的集合来管理每个Callable的执行结果
需要注意的是,任务有可能因为异常而导致运行结束,所以它可能并不是真的成功运行了。但是我们没有办法通过 Future 对象来了解到这个差异
背诵:
•线程池主要用来解决线程生命周期开销问题和资源不足问题。通过对多个任务重复使用线程,线程创建的开销就被分摊到了多个任务上了,而且由于在请求到达时线程已经存在,所以消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使用应用程序响应更快
自己理解:
线程池主要解决线程的生命周期的开销和资源不足的问题。通过创建多个线程,使多个任务重复的使用,这几个被创建的线程,这样创建线程的开销被分摊到各个任务中来。由于请求到达,线程已经被创建好,可以消除线程所带来的延迟,这样,立即处理请求,使响应程序会更快。
死锁:
每个 线程都使用一点资源,此时资源已经用完,等另外的资源的释放,一直使资源耗尽
public class Philosopher extends Thread {
//死锁
private String x1;
private String x2;
private int idx;
public Philosopher(String x1, String x2, int idx) {
this.x1 = x1;
this.x2 = x2;
this.idx = idx;
}
public void run() {
if (idx % 2 == 1) {
synchronized (x1) {
System.out.println("A x1锁定");
sleep();
System.out.println("A 尝试获取x1的锁");
try {
x1.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (x2) {
System.out.println("A x2锁定");
sleep();
}
}
} else
{
synchronized (x2) {
System.out.println("B x2锁定");
sleep();
System.out.println("B 尝试获取x1的锁");
synchronized (x1) {
System.out.println("B x1锁定");
sleep();
x1.notify();
}
}
}
}
private void sleep() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
信号量:
概念:进行资源协调调度的工具,保障多线程共享资源的可用性,保证它们能够正确、合理的使用公共资源。
怎么用:一个计数信号量。维护了一个许可集。在许可可用前会阻塞每一个资源申请请求,然后再获取该许可。信号量提供一个方法添加一个许可,从而可能释放一个正在阻塞的获取者。信号量对可用许可进行计数,并采取相应的行动。拿到信号量许可的线程可以进入代码,否则就等待。通过申请和释放方法获取和释放访问许可。
生产者消费者通常用型号量的方式来处理
背诵:
•信号量,有时被称为信号灯,是在多线程环境下使用的一种设施, 它负责协调各个线程, 以保证它们能够正确、合理的使用公共资源,一个计数信号量。从概念上讲,信号量维护了一个许可集。如有必要,在许可可用前会阻塞每一个资源申请请求,然后再获取该许可。信号量提供一个方法添加一个许可,从而可能释放一个正在阻塞的获取者
自己的话来说:
信号量有时候被称 信号灯 ,在 多线程 下,负责 调度 各个线程,保证 正确、合理使用 公共资源 ,用计量单位计数,从而可以释放 一群 正在 堵塞 的线程。拿到 信号量就可以 进入 被锁住 的代码块,否则就等待。通过申请、释放的方法来获取和释放访问许可。
生产者-消费者
•生产者-消费者模式描述一块缓冲区作为仓库,生产者可以将产品放入仓库,消费者则可以从仓库中取走产品
Lock:锁对象
我们并不能直接看到在哪里加了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5中提供了一个新的锁对象Lock(接口),•默认实现:ReentrantLock:
•公平锁:
先来一定先排队,一定先获取锁
•非公平锁:
不保证上述条件。非公平锁的吞吐量更高
重入锁被最近的一个成功lock的线程占有(unlock后释放)。该类有一个重要特性体现在构造器上,构造器接受一个可选参数,是否是公平锁,默认是非公平锁
代码:
ThreadLocal:共享的散列表(在同一个线程中共享一个变量)
用线程对象作为键,需要线程中共享的变量作为值保存起来,每次使用散列表.get(Thread.currentThread())的方式获取共享变量的本线程
作用:
1:解决多线程程序的并发问题
2:并不是一个Thread,而是Thread的局部变量,当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本
3:从线程的角度看,目标变量就象是线程的本地变量,这也是类名中“Local”所要表达的意思
方法: •void set(T value):将此线程局部变量的当前线程副本中的值设置为指定值
•void remove():移除此线程局部变量当前线程的值
•protected T initialValue():返回此线程局部变量的当前线程的“初始值”
•T get():返回此线程局部变量的当前线程副本中的值
原子操作类
概念:JDK5之后,Java提供了粒度更细、量级更轻,并且在多核处理器具有高性能的原子操作类。把竞争的范围缩小到单个变量上。
相当于泛化的volatile变量,能够支持原子读取-修改-写操作。
(例如:提供了get和set方法,这些volatile类型的变量在读取与写入上有着相同的内存语义。)
原子操作类在Java.util.concurrent.atomic包下,可以分为四种类型的原子更新类:
•原子更新基本类型
•原子更新数组类型
•原子更新引用
•原子更新属性
使用原子方式更新基本类型:
•AtomicBoolean:原子更新布尔变量
•AtomicInteger:原子更新整型变量
•AtomicLong:原子更新长整型变量
通过原子更新数组里的某个元素:
•AtomicIntegerArray:原子更新整型数组的某个元素
•AtomicLongArray:原子更新长整型数组的某个元素
•AtomicReferenceArray:原子更新引用类型数组的某个元素
AtomicIntegerArray常用的方法有:
•int **addAndSet(int i,int **delta):以原子方式将输入值与数组中索引为i的元素相加
•boolean **compareAndSet **( int i, int expect, int update):如果当前值等于预期值,则以原子方式更新数组中索引为i的值为update值
需要更新引用类型往往涉及多个变量:
•AtomicReference:原子更新引用类型
•AtomicReferenceFieldUpdater:原子更新引用类型里的字段
•AtomicMarkableReference:原子更新带有标记位的引用类型
如果需要原子更新某个类的某个字段,就需要用到原子更新字段类,可以使用以下几个类:
•AtomicIntegerFieldUpdater:原子更新整型字段
•AtomicLongFieldUpdater:原子更新长整型字段
•AtomicStampedReference:原子更新带有版本号的引用类型。
要想原子更新字段,需要两个步骤:
•每次必须使用newUpdater创建一个更新器,并且需要设置想要更新的类的字段
•更新类的字段(属性)必须为public volatile
线程的工具类:(自己创建的 )
工作线程
1:创建时,wait.
2:当有任务时,唤醒(notify)线程,执行任务
3:任务完毕之后进入wait
线程的运行状态(生命周期):
1:新建 NEW:new创建线程对象
2:就绪 : 调用start方法后,得到了除CPU时间以外的所有资源
3:运行 : 获得CPU时间,进入运行状态
4:阻塞/等待 : 遇到sleep、yield、wait等等
5:结束 : 中断或者执行结束