JUC
JUC:java.util.concurrent
有两个子包
java.util.concurrent.atomic
java.util.concurrent.locks
需要掌握什么是:进程/线程 并发/并行
回顾:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/*
* 使用高内聚低耦合的方法创建卖票实例(三个售票员,三十张票)
* :线程/操作/资源类(资源类包含操作(票有sale方法))
* 线程并不是.start之后就开始调用
*
* */
class Ticket{
// 初始票数
private int num = 30;
Lock lock = new ReentrantLock();
public void sale(){
lock.lock();
try{
if (num>0){
System.out.println(Thread.currentThread().getName()+"卖"+num-- +"票");
Thread.sleep(10);
}else {
System.out.println("没票了");
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
public class SaleTicketDemo01 {
public static void main(String[] args) {
Ticket ticket = new Ticket();
// Thread t1 = new Thread();企业级怎么写?
// Thread t2 = new Thread();
// Thread t3 = new Thread();
//Thread(Runnable target, String name)分配一个新的 Thread对象。
//匿名内部类
// new Thread(new Runnable() {
// @Override
// public void run() {
// for (int i = 0; i < 10; i++) {
// ticket.sale();
// }
// }
// },"售票员1").start();
// new Thread(new Runnable() {
// @Override
// public void run() {
// for (int i = 0; i < 10; i++) {
// ticket.sale();
// }
// }
// },"售票员3").start();
// new Thread(new Runnable() {
// @Override
// public void run() {
// for (int i = 0; i < 10; i++) {
// ticket.sale();
// }
// }
// },"售票员2").start();
new Thread(() -> {for (int i = 0; i < 10; i++)ticket.sale();},"售票员1").start();
new Thread(()->{for (int i = 0; i < 10; i++)ticket.sale();},"售票员2").start();
new Thread(()->{for (int i = 0; i < 10; i++)ticket.sale();},"售票员3").start();
}
Lambda写法:
前提要求
必须要是函数式接口(@FunctionalInterface表示函数式接口,只能声明一个方法,但是可以声明多个静态方法或default方法)
写法:复制小括号,写箭头,写大括号
例如:
package org.yyx.lambda;
/*
* @auther yyx
* Lambda表达式写法:(函数式接口、复制小括号,写箭头,写大括号)
* @FunctionalInterface表示函数式接口,只能声明一个方法,但是可以声明多个静态方法或default方法
* */
@FunctionalInterface
interface Yyx{
//public void write();
public int add(int x,int y);
default void mul(int x,int y){
System.out.println(x*y);
}
static int div(int x,int y){
return x/y;
}
}
public class LambdaExpressDemo {
public static void main(String[] args) {
/*
Yyx yyx = new Yyx() {
@Override
public void write() {
System.out.println("Yyx匿名内部类输出");
}
};
yyx.write();
*/
// Lambda表达式
/*Yyx yyx = () -> {
System.out.println("Lambda表达式输出");
};
yyx.write();
*/
// 含参数的Lambda表达式
Yyx yyx = (int x,int y) -> {
System.out.println("Lambda表达式中add");
return x+y;
};
System.out.println(yyx.add(7, 5));
yyx.mul(7,6);
System.out.println(Yyx.div(6, 2));
}
}
集合类不安全
以ArrayList为例
/*
* 故障现象(报错):java.util.ConcurrentModificationException表示高并发错误
* 导致原因:多线程并发争抢数据没有加锁
* 解决方法:
* 1. new Vector<>();线程安全
* 2. Collections.synchronizedList(new ArrayList<>());工具类上锁
* 3. CopyOnWriteArrayList<>();
* 优化建议:
* */
public class NotSateDemo {
// 以ArrayList为例
public static void main(String[] args) {
List<String> list = new CopyOnWriteArrayList<>(); //Collections.synchronizedList(new ArrayList<>()); //new Vector<>();//new ArrayList<>();
for (int i = 0; i < 3; i++) {
new Thread(()->{list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
写时复制
CopyOnWriteArrayList
即写时复制的容器,往一个容器添加元素时,不直接往当前Object[]添加,而是先将容器进行copy,复制出一个新的Object【】newElements,然后新的Object[ ] newElements添加元素,之后再将原容器的引用指向新的容器 setArray(newElements),这样做的好处时可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素,所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
public boolean add(E e) {
synchronized(this.lock) {
Object[] es = this.getArray();
int len = es.length;
es = Arrays.copyOf(es, len + 1);
es[len] = e;
this.setArray(es);
return true;
}
}
注意:ArrayList、HashSet、HashMap都是线程不安全的,解决方法都可以用
CopyOnWriteArrayList、CopyOnWriteHashSet、CopyOnWriteHashMap
锁机制
思考:
/*
* 1.标准方法,先输出A还是B
* 2.暂停四秒在A方法中,先输出A还是B
* 3.新增一个输出C的方法,请问先输出A还是C
* 4.两个打印机,先输出A还是B
* 5.两个静态同步方法,同一台打印机,先输出A还是B
* 6.两个静态同步方法,两台打印机,先输出A还是B
* 7.一个静态同步方法,一个普通同步方法,同一台打印机,先输出A还是B
* 8.一个静态同步方法,一个普通同步方法,两台打印机,先输出A还是B
*
*
* */
- 1.标准方法,先输出A还是B 随机
- 调用了资源类中的任一同步方法,synchronized锁的并不是一个方法,而是整个资源类对象,这时不能调用其它的同步方法
- 2.暂停四秒在A方法中,先输出A还是B A
- 3.新增一个输出C的普通方法,请问先输出A还是C(A还有四秒暂停) C
- 加普通方法与资源类无关。
- 4.两个打印机,先输出A还是B
- 先输出B(因为A sleep,如果不sleep,随机)
- 5.两个静态同步方法,同一台打印机,先输出A还是B(静态同步锁)
- static锁的是全局锁(锁了整个类Sout Class对象) A
- 6.两个静态同步方法,两台打印机,先输出A还是B
- static锁的是全局锁(锁了整个类Sout Class对象) A
- 7.一个静态同步方法,一个普通同步方法,同一台打印机,先输出A还是B AB(锁的是全局Sout和锁的是sout,锁的是两个东西)
- 8.一个静态同步方法,一个普通同步方法,两台打印机,先输出A还是B AB
线程通信
记得防止虚假唤醒,两种方式都是对的,一种是旧版本,一种是JUC。
package org.yyx.comunication;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/*
* 资源类(附带该类的操作方法)
* 线程之间通信
* 一个线程加一,一个线程减一,来回十次
* 多线程(只有两个线程情况下,只用if就可以解决,但是多线程情况,容易造成虚假唤醒,所以,wait用while循环)
*
* 1.高内聚低耦合情况下(线程类操作资源类)
* 2.判断、操作、通知
* 3.防止虚假唤醒(多个线程,wait操作还是if)
*
* */
class Number{
private int num = 0;
// 用JUC来写
private Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void add(){
lock.lock();
try {
// 判断
while (num != 0) {
condition.await();
}
// 干活
num++;
System.out.println(Thread.currentThread().getName()+" "+num);
// 通知
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void de(){
lock.lock();
try {
// 判断
while (num == 0) {
condition.await();
}
// 干活
num--;
System.out.println(Thread.currentThread().getName()+" "+num);
// 通知
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
/*public synchronized void add(){
// 判断
while (num != 0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 操作
num++;
System.out.println(Thread.currentThread().getName()+" "+num);
// 通知
this.notifyAll();
}
public synchronized void de(){
// 判断
while (num == 0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 操作
num--;
System.out.println(Thread.currentThread().getName()+" "+num);
// 通知
this.notifyAll();
}
*/
}
public class ProdConsumerDemo {
public static void main(String[] args) {
// 创建资源对象
Number number = new Number();
new Thread(()->{ for (int i = 0; i < 10; i++) number.add();},"A").start();
new Thread(()->{ for (int i = 0; i < 10; i++) number.de();},"B").start();
new Thread(()->{ for (int i = 0; i < 10; i++) number.add();},"C").start();
new Thread(()->{ for (int i = 0; i < 10; i++) number.de();},"D").start();
}
}
线程之间的精确唤醒
有几点要注意以下:
1.用JUC写必须记得lock.lock
2.千万不能忘记唤醒另一个线程之前要修改标记
package org.yyx.comunication;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/*
* 线程之间的顺序调用(精确调用)实现A->B->C
* 三个线程启动,要求:
* AA打印5次,BB打印10次,CC打印15次,接着在打印
* AA打印5次,BB打印10次,CC打印15次
* */
class Word {// 资源类
// 设置一个锁
private Lock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
// 设置一个标志
private int num = 1;
// 设置打印次数函数
public void printTimes(int i){
for (int j = 0; j < i; j++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
public void A(){
try {
lock.lock();
// 判断
while (num != 1){
condition1.await();
}
// 操作
printTimes(5);
// 唤醒
// 唤醒之前必须修改标记
num = 2;
condition2.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void B(){
try {
lock.lock();
// 判断
while (num != 2){
condition2.await();
}
// 操作
printTimes(10);
// 唤醒
num = 3;
condition3.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void C(){
try {
lock.lock();
// 判断
while (num != 3){
condition3.await();
}
// 操作
printTimes(15);
// 唤醒
num = 1;
condition1.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class ConditionDemo {
public static void main(String[] args) {
// 实例化资源类
Word word = new Word();
new Thread(()->{for (int i = 0; i < 10; i++) word.A();},"AA").start();
new Thread(()->{for (int i = 0; i < 10; i++) word.B();},"BB").start();
new Thread(()->{for (int i = 0; i < 10; i++) word.C();},"CC").start();
}
}
第三种线程方法
重点:
和Runnable相比区别:重载方法不同,有返回值,有泛型
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/*
* 第三种线程方法(之前两种是线程类和Runnable接口)
* Callable接口
* */
class MyThread implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("进入call方法");
return 520;
}
}
public class NewThread {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyThread myThread = new MyThread();
FutureTask<Integer> futureTask = new FutureTask(myThread);
new Thread(futureTask,"A").start(); // 进入call方法
System.out.println(futureTask.get()); // 520
}
}