JUC版生产者与消费者的问题
Condition接口的生产者消费者问题:
await()跟signal()方法能够精准的通知,能够实现线程的执行顺序,await方法应该写在while循环里边,防止并发带来的线程不安全.
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class A {
public static void main(String[] args) {
Date date = new Date();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
date.printA();
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
date.printB();
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
date.printC();
}
}, "C").start();
}
}
//资源类 属性加方法
class Date {
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private int count = 1; //定义一个变量
public void printA() {
lock.lock();
try {
//业务逻辑代码
if (count != 1) {
//等待
condition1.await();
}
//如果count等于一就是输入并且通知监听器二执行
System.out.println(Thread.currentThread().getName() + "AAAAAA");
count = 2;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB() {
lock.lock();
try {
//业务逻辑代码
if (count != 2) {
condition2.await();
}
System.out.println(Thread.currentThread().getName() + "BBBBBBB");
count = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC() {
lock.lock();
try {
//业务逻辑代码
if (count != 3) {
condition3.await();
}
System.out.println(Thread.currentThread().getName() + "CCCCCCC");
count = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
8锁现象
如何判断锁的是谁?锁到底锁的是谁!
锁只会锁两个东西:对象, class
测试一:
package Lock8;
import java.util.concurrent.TimeUnit;
/**
* 1.标准情况下,两个线程先发短信再打电话
* 2.sendSms延迟四秒,两个线程还是先发短信再打电话 是因为有锁的存在
*/
class Test{
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(()->{
phone.sendSms();
},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
phone.call();
},"B").start();
}
}
public class Phone {
//synchronized锁的对象是方法的调用者!
//两个方法用的是同一把锁
public synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("手机发信息");
}
public synchronized void call(){
System.out.println("手机打电话");
}
}
测试二:
package Lock8;
import java.util.concurrent.TimeUnit;
//增加了一个普通方法 hello 结果是先执行普通方法,再执行发信息的方法,普通方法没有锁不是同步方法,不受锁的影响
//两个对象,锁的对象不一样,因为发短信方法里面有四秒延迟,所以先执行了打电话方法
public class Test2 {
public static void main(String[] args) throws InterruptedException {
Phone2 phone1 = new Phone2();
Phone2 phone2 = new Phone2();
new Thread(()->{
phone1.sendSms();
},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
phone2.call();
},"B").start();
}
}
class Phone2 {
//synchronized锁的对象是方法的调用者!
//两个方法用的是同一把锁
public synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("手机发信息");
}
public synchronized void call(){
System.out.println("手机打电话");
}
public void hello(){
System.out.println("hello");
}
}
测试三:
package Lock8;
import java.util.concurrent.TimeUnit;
//增加两个静态的同步方法,static静态方法只会执行一次,就算是不同对象,他的锁只会是同一个
public class Test3 {
public static void main(String[] args) throws InterruptedException {
Phone3 phone1 = new Phone3();
Phone3 phone2 = new Phone3();
new Thread(()->{
phone1.sendSms();
},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
phone2.call();
},"B").start();
}
}
//Phone3唯一的一个class对象
class Phone3 {
//synchronized锁的对象是方法的调用者!
//static类一加载他就有了 class 模板 锁的是class
public static synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("手机发信息");
}
public static synchronized void call(){
System.out.println("手机打电话");
}
public void hello(){
System.out.println("hello");
}
}
测试四:
package Lock8;
import java.util.concurrent.TimeUnit;
//发短信方法是静态的,打电话方法是非静态的 就会先执行打电话的方法,
// 因为static锁的是class对象 非静态方法锁的是对象,就算两个是同一个phone对象也不是同一把锁
//如果不是同一个对对象结果还是一样
public class Test4 {
public static void main(String[] args) throws InterruptedException {
Phone4 phone = new Phone4();
Phone4 phone2 = new Phone4();
new Thread(()->{
phone.sendSms();
},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
phone2.call();
},"B").start();
}
}
class Phone4 {
//synchronized锁的对象是方法的调用者!
//两个方法用的是同一把锁
public static synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("手机发信息");
}
public synchronized void call(){
System.out.println("手机打电话");
}
}
小结:
new this 具体的一个对象
class 唯一的一个模板
集合类不安全
ArrayList集合多线程下线程不安全,会抛出
//java.util.ConcurrentModificationException 并发修改异常
//java.util.ConcurrentModificationException 并发修改异常
public class ListTest {
public static void main(String[] args) {
//并发下ArrayList是不安全的
//CopyOnWriteArrayList 写入时复制
//在写入的时候复制,避免数据覆盖
//CopyOnWriteArrayList 比vector的区别:vector使用的是synchronized 效率低 而CopyOnWriteArrayList是利用lock锁机制 效率高
List<String> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
解决:
1.可是使用vector的实例 new Vector();
2.使用collections集合工具类:
List<String> list = Collections.synchronizedList(new ArrayList<>());
3.使用CopyOnWriteArrayList<>()
List<String> list = new CopyOnWriteArrayList<>();//CopyOnWriteArrayList 写入时复制
//在写入的时候复制,避免数据覆盖
//CopyOnWriteArrayList 比vector的区别:vector使用的是synchronized 效率低 而CopyOnWriteArrayList是利用lock锁机制 效率高
Callable接口
Callable接口能够有返回值,并且能够抛出异常,而Runnable接口不能,这里有个重要的类,Runnable接口的实现类,FutureTask,他的构造方法是public FutureTask(Callable<V> callable),这里需要的是一个Callable接口的实例
/**
* Callable接口
* 我们都知道new Thread().start()里边是需要一个Runnable接口的
* 但是Runnable接口下面的一个实现类,FutureTask,他的构造方法为 public FutureTask(Callable<V> callable)
* 所以这样就能实现Runnable接口跟Callable接口挂钩上关系
* 然后直接 new Thread(new FutureTask(new Callable())).start()便能实现线程的启动,
*/
class MyThread implements Callable {
@Override
public Integer call() throws Exception {
System.out.println("call方法");
return 1024;
}
}
class CallableTest{
public static void main(String[] args) {
//创建Callable接口的实现类
MyThread thread=new MyThread();
//new 一个FutureTask 构造方法传一个Callable接口的实例
FutureTask futureTask = new FutureTask<>(thread);
//线程启动
new Thread(futureTask).start();
}
}
常用辅助类
1.CountDownLatch
一个减法计数器
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
//计时 总数是六
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 0; i < 6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"go gout");
//减一操作
countDownLatch.countDown();//-1
},String.valueOf(i)).start();
}
//等待计数器归零,然后在向下执行
countDownLatch.await();
System.out.println("关门");
}
}
这里的例子就是等待六条线程都跑完之后再关门
原理:有两个方法: countDownLatch.countDown();//-1 ;countDownLatch.await();
每次有线程调用countDown()方法数量就会减一,假设计数器变为零,countDownLatch.await()机会呗唤醒,继续执行;
CyclicBarrier 加法计数器
public class CyclicBarrierDemo {
public static void main(String[] args) {
/*
集齐七颗龙珠召唤神龙
*/
//召唤龙珠的线程
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
System.out.println("召唤神龙成功");
});
for (int i = 0; i < 7; i++) {
//lambda表达式不能拿到变量i 要把i赋值到一个常量当中
final int temp = i;
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "收集了" + temp + "颗龙珠");
try {
cyclicBarrier.await();//等待七个线程变为七才能执行上面的召唤神龙成功语句
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
Semaphore 信号量
模拟停车位案例
public class SemaphoreDemo {
public static void main(String[] args) throws InterruptedException {
//线程数量 模拟停车位 这里有三个停车位
Semaphore semaphore = new Semaphore(3);
//现在模拟有六台车 六个线程
for (int i = 1; i <= 6; i++) {
new Thread(()->{
//获得车位
//acquire() 获得
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"占用了车位");
//模拟停车时间为四秒
TimeUnit.SECONDS.sleep(4);
System.out.println(Thread.currentThread().getName()+"离开了车位");
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
//release 释放
semaphore.release();
}
},"车辆"+String.valueOf(i)).start();
}
}
}
原理:
semaphore.acquire() : 获得,假设已经满了,就等待,等待释放为止
demaphore.release() : 释放,会将当前的信号释放 +1,然后唤醒等待的线程
作用: 多个线程共享资源互斥的使用!,并发限流
读写锁ReadWriteLock
案例:
package rw;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class MyReadWriteLock {
public static void main(String[] args) {
MyCache myCache = new MyCache();
//写操作
for (int i = 0; i < 5; i++) {
final int temp=i;
new Thread(()->{
myCache.put(temp+"",temp+"");
},String.valueOf(i)).start();
}
//读取操作
for (int i = 0; i < 5; i++) {
final int temp =i;
new Thread(()->{
myCache.get(temp+"");
},String.valueOf(i)).start();
}
}
}
//自定义一个缓存
class MyCache {
private volatile Map<String, Object> map = new HashMap<>();
//读写锁 更加细粒度的控制
private ReentrantReadWriteLock ReadWriteLock = new ReentrantReadWriteLock();
public void put(String key, Object value) {
ReadWriteLock.writeLock().lock();//写的操作加锁
try {
System.out.println(Thread.currentThread().getName()+"写入"+key);
map.put(key, value);
System.out.println(Thread.currentThread().getName()+"写入完成");
} catch (Exception e) {
e.printStackTrace();
}finally{
ReadWriteLock.writeLock().unlock();//释放锁
}
}
public void get(String key) {
ReadWriteLock.readLock().lock();//读操作加锁
try {
System.out.println(Thread.currentThread().getName()+"读取"+key);
map.get(key);
System.out.println(Thread.currentThread().getName()+"读取完成");
} catch (Exception e) {
e.printStackTrace();
}finally{
ReadWriteLock.readLock().unlock();//读操作释放锁
}
}
}
加了读写锁之后,线程安全,一个线程操作写的操作完成之后另外一个线程再执行!
没有加读写锁会造成线程插队现象
阻塞队列BlockingQueue
学会使用队列:跟list一样,添加,移除
四组Api:
遵循FIFO原则,先进先出,先进去的元素会先出来
方式 | 抛出异常 | 有返回值,不抛出异常 | 阻塞等待 | 超时等待 |
添加 | add | offer | put | offer(,,) |
移除 | remove | poll | take | poll(,) |
检测队首元素 | element | peek |
1.抛出异常
public class BlockingQueueDemo1 {
/*
抛出异常
*/
public static void main(String[] args) {
test1();
}
public static void test1(){
//队列的元素是3
ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
System.out.println(blockingQueue.add("c"));
//添加第四个元素的时候会报错 Exception in thread "main" java.lang.IllegalStateException: Queue full 队列已满
//System.out.println(blockingQueue.add("d"));
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
//abc已经取出来了,如果再取的话会报错,Exception in thread "main" java.util.NoSuchElementException 队列是空的
//System.out.println(blockingQueue.remove());
}
}
2.有返回值,但不会抛出异常
/*
有返回值 但不会抛出异常
*/
public static void test2() {
//队列得元素最多只能是三个
ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
//offer方法跟add方法一样,返回值是boolean类型,但是add方法添加失败的话会抛出异常,offer方法只会返回一个false
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
//返回一个布尔值,false,不会抛出异常
System.out.println(blockingQueue.offer("d"));
System.out.println(blockingQueue.peek());//检测队首元素
System.out.println("======================");
//同理,poll方法跟remove方法也一样,但是poll方法在队列空的时候不会抛出异常,只会返回一个null,而remove方法会抛出一个队列为空的异常
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
}
3.阻塞等待,一直阻塞,一直等待
/*
队列满了不会返回false,等待队列不为满的时候添加进去 一直阻塞
*/
public static void test3(){
ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
/*
一直阻塞
*/
try {
blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c");
//我们设置队列的元素只有三个,这里添加第四个,队列已经满了,没有位置了,会一直阻塞,一直等待
// blockingQueue.put("d");
blockingQueue.take();
blockingQueue.take();
blockingQueue.take();
//这里再取的话队列已经没有元素了,会一直阻塞等待队列有原色
blockingQueue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
4.超时等待,不会一直阻塞
/*
队列满了不会返回false,等待队列不为满的时候添加进去 等待超时
*/
public static void test4() throws InterruptedException {
ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
// public boolean offer(E e, long timeout, TimeUnit unit)
//添加不进去会阻塞两秒,两秒后如果还没添加成功就退出
System.out.println(blockingQueue.offer("d",2, TimeUnit.SECONDS));
System.out.println("==================");
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll(2, TimeUnit.SECONDS));
}
同步队列SynchronousQueue : BlockingQueue的一个实现类
同步队列时没有容量的
进去一个元素,必须等待其被取出来才能往里面放其他的元素
队列中同一时间只有一个元素
方法:添加 put ; 移除:take
/*
同步队列
和其他的BlockingQueue不一样, SynchronousQueue 不存储元素
put了一个元素,必须从里面先take出来,否则不能再put值进去
*/
public class BlockingQueueDemoDemo {
public static void main(String[] args) throws InterruptedException {
SynchronousQueue<String> synchronous = new SynchronousQueue<>();
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+"put 1");
synchronous.put("1");
System.out.println(Thread.currentThread().getName()+"put 2");
synchronous.put("2");
System.out.println(Thread.currentThread().getName()+"put 3");
synchronous.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T1").start();
new Thread(()-> {
try {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+synchronous.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+synchronous.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+synchronous.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T2").start();
}
}