1、概念
1.1 什么是JUC
JUC是java.util.concurrent工具包,在java5时出现,是操作线程的工具包
1.2 线程与进程
1.2.1 区分
进程:指系统中正在运行的应用程序,进程就是运行了的程序,进程是资源分配的最小单位
线程:系统分配处理器时间资源的基本单元,线程是程序执行的最小单元
1.2.2 wait/sleep
- sleep是Thread类的静态方法,wait是Object的方法(任何实例对象都可以调用)
- sleep不会释放锁,他也不需要占用锁。wait会释放锁(调用wait前提是当前线程站有锁,即代码在synchronized中)
- 他们都会被interrupted打断
1.2.3 并发与并行
串行:多个任务一个一个执行
并行:多个任务同时执行
并发:同一时刻多个线程访问同一个资源(抢票)
1.2.4 管程
Monitor 监视器 java中也叫锁
是一种同步机制,保证同一时间只有一个线程在操作资源
jvm中的同步基于进入和退出,是使用管程对象实现的
2、Lock接口
2.1 Synchronized
- 修饰代码块
- 修饰方法
2.2 创建线程的方式
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口
- 使用线程池
2.3 Lock使用
private ReentrantLock lock = new ReentrantLock();
......
lock.lock();
try {
......
} finally {
lock.unlock();
}
......
2.4 synchronized与Lock的区别
- Lock是一个接口,synchronized是java中的关键字
- synchronized在发生异常时,会自动释放线程占有的锁,所以不会导致死锁的出现;Lock在出现异常时如果没有去使用unlock()释放,会造成死锁现象,所以需要在finally块中释放锁。
- 通过Lock可以知道有没有成功的获取到锁,synchronized不行
- 在性能上说当资源的竞争不激烈时,两者性能差不多,而当竞争资源非常激烈时(同时存在大量线程竞争),此时Lock性能要优于synchronized
3、线程间的通信
class ANum{
private int num = 0;
public synchronized void add() {
//判断
if (num !=0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//do something
num++;
System.out.println(Thread.currentThread().getName() + "== value::" + num);
this.notify();
}
public synchronized void reduce() {
if(num != 1) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num--;
System.out.println(Thread.currentThread().getName() + "== value::" + num);
//通知其他
this.notify();
}
}
public class ThreadCommuniaction {
public static void main(String[] args) {
ANum aNum = new ANum();
new Thread(() ->{
for (int i = 0; i < 10; i++){
aNum.add();
}
},"AA").start();
new Thread(() ->{
for (int i = 0; i < 10; i++){
aNum.reduce();
}
},"BB").start();
}
}
再增加两个线程,作加和减操作,可能会出现类似的错误
AA== value::1
BB== value::0
AA== value::1
BB== value::0
AA== value::1
DD== value::0
BB== value::-1
DD== value::-2
wait的虚假唤醒:
线程也可以唤醒,而不会被通知,中断或超时,即所谓的虚假唤醒 。 虽然这在实践中很少会发生,但应用程序必须通过测试应该使线程被唤醒的条件来防范,并且如果条件不满足则继续等待。 换句话说,等待应该总是出现在循环中,就像这样:
synchronized (obj) {
while (<condition does not hold>)
obj.wait(timeout);
... // Perform action appropriate to condition
}
class Share{
private int num = 0;
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void add() {
//判断
lock.lock();
try {
if (num !=0){
condition.await();
}
//do something
num++;
System.out.println(Thread.currentThread().getName() + "== value::" + num);
//通知其他线程
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void reduce() {
lock.lock();
try {
if (num !=1){
condition.await();
}
//do something
num--;
System.out.println(Thread.currentThread().getName() + "== value::" + num);
//通知其他线程
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class ThreadCommuniaction {
public static void main(String[] args) {
ANum aNum = new ANum();
Share share = new Share();
new Thread(() ->{
for (int i = 0; i < 10; i++){
share.add();
}
},"AA").start();
new Thread(() ->{
for (int i = 0; i < 10; i++){
share.reduce();
}
},"BB").start();
}
}
3.1 线程间的定制通信
三个线程A、B、C ,让线程A、线程B、线程C按照顺序输出,多次循环。
class ShareResource {
//标志位
private Integer flag = 1;
private ReentrantLock lock = new ReentrantLock();
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
private Condition c3 = lock.newCondition();
public void print5(int loop) {
lock.lock();
try {
while (flag != 1) {
c1.await();
}
for (int i = 0; i < 5; i++) {
System.out.println("===="+i+"===="+Thread.currentThread().getName()+" loop:"+loop);
}
//调整标志位
flag = 2;
//唤醒线程
c2.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void print10(int loop) {
lock.lock();
try {
while (flag != 2) {
c2.await();
}
for (int i = 0; i < 10; i++) {
System.out.println("####"+i+"####"+Thread.currentThread().getName()+" loop:"+loop);
}
flag = 3;
c3.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void print15(int loop) {
lock.lock();
try {
while (flag != 3) {
c3.await();
}
for (int i = 0; i < 15; i++) {
System.out.println("****"+i+"****"+Thread.currentThread().getName()+" loop:"+loop);
}
flag = 1;
c1.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class CustomizationThread {
public static void main(String[] args) {
ShareResource resource = new ShareResource();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
resource.print5(i);
}
},"A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
resource.print10(i);
}
},"B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
resource.print15(i);
}
},"C").start();
}
}
4、集合的线程安全
4.1 ArrayList
问题,ArrayList的add方法并不是线程安全的,多线程操作时可能会出现错误
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
for (int i = 0; i < 30; i++) {
new Thread(() ->{
list.add(UUID.randomUUID().toString());
//在输出的时候,可能会有线程修改list,报ConcurrentModificationException错误
System.out.println(list);
}).start();
}
}
4.1.1 Vector
java.util.Vector 是线程安全的因为它的方法上修饰了synchronized
public synchronized void addElement(E obj) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = obj;
}
4.1.2 Collections
使用Collections得到一个线程安全的list,内部也是使用synchronized修饰
//获得一个线程安全的list
List<String> list = Collections.synchronizedList(new ArrayList<>());
4.1.3 CopyOnWriteArrayList
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
//CopyOnWriteArrayList内部add()方法
private transient volatile Object[] array;
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
//将上面定义的array赋值给elements
Object[] elements = getArray();
int len = elements.length;
//将elements中的内容复制到新的数组 这个数组长度比elements长度加一
//这样线程的写入操作和其他线程的读取操作的对象是不同的,不会报出异常
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
//将newElements赋值给定义的array
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
4.2 HashMap与HashSet
4.2.1 CopyOnWriteArraySet
HashSet在多线程操作时也会出现ConcurrentModificationException错误
在CopyOnWriteArraySet中持有着CopyOnWriteArrayList实例,CopyOnWriteArraySet的操作是调用CopyOnWriteArrayList的方法。
private final CopyOnWriteArrayList<E> al;
4.2.2 ConcurrentHashMap
其实HashSet的底层就是使用HashMap保存数据的,所以HashMap也不是线程安全的
使用ConcurrentHashMap来替代HashMap在线程不安全的情景的使用