1.生产者消费者模型
技术点:
多线程模型,安全锁机制,等待与唤醒
男人挣钱,女人花钱天经地义
有钱就花,没钱限制借钱消费
男人挣钱—生产者负责生产
女人花钱—消费者负责消费
有钱就花—我们需要准备一个仓库存钱,消费者看库存,库存有钱才能消费
库存没钱—等待消费
库存满了—生产者停止生产,等待消费者消费后,有库存空间才挣
代码案例
/**
多线程 生产与消费模型:
分析:
1,创建生产者类继承Thread,只负责生产
2. 创建消费者类继承Thread,只负责消费
3. 两个线程共同操作仓库类Store
4. 加入安全锁及等待与唤醒机制
问题1:生产两个动作,消费也是两个动作,当你打印生产,还没加库存;就已经消费了,库存值不对
处理:加锁 ,解决生产与消费的数据混乱
问题2: 仓满和仓空的限制
处理: 限制仓满,生产者等待(wait);仓空,消费者等待(wait)
细节:
1. 等待时,会将资源交出去
2. 锁对象与等待唤醒的对象是同一个
扩展: 多个生产与多个消费情况
判断中改为while
*
*/
public class Test1 {
public static void main(String[] args) {
Store store = new Store();
new Producter(store).start();
new Customer(store).start();
//多个生产者和消费者线程
new Producter(store).start();
new Customer(store).start();
}
}
//-----生产者-----
public class Producter extends Thread {
private Store store;
public Producter(Store store){
//库存要存进来
this.store = store;
}
@Override
public void run() {
while(true) {
try {
store.push();
} catch (InterruptedException e) {
e.printStackTrace();
} //生产者负责生产
}
}
}
//----消费者----
public class Customer extends Thread {
private Store store;
public Customer(Store store) {
this.store = store;
}
@Override
public void run() {
while(true) {
try {
store.pop();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} //消费者负责消费
}
}
}
//---库存---
public class Store {
private int num; //单位万
public void push() throws InterruptedException {
//库存生产
synchronized (this) {
//生产20件货,就满了,停止生产
while(num>=20) {
this.wait();
}
num++;
System.out.println("生产者已经生产了第"+num+"件货");
this.notify(); //唤醒等待的线程
}
}
public void pop() throws InterruptedException {
//库存消费
synchronized (this) {
//
while(num<=0) {
this.wait(); //等待消费,等待时,相当于把自身资源交出去了
}
System.out.println("消费者已经消费了第"+num+"件货");
num--;
this.notify(); //唤醒等待的线程
}
}
}
线程状态(阻塞)
-
多线程高级
2.1 线程池(重点)概念:可以认为是线程对象的容器,预先创建多个线程对象,然后根据这些对象可以实现复用
好处:减少创建和销毁线程的数目,提升性能
复用机制: 用完了线程对象后,重新回收到线程池中
class Task implements Runnable{
@Override
public void run() {
for(int i=1;i<=10;i++) {
System.out.println(Thread.currentThread().getName()+"===>"+i);
}
};
}
public class Test1 {
public static void main(String[] args) {
//创建一个单个线程的线程池对象
//ExecutorService es = Executors.newSingleThreadExecutor();
//创建一个带缓冲区的线程池对象,灵活控制线程对象,有多少任务,我就创建多少个线程对象
//ExecutorService es = Executors.newCachedThreadPool();
//创建固定个数的线程池对象,如果任务过多,则用回收线程对象去处理多余任务
ExecutorService es = Executors.newFixedThreadPool(2);
Task task = new Task();
es.submit(task); //类似,线程.start()
es.submit(task);
es.submit(task); //谁先用完并回收,就谁去执行新任务
es.shutdown(); //关闭线程池
}
}
2.2 Callable接口
Callable接口: 和Runnable类似,也是处理任务的接口
区别: Callable是带返回值的接口,可以将线程中处理完的数据返回
Future接口,该对象接收submit的返回值,可以求出call返回值
//案例: 使用两个线程计算1~50,51~100的和,并进行汇总
public class Test2 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService es = Executors.newFixedThreadPool(2);
Future<Integer> future = es.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for(int i=1;i<=50;i++) {
sum += i;
}
return sum;
}
});
Future<Integer> future2 = es.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for(int i=51;i<=100;i++) {
sum += i;
}
return sum;
}
});
//get方法类似线程基础的join,在主线程中阻塞
int sum = future.get()+future2.get();
System.out.println("两个线程的和:"+sum);
}
}
2.3 重入锁
重入锁:Lock接口 实现类:ReentrantLock
与synchronized类似,用于加锁的
提供了两个方法: lock()-获取锁 unlock()-释放锁
class MyList{
String[] ss = {
"A","B","",""};
int index = 2;
Lock lock = new ReentrantLock();
public void add(String value) {
//有多个线程可以调用add方法
lock.lock(); //加锁
try {
ss[index] = value;
index++;
} finally {
lock.unlock();
}
}
}
public class Test1 {
public static void main(String[] args) throws InterruptedException {
MyList list = new MyList();
Thread th1 = new Thread(new Runnable() {
@Override
public void run() {
list.add("hello");
}
});
th1.start();
Thread th2 = new Thread(new Runnable() {
@Override
public void run() {
list.add("world");
}
});
th2.start();
th1.join(); th2.join();
System.out.println(Arrays.toString(list.ss));
}
}
2.4 读写锁
读写锁:一般用在读远远高于写的情况,可以提升线程执行的性能
读写锁,在读与读之间是不互斥,不阻塞的,所以性能会提升
class MyClass{
//加入读写锁
ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
ReadLock rl = readWriteLock.readLock();
WriteLock wl = readWriteLock.writeLock();
Integer value;
public Integer getValue() {
try {
rl.lock();
return value;
} finally {
rl.unlock();
}
}
public void setValue(Integer value) {
try {
wl.lock();
this.value = value;
} finally {
wl.unlock();
}
}
}
public class Test2 {
public static void main(String[] args) {
MyClass myClass = new MyClass();
Runnable rt = new Runnable() {
@Override
public void run() {
myClass.getValue();
}
};
Runnable wt = new Runnable() {
@Override
public void run() {
myClass.setValue(1);
}
};
ExecutorService es = Executors.newFixedThreadPool(20);
long start = System.currentTimeMillis();
for(int i=0;i<18;i++) {
es.submit(rt); //18个线程用于读操作
}
for(int i=0;i<2;i++) {
es.submit(wt); //2个线程用于写操作
}
es.shutdown();
while(!es.isTerminated()){
} //阻塞,确保所有线程结束后,才往下走
System.out.println(System.currentTimeMillis()-start);
}
}
3.线程安全的集合
3.1 Collections提供的安全集合
Collections工具类中提供了针对List,Set,Map的安全集合
当然他们的安全机制性能方面有比较大的影响,一般我们会用后续并发安全集合
public class Test1 {
public static void main(String[] args) {
List<Integer> list = Collections.synchronizedList(new ArrayList<Integer>());
//多个线程进入add方法
list.add(2);
}
}
3.2 CopyOnWriteArrayList
CopyOnWriteArrayList: ArrayList的安全集合(读写并发)
内部进行了加锁,且性能方面比Collestions提供的安全集合更高
该安全集合具有读写分离的特性
读无锁,写有锁,读写之间不互斥,不阻塞,在内部拷贝了一份副本存数据
往往也是用在读多写上的情况
public class Test2 {
public static void main(String[] args) {
List<String> list = new CopyOnWriteArrayList<String>();
for(int i=0;i<5;i++) {
final int temp = i;
new Thread(new Runnable() {
@Override
public void run() {
//T1 T2 T3
//读写并发执行---执行效率很高
list.add(Thread.currentThread().getName()+"-->"+temp);
System.out.println(list); //读
}
}).start();
}
}
}
3.3 CopyOnWriteArraySet
//CopyOnWriteArraySet: 安全的set集合,底层实现通过CopyOnWriteArrayList
//该类也是读写分离的set集合类,该类具备存储唯一性
public class Test3 {
public static void main(String[] args) {
Set<String> set = new CopyOnWriteArraySet<String>();
set.add("苹果");
set.add("香蕉");
set.add("苹果");
System.out.println(set);
}
}