JUC
本文档主要是记录juc和jvm的学习之路,阳哥,永远滴神!
“生死看淡,不服就干”
“基础不牢,地动山摇”
“发财的方法都写在’刑法’上”
“大学大学: 大家自己学”
“没有无所谓,只有做到位”
“树长得高是因为它的根扎得深”
“沉下来,深深的水,静静地流”
“扫帚不倒,灰尘不会自己跑掉”
“万丈高楼平地起,一切承担靠地基”
SaleTickets
并发/并行
concurrent parallel。分门别类两件事,并行。
三个包
- Java.util.concurrent
- java.util.concurrent.atomic
- java.util.concurrent.locks
线程状态
- NEW
- RUNNABLE
- BLOCKED
- WAITTING 一直等待
- TIMED_WATTING 过时等待,过时不侯
- TERMINATED
java代码
Tickets tickets = new Tickets();
// Thread(Runnable target, String name)
// 匿名内部类
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i < 40; ++i) {
tickets.sale();
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "A").start();
Lambda 表达式版本:
new Thread(() -> {
for (int i = 1; i < 40; ++i) tickets.sale(); }, "A").start();
Lambda 表达式
函数式编程
int age = 23;
y = kx + 1;
f(x) = kx + 1; //动态赋值
lambda
//指的是 函数式接口, 只能有一个接口函数,如果不写@FunctionalInterface,默认根据函数数量来判断,如果一个则是函数式接口,超过一个就是普通接口。
@FunctionalInterface
public interface Foo {
//public void sayHello();
public int add(int x, int y);
}
口诀:拷贝小括号,写死右箭头,落地大括号
- 有且仅有一个函数,无返回值,无参数,方法名可以省略。
Foo foo = () -> {
};
Foo foo = () -> {
System.out.println("hello"); };
- 有且仅有一个函数,有返回值,有参数
Foo foo = (int x, int y) -> {
System.out.println("hello");
return x + y;
};
- 使用default后,完整定义+实现。可以有多个default方法,仍然是函数式接口。可以使哟过lambda表达式。
//指的是 函数式接口
@FunctionalInterface
public interface Foo {
//public void sayHello();
public int add(int x, int y);
public default int mul (int x, int y) {
return x * y;
}
}
- 使用static静态方法, 也可以定义多个,仍然是函数式接口,能用lambda表达式。
@FunctionalInterface
public interface Foo {
//public void sayHello();
public int add(int x, int y);
public static int div(int x, int y) {
return x / y;
}
}
Runnable接口
Runnable 也是 @FunctionalInterface
new Thread(() -> {
System.out.println("hello"); }, "A").start();
集合类不安全
ArrayList
ArrayList 基础
New ArrayList(); 不指定使用对象类型的时候,默认是Object类型,啥都可以往里面装
java8 一开始new是空引用,第一次add 的时候创建list,初始值长度是10,hashmap初始值长度是16.
/**
* Default initial capacity. ArrayList() 默认容量
*/
private static final int DEFAULT_CAPACITY = 10;
/** 第一个元素被添加的时候,数组从默认空容量扩容到默认容量
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access
/** hashMap默认初始容量是 1 << 4,也就是16
* The default initial capacity - MUST be a power of two.
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
ArrayList扩容,扩容原值的一半 = 10 + 5 = 15. 使用arrays.copyOf 来搬家
扩容代码:
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); //右移实现扩容一半的容量
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
第二次扩容,15 + 7.5 = 22; hashmap扩容是原值的一倍。
//hashMap resize部分代码:
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
ArrayList 线程不安全
多个线程对一个ArrayList操作的时候,数据会出错。下述代码中,数据会出错;会出现报错情况,不一定每次都会报错。
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
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();
}
}
可能的报错:并发修改异常
java.util.ConcurrentModificationException
解决方法:
使用vector,vector add方法加了symchronized,而ArrayList add方法没有加锁,
//ArrayList add()
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
//Vector
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
vector数据一致性能保证,但是效率低;arrayList效率高,数据不能保证一致性
Collection 和 Collections
Collection是集合的顶级接口,Collections是工具类
List<String> list = Collections.synchronizedList(new ArrayList<>());
把list变成线程安全的
CopyOnWriteArrayList
List<String> list = new CopyOnWriteArrayList();
/**A thread-safe variant of {@link java.util.ArrayList} in which all mutative
* operations ({@code add}, {@code set}, and so on) are implemented by
* making a fresh copy of the underlying array.
* 通过实现每次都copy来保证线程安全,不会出现 ConcurrentModificationException
*/
add 方法实现:
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock(); //获取锁
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);//拷贝数组
newElements[len] = e; //赋值新元素
setArray(newElements);
return true;
} finally {
lock.unlock(); //释放锁
}
}
CopyOnWriteArrayList 和 Vector 和 ArrayList 平级
CopyOnWrite并发容器用于读多写少的并发场景。比如白名单,黑名单,商品类目的访问和更新场景,假如我们有一个搜索网站,用户在这个网站的搜索框中,输入关键字搜索内容,但是某些关键字不允许被搜索。这些不能被搜索的关键字会被放在一个黑名单当中,黑名单每天晚上更新一次。当用户搜索时,会检查当前关键字在不在黑名单当中,如果在,则提示不能搜索。
CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器。Vector是增删改查方法都加了synchronized,保证同步,但是每个方法执行的时候都要去获得锁,性能就会大大下降,而CopyOnWriteArrayList 只是在增删改上加锁,但是读不加锁,在读方面的性能就好于Vector,CopyOnWriteArrayList支持读多写少的并发情况。
Set
如下代码仍会报 ConcurrentModificationException 异常,所以是线程不安全的。HashSet内部结构是数组+链表(红黑树),内部本质是HashMap。key还是key,value是一个object
public static void main(String[] args) {
Set<String> set = new HashSet<>();
for (int i = 0; i <= 30; ++i) {
new Thread(() -> {
set.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(set );
}, String.valueOf(i)).start();
}
}
hashset源码:
public HashSet() {
map = new HashMap<>(); //表明它是一个hashmap 本质上
}
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object(); //这个Object会被当作value处理
public boolean add(E e) {
return map.put(e, PRESENT) == null; //放入key 和 value,value是一个Object
}
可以使用 CopyOnWriteSet
Set<String> set = new CopyOnWriteArraySet<>();
//构造函数 本质上来说,他是个CopyOnWriteArrayList
public CopyOnWriteArraySet() {
al = new CopyOnWriteArrayList<E>()