1 (1) 常用的数据结构是不安全的:
- ArrayList HashMap HashSet是非同步的
- 多个线程读写可能会抛出异常和错误
(2) 传统的vector,hashTable等同步集合性能过差
(3) 并发数据结构数据的添加和删除
- 阻塞式集合:当集合为空或者为满时,等待
- 非阻塞式集合:当集合为空或者为满时,不等待,返回null或者异常
(4) 所以高并发中,为了保证数据的一致性,此处应该用Collections中的方法
List list = Collections.synchronizedList(new ArrayList());
Set s = Collections.synchronizedSet(new HashSet());
Map m = Collections.synchronizedMap(new HashMap());
2 并发协作控制
(1) Thread/Excutor/Fork-join
-线程启动,运行,结束
- 线程之间缺少协作
(2) synchronized同步
- 限定只有一个线程才能进入关键区
- 简单粗暴,性能损失有点大
(3) Lock锁,也可以实现与synchronized一模一样的功能。
-且它可以实现更复杂的临界区结构,
-tryLock方法可以判断锁是否空闲
-允许分离读写的操作,多个读,一个写
-性能更好
(4) ReentrantLock类,可重入的互斥锁
(5) ReentrantLock类,可重入的读写锁
(6) lock和unLock函数
3 代码单例:
有家奶茶店,点单有时需要排队,假设想买奶茶的人如果看到需要排队,就决定不买
public class LockExample {
private static final ReentrantLock queueLock = new ReentrantLock(); //可重入锁
public static void main(String[] args) throws InterruptedException {
buyMilkTea();
}
public static void buyMilkTea() throws InterruptedException {
LockExample lockExample = new LockExample();
int STUDENTS_CNT = 10;
//创建一个学生线程组,这个线程组中有10个匿名线程
Thread[] students = new Thread[STUDENTS_CNT];
for (int i = 0; i < STUDENTS_CNT; i++) {
//分别启动这10个线程,去执行买奶茶这个动作
students[i] = new Thread(new Runnable() {
@Override
public void run() {
try {
long walkingTime = (long) (Math.random() * 1000);
Thread.sleep(walkingTime);
lockExample.tryToBuyMilkTea();
} catch(InterruptedException e) {
System.out.println(e.getMessage());
}
}
}
);
students[i].start();
}
for (int i = 0; i < STUDENTS_CNT; i++)
students[i].join();
}
}
public void tryToBuyMilkTea() throws InterruptedException {
//温馨提示:调用了tryLock之后一定要调用unlock方法
boolean flag = true;
while(flag)
{ //queueLock为可重入锁,如果当前线程进来试着加锁,如果资源被别人占用,直接会返回让你等待
if (queueLock.tryLock()) {
//queueLock.lock();
long thinkingTime = (long) (Math.random() * 500);
Thread.sleep(thinkingTime);
System.out.println(Thread.currentThread().getName() + ": 来一杯珍珠奶茶,不要珍珠");
//卖完奶茶之后要解锁离开,不能一直占用资源
flag = false;
queueLock.unlock();
} else {
//System.out.println(Thread.currentThread().getName() + ":" + queueLock.getQueueLength() + "人在排队");
System.out.println(Thread.currentThread().getName() + ": 再等等");
}
//如果flag等于true,睡眠一秒钟再去看一看资源是否已经被释放
if(flag)
{
Thread.sleep(1000);
}
}
}
可重入读写锁,假设奶茶店中有一名老板和多名员工,老板负责写订单,员工负责查看订单并且制作奶茶,老板写订单的时候员工不能查看订单,多个员工可以同时查看订单本,员工查看订单时老板不能新增订单.
public class LockExample {
private static final ReentrantReadWriteLock orderLock = new ReentrantReadWriteLock(); //可重入读写锁
public static void main(String[] args) throws InterruptedException {
handleOrder(); //需手动关闭
}
public static void handleOrder() throws InterruptedException {
LockExample lockExample = new LockExample();
//新建一个老板线程,并且启动此线程
Thread boss = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
lockExample.addOrder();
long waitingTime = (long) (Math.random() * 1000);
Thread.sleep(waitingTime);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
}
}
});
boss.start();
//新建3个员工线程
int workerCnt = 3;
Thread[] workers = new Thread[workerCnt];
for (int i = 0; i < workerCnt; i++)
{ //并且启动这三个线程
workers[i] = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
lockExample.viewOrder();
long workingTime = (long) (Math.random() * 5000);
Thread.sleep(workingTime);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
}
}
});
workers[i].start();
}
}
}
public void addOrder() throws InterruptedException {
//写订单之前,先锁住此资源
orderLock.writeLock().lock();
long writingTime = (long) (Math.random() * 1000);
Thread.sleep(writingTime);
System.out.println("老板新加一笔订单");
//释放锁
orderLock.writeLock().unlock();
}
public void viewOrder() throws InterruptedException {
//查看订单之前所住此资源
orderLock.readLock().lock();
long readingTime = (long) (Math.random() * 500);
Thread.sleep(readingTime);
System.out.println(Thread.currentThread().getName() + ": 查看订单本");
orderLock.readLock().unlock();
//查看完成之后释放锁
}
4 semaphore
(1)信号量:本质上是一个计数器
计数器大于0可以使用,等于0不能使用
可以设置多个并发量,例如限制10个访问
(2)seamphore
-acquire 获取 信号量减1
- release释放 信号量加一
(3) 比lock更进一步,可以同时访问多个关键区
代码案例:停车场
public class SemaphoreExample {
//创建5个信号源,表示只有5辆车可以执行停车这个动作,如果placeSemaphore等于0,则线程等待
private final Semaphore placeSemaphore = new Semaphore(5);
/**
* 现有一地下车库,共有车位5个,由10辆车需要停放,每次停放时,去申请信号量
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
int tryToParkCnt = 10;
SemaphoreExample semaphoreExample = new SemaphoreExample();
//新建一个线程组,创建10个匿名线程去执行停车任务
Thread[] parkers = new Thread[tryToParkCnt];
for (int i = 0; i < tryToParkCnt; i++) {
parkers[i] = new Thread(new Runnable() {
@Override
public void run() {
try {
long randomTime = (long) (Math.random() * 1000);
Thread.sleep(randomTime);
if (semaphoreExample.parking()) {
long parkingTime = (long) (Math.random() * 1200);
Thread.sleep(parkingTime);
semaphoreExample.leaving();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
parkers[i].start();
}
for (int i = 0; i < tryToParkCnt; i++) {
parkers[i].join();
}
}
public boolean parking() throws InterruptedException {
//如果申请到信号量表示停车成功,停车成功信号量减去1
if (placeSemaphore.tryAcquire()) {
System.out.println(Thread.currentThread().getName() + ": 停车成功");
return true;
} else {
System.out.println(Thread.currentThread().getName() + ": 没有空位");
return false;
}
}
public void leaving() throws InterruptedException {
//释放信号量,使信号量加1
placeSemaphore.release();
System.out.println(Thread.currentThread().getName() + ": 开走");
}
}
5 Latch
(1)等待锁,是一个同步辅助类
(2)用来同步执行任务的一个或者多个线程
(3)不是用来保护临界区或者共享资源的
-countDown()计数减1
- await()等待latch变成0
(4) 代码案例
public class CountDownLatchExample {
/**
* 设想百米赛跑比赛 发令枪发出信号后选手开始跑,全部选手跑到终点后比赛结束
*
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
int runnerCnt = 10;
CountDownLatch startSignal = new CountDownLatch(1);
CountDownLatch doneSignal = new CountDownLatch(runnerCnt);
for (int i = 0; i < runnerCnt; ++i) // create and start threads
new Thread(new Worker(startSignal, doneSignal)).start();
System.out.println("准备工作...");
System.out.println("准备工作就绪");
startSignal.countDown(); // let all threads proceed
System.out.println("比赛开始");
doneSignal.await(); // wait for all to finish
System.out.println("比赛结束");
}
static class Worker implements Runnable {
private final CountDownLatch startSignal;
private final CountDownLatch doneSignal;
Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
this.startSignal = startSignal;
this.doneSignal = doneSignal;
}
public void run() {
try {
startSignal.await();
doWork();
doneSignal.countDown();
} catch (InterruptedException ex) {
} // return;
}
void doWork() {
System.out.println(Thread.currentThread().getName() + ": 跑完全程");
}
}
6 线程之间进行数据交换
(1) 允许并发线程中相互交换消息
(2) 允许在两个线程中定义同步点,当两个线程都达到同步点,他们交换数据结构
(3) Exchanger
- exchanger(),线程双发相互交互数据
- 交互数据是双向的
(4) 代码案例:
import java.util.Scanner;
import java.util.concurrent.Exchanger;
public class ExchangerExample {
/**
* 本例通过Exchanger实现学生成绩查询,简单线程间数据的交换
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
Exchanger<String> exchanger = new Exchanger<String>();
BackgroundWorker worker = new BackgroundWorker(exchanger);
new Thread(worker).start();
Scanner scanner = new Scanner(System.in);
while(true) {
System.out.println("输入要查询的属性学生姓名:");
String input = scanner.nextLine().trim();
exchanger.exchange(input); //把用户输入传递给线程
String value = exchanger.exchange(null); //拿到线程反馈结果
if ("exit".equals(value)) {
break;
}
System.out.println("查询结果:" + value);
}
scanner.close();
}
}
class BackgroundWorker implements Runnable {
final Exchanger<String> exchanger;
BackgroundWorker(Exchanger<String> exchanger) {
this.exchanger = exchanger;
}
@Override
public void run() {
while (true) {
try {
String item = exchanger.exchange(null);
switch (item) {
case "zhangsan":
exchanger.exchange("90");
break;
case "lisi":
exchanger.exchange("80");
break;
case "wangwu":
exchanger.exchange("70");
break;
case "exit":
exchanger.exchange("exit");
return;
default:
exchanger.exchange("查无此人");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}