一、死锁*
1.1 概述
- 当第一个线程拥有A对象锁标记,并等待B对象锁标记,同时第二个线程拥有B对象锁标记,并等待A对象锁标记时,产生死锁。
- 一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已经拥有的钡怀记,由此可能造成死锁。
1.2 代码实现
public class Demo01 {
public static void main(String[] args) {
/**
* 创建类
* 测试类
* 锁对象
* 存储两个锁
* 线程BB
* 线程CC
*/
// 创建创建
ThreadBB bb = new ThreadBB();
ThreadCC cc = new ThreadCC();
// 开启线程
bb.start();
cc.start();
}
}
class LockObject {
public static final Object LOCK_A = new Object();
public static final Object LOCK_B = new Object();
}
class ThreadBB extends Thread {
@Override
public void run() {
synchronized (LockObject.LOCK_A) {
System.out.println("BB获取到第一根筷子LOCK_A,正在尝试获取第二根筷子LOCK_B");
synchronized (LockObject.LOCK_B) {
System.out.println("BB获取到筷子LOCK_A和筷子LOCK_B,可以开动啦");
}
}
}
}
class ThreadCC extends Thread {
@Override
public void run() {
synchronized (LockObject.LOCK_B) {
System.out.println("CC获取到第一根筷子LOCK_B,正在尝试获取第二根筷子LOCK_A");
synchronized (LockObject.LOCK_A) {
System.out.println("CC获取到筷子LOCK_A和筷子LOCK_B,可以开动啦");
}
}
}
}
二、生产者和消费者
2.1 概述
-
若干个生产者在生产产品,这些产品将提供给若干个消费者去消费
-
为了使生产者和消费者能并发执行,在两者之间设置一个能存储多个产品的缓冲区
-
生产者将生产的产品放入缓冲区中,消费者从缓冲区中取走产品进行消费
-
显然生产者和消费者之间必须保持同步
- 即不允许消费者到一个空的缓冲区中取产品
- 也不允许生产者向一个满的缓冲区中放入产品。
2.2 第一版
- 线程安全问题
- 顺序问题
public class Demo01 {
public static void main(String[] args) {
/**
* 测试类
*
* 线程BB
* 线程CC
*/
// 创建对象
Card card = new Card();
ThreadBB bb = new ThreadBB(card);
ThreadCC cc = new ThreadCC(card);
// 开启线程
bb.start();
cc.start();
}
}
class Card {
private int money = 0;
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
}
class ThreadBB extends Thread {
private Card card;
public ThreadBB(Card card) {
super();
this.card = card;
}
@Override
public void run() {
for (int i = 1; i <= 12; i++) {
card.setMoney(card.getMoney()-5000);
System.out.println("BB取出了5000,余额" + card.getMoney());
}
}
}
class ThreadCC extends Thread {
private Card card;
public ThreadCC(Card card) {
super();
this.card = card;
}
@Override
public void run() {
for (int i = 1; i <= 12; i++) {
card.setMoney(card.getMoney()+5000);
System.out.println("CC存入了5000,余额" + card.getMoney());
}
}
}
2.3 第二版
- 线程之间无通讯,使用i--的方式处理无法执行的线程
public class Demo01 {
public static void main(String[] args) {
/**
* 测试类
*
* 线程BB
* 线程CC
*/
// 创建对象
Card card = new Card();
ThreadBB bb = new ThreadBB(card);
ThreadCC cc = new ThreadCC(card);
// 开启线程
bb.start();
cc.start();
}
}
class Card {
private int money = 0;
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
}
class ThreadBB extends Thread {
private Card card;
public ThreadBB(Card card) {
super();
this.card = card;
}
@Override
public void run() {
for (int i = 1; i <= 12; i++) {
synchronized (Card.class) {
if (card.getMoney() <= 0) {
i--;
continue;
}
card.setMoney(card.getMoney()-5000);
System.out.println("BB取出了5000,余额" + card.getMoney());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
class ThreadCC extends Thread {
private Card card;
public ThreadCC(Card card) {
super();
this.card = card;
}
@Override
public void run() {
for (int i = 1; i <= 12; i++) {
if (card.getMoney() > 0) {
i--;
continue;
}
synchronized (Card.class) {
card.setMoney(card.getMoney()+5000);
System.out.println("CC存入了5000,余额" + card.getMoney());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
2.4 第三版
- 采用线程通讯方式交替执行
public class Demo01 {
public static void main(String[] args) {
/**
* 测试类
*
* 线程BB
* 线程CC
*/
// 创建对象
Card card = new Card();
ThreadBB bb = new ThreadBB(card);
ThreadCC cc = new ThreadCC(card);
// 开启线程
bb.start();
cc.start();
}
}
class Card {
private int money = 0;
/**
* 存款方法
* CC操作存款方法
* 如果money>0,CC线程等待,通知BB线程执行
* 如果money<=0,CC线程执行
*/
public synchronized void saveMoney() {
if (money > 0) {
try {
// money>0,线程等待
this.wait();
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 如果代码执行到这里说明money<=0
money += 5000;
System.out.println("CC存入了5000,余额" + money);
// 已经存入,通知BB执行
this.notify();
}
/**
* 取款方法
* BB线程执行
* 如果money>0,BB线程执行
* 如果money<=0,BB线程等待,通知CC线程执行
*/
public synchronized void takeMoney() {
// 判定是否有余额
if (money <= 0) {
// 卡里空了
try {
this.wait();
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 如果代码执行到这里,money>0
money -= 5000;
System.out.println("BB取出了5000,余额" + money);
// 已经取出,通知CC执行
this.notify();
}
}
class ThreadBB extends Thread {
private Card card;
public ThreadBB(Card card) {
super();
this.card = card;
}
@Override
public void run() {
for (int i = 1; i <= 12; i++) {
card.takeMoney();
}
}
}
class ThreadCC extends Thread {
private Card card;
public ThreadCC(Card card) {
super();
this.card = card;
}
@Override
public void run() {
for (int i = 1; i <= 12; i++) {
card.saveMoney();
}
}
}
三、线程池
3.1 概述
-
线程的创建和销毁会占用大量的资源
- 内存
- 处理器
-
可以限制线程创建的数量,复用创建的线程,把线程放入一个容器
3.2 线程池入门
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo02 {
public static void main(String[] args) throws InterruptedException {
// 创建线程池对象
ExecutorService es = Executors.newFixedThreadPool(2);
// 提交任务
es.submit(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "==>" + i);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
es.submit(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "==>" + i);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
es.submit(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "==>" + i);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
// 启用顺序关闭:运行结束的线程退出任务
es.shutdown();
System.out.println(es);
Thread.sleep(3000);
System.out.println(es);
}
}
3.3 线程池API
Executor
- 线程池的顶层接口
- 执行已提交的
Runnable任务的对象。
ExecutorService
- 声明的线程池对象类型
- 有子类ThreadPoolExecutor
boolean isTerminated()
如果关闭后所有任务都已完成,则返回 true。
void shutdown()
启动一次顺序关闭,执行以前提交的任务,但不接受新任务。
<T> Future<T> submit(Callable<T> task)
提交一个返回值的任务用于执行,返回一个表示任务结果的 Future。
Future<?> submit(Runnable task)
提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future。
Executors
Executors类为这些 Executor 提供了便捷的工厂方法。- 创建线程池对象
static ExecutorService newCachedThreadPool()
创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。
static ExecutorService newFixedThreadPool(int nThreads)
创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。
static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
static ExecutorService newSingleThreadExecutor()
创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。
Callable
- 返回结果并且可能抛出异常的任务。实现者定义了一个不带任何参数的叫做
call的方法。 Callable接口类似于Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。- 但是
Runnable不会返回结果,并且无法抛出经过检查的异常。
Future
-
Future表示异步计算的结果。 -
它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。
-
计算完成后只能使用
get方法来获取结果- 如有必要,计算完成前可以阻塞此方法。
3.4 多线程计算数组总和【掌握】
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class Demo06 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
/**
* 有一个二维数组,长度是4,使用多线程计算所有元素的总和【考题】
*/
// 创建数组
int[][] arr = {
{12,565,98,2,4,67,8,322,657,97,656},
{9,766,5,456,67,8,78,8,34,343,37632},
{42314,7,3,76,1356763,45,85,464},
{7633,24,876432,8,7434,5765,323,46,5}
};
// 创建线程池
ExecutorService es = Executors.newFixedThreadPool(4);
// 提交任务
Future<Integer> future00 = es.submit(new CalculatCallable(arr[0]));
Future<Integer> future01 = es.submit(new CalculatCallable(arr[1]));
Future<Integer> future02 = es.submit(new CalculatCallable(arr[2]));
Future<Integer> future03 = es.submit(new CalculatCallable(arr[3]));
// 启动顺序关闭
es.shutdown();
// 输出计算结果
System.out.println(future00.get() + future01.get() + future02.get() + future03.get());
System.out.println(39406+2488+1399757+897670);
}
}
/**
* 创建任务线程类
* 计算指定数组中所有元素的总和
*/
class CalculatCallable implements Callable<Integer>{
private int[] arr;
public CalculatCallable(int[] arr) {
super();
this.arr = arr;
}
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i < arr.length; i++) {
sum += arr[i];
}
return sum;
}
}
四、Lock
4.1 概述
-
Lock提供了比使用synchronized方法和语句更广泛的锁定操作。- 能实现synchronized的所有参数
- 更加的灵活、高效
4.2 核心方法
lock
锁定
unLock
解锁,必须执行,放在finally代码块中执行
4.3 ReentrantLock
-
一个可重入的互斥锁
Lock,它具有与使用synchronized方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。 -
可重入
- 可以多次执行
-
互斥
- A线程执行的时候B线程阻塞
- B线程执行的时候A线程阻塞
-
卖票
import java.util.concurrent.locks.ReentrantLock;
public class Demo01 {
public static void main(String[] args) {
/**
* 火车站四个窗口,共同卖出100张车票,用多线程实现。
*/
// 创建线程
TicketThread t01 = new TicketThread();
TicketThread t02 = new TicketThread();
TicketThread t03 = new TicketThread();
TicketThread t04 = new TicketThread();
// 设置名字
t01.setName("一号窗口");
t02.setName("22号窗口");
t03.setName("叁号窗口");
t04.setName("④号窗口");
// 启动线程
t01.start();
t02.start();
t03.start();
t04.start();
}
}
class TicketThread extends Thread {
private static int ticket = 100;
// 创建互斥锁
private static ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
// 上锁
lock.lock();
try {
if (ticket <= 0) {
break;
}
ticket--;
System.out.println(Thread.currentThread().getName() + "卖出了第" + (100 - ticket) + "张票,还剩下" + ticket);
} finally {
// 解锁
lock.unlock();
}
}
}
}
- 数组
import java.util.Arrays;
import java.util.concurrent.locks.ReentrantLock;
public class Demo02 {
public static void main(String[] args) throws InterruptedException {
/**
* 创建类
* 测试类
* 数组资源类
* 数组
* 索引
* 线程A
* 存入Hello
* 线程B
* 存入World
*/
// 创建数组和索引
ArrSrc arrSrc = new ArrSrc();
// 创建线程A和B
ThreadA a = new ThreadA(arrSrc);
ThreadB b = new ThreadB(arrSrc);
// 启动线程修改数组
a.start();
b.start();
// main线程休眠,等待线程a和b执行结束之后再看修改之后的结果
Thread.sleep(10);
// 输出修改之后的数组
System.out.println(arrSrc);
}
}
/**
* 数组资源
*/
class ArrSrc {
String[] arr = new String[5];
int index = 0;
private static ReentrantLock lock = new ReentrantLock();
/**
* 修改数组的方法
* 修改数组数据
* 索引自增
* @param str
*/
public void modifyArr(String str) {
lock.lock();
try {
arr[index] = str;
index++;
} finally {
lock.unlock();
}
}
@Override
public String toString() {
return "ArrSrc [arr=" + Arrays.toString(arr) + ", index=" + index + "]";
}
}
class ThreadA extends Thread {
private ArrSrc arrSrc;
public ThreadA(ArrSrc arrSrc) {
super();
this.arrSrc = arrSrc;
}
@Override
public void run() {
arrSrc.modifyArr("Hello");
}
}
class ThreadB extends Thread {
private ArrSrc arrSrc;
public ThreadB(ArrSrc arrSrc) {
super();
this.arrSrc = arrSrc;
}
@Override
public void run() {
arrSrc.modifyArr("World");
}
}
4.4 读写锁
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
public class Demo03 {
public static void main(String[] args) {
MyClass mc = new MyClass();
long startTime = System.currentTimeMillis();
// 创建线程池,容量20【18读 + 2写】
ExecutorService es = Executors.newFixedThreadPool(20);
// 提交任务:读取数据*18
for (int i = 0; i < 18; i++) {
es.submit(new Runnable() {
@Override
public void run() {
mc.getValue();
}
});
}
// 提交任务:写入数据*2
for (int i = 0; i < 2; i++) {
es.submit(new Runnable() {
@Override
public void run() {
mc.setValue("Hello");
}
});
}
es.shutdown();
// 阻塞:判定线程池中任务是否全部完成
while (!es.isTerminated()) {}
long endTime = System.currentTimeMillis();
System.out.println(endTime - startTime);
}
}
class MyClass {
// 创建互斥锁
private ReentrantLock lock = new ReentrantLock();
// 创建读写锁
private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
// 获取读锁
ReadLock readLock = readWriteLock.readLock();
// 获取写锁
WriteLock writeLock = readWriteLock.writeLock();
private String value;
/**
* 读取数据
* @return
*/
public String getValue() {
// lock.lock();
readLock.lock();
try {
Thread.sleep(1000);
return value;
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// lock.unlock();
readLock.unlock();
}
return value;
}
/**
* 写入数据
* @param value
*/
public void setValue(String value) {
//lock.lock();
writeLock.lock();
try {
this.value = value;
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//lock.unlock();
writeLock.unlock();
}
}
}