一 ArrayList集合的线程安全
引入线程不安全问题
package new_course.chp3.list_demo;
import java.util.ArrayList;
import java.util.UUID;
/**
* @author Created by Lin Weihong
* @date on 2022/6/1 21:33
* List集合线程不安全
*/
public class ErrorList {
public static void main(String[] args) {
//创建ArrayList集合
List<String> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
//往集合添加内容
list.add(UUID.randomUUID().toString().substring(0, 8));
//从集合取出内容
System.out.println(list);
}, String.valueOf(i)).start();
}
}
}
//结果:
java.util.ConcurrentModificationException
程序会报错,修改异常
解决方法Vector Collections集合
package new_course.chp3.list_demo;
import java.util.*;
/**
* @author Created by Lin Weihong
* @date on 2022/6/1 21:33
* List集合线程不安全
*/
public class ErrorList {
public static void main(String[] args) {
//Vector解决
// List<String> list = new Vector<>();
//Collections解决
List<String> list = Collections.synchronizedList(new ArrayList<>());
for (int i = 0; i < 10; i++) {
new Thread(() -> {
//往集合添加内容
list.add(UUID.randomUUID().toString().substring(0, 8));
//从集合取出内容
System.out.println(list);
}, String.valueOf(i)).start();
}
}
}
解决方法 CopyOnWriteArrayList
package new_course.chp3.list_demo;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* @author Created by Lin Weihong
* @date on 2022/6/1 22:38
*/
public class Solution2 {
public static void main(String[] args) {
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
//存入集合
list.add(UUID.randomUUID().toString().substring(0,8));
//从集合当中取出
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
CopyOnWriteArrayList原理
- 支持并发读操作
- 写的时候,先讲当前容器复制一份,然后在新副本上执行写的操作
- 结束之后再将原容器的引用指向新的容器
//CopyOnWriteArrayList 的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);//调用下面的setArray方法
return true;
} finally {
lock.unlock();
}
}
final void setArray(Object[] a) {
array = a;//将原本的引用指向新的数组
}
二 HashSet集合的线程安全
引入线程不安全问题
package new_course.chp3.hashset_demo;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
/**
* @author Created by Lin Weihong
* @date on 2022/6/2 0:21
* 演示HashSet线程不安全
*/
public class ErrorHashSet {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
for (int i = 0; i < 30; i++) {
new Thread(() -> {
//放入set集合
set.add(UUID.randomUUID().toString().substring(0,8));
//从set集合取出
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
//程序报错
解决方法 CopyOnWriteArraySet
/**
* @author Created by Lin Weihong
* @date on 2022/6/2 0:21
* 演示HashSet线程不安全
*/
public class Solution1{
public static void main(String[] args) {
Set<String> set = new CopyOnWriteArraySet<>();
for (int i = 0; i < 30; i++) {
new Thread(() -> {
//放入set集合
set.add(UUID.randomUUID().toString().substring(0,8));
//从set集合取出
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
三 HashMap集合的线程安全
引入线程不安全问题
package new_course.chp3.hashmap_demo;
import java.util.Map;
import java.util.UUID;
import java.util.HashMap;
/**
* @author Created by Lin Weihong
* @date on 2022/6/2 0:27
* 演示HashMap线程不安全问题
*/
public class ErrorHashMap {
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
for (int i = 0; i < 10; i++) {
String key = String.valueOf(i);
new Thread(() -> {
//放入集合
map.put(String.valueOf(key),UUID.randomUUID().toString().substring(0,8));
//取出集合
System.out.println(map);
},String.valueOf(i)).start();
}
}
}
//程序报错
解决方法 ConcurrentHashMap
package new_course.chp3.hashmap_demo;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author Created by Lin Weihong
* @date on 2022/6/2 0:36
*/
public class Solution1 {
public static void main(String[] args) {
Map<String, String> map = new ConcurrentHashMap<>();
for (int i = 0; i < 50; i++) {
String key = String.valueOf(i);
new Thread(() -> {
//放入集合
map.put(String.valueOf(key), UUID.randomUUID().toString().substring(0, 8));
//取出集合
System.out.println(map);
}, String.valueOf(i)).start();
}
}
}
四 多线程锁
八锁现象
package new_course.chp3;
import java.util.concurrent.TimeUnit;
/**
* @author Created by Lin Weihong
* @date on 2022/6/2 0:47
*/
class Phone {
public synchronized void sendSMS() {
//停留4秒
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("------sendSMS");
}
public static synchronized void sendSMS1() {
//停留4秒
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("------sendSMS1");
}
public synchronized void sendEmail() {
System.out.println("------sendEmail");
}
public static synchronized void sendEmail1() {
System.out.println("------sendEmail1");
}
public void getHello() {
System.out.println("------getHello");
}
}
public class Lock8 {
public static void main(String[] args) throws InterruptedException {
Phone phone1 = new Phone();
Phone phone2 = new Phone();
new Thread(() -> {
// phone1.sendSMS();
phone1.sendSMS1();
}, "线程1").start();
Thread.sleep(100);
new Thread(() -> {
// phone1.getHello();
phone2.sendEmail();
// phone1.sendEmail();
// phone2.sendEmail1();
}, "线程2").start();
}
}
/**
* @Description 8锁
* <p>
* 1、标准访问,先打印短信还是邮件
* ------sendSMS
* ------sendEmail
* <p>
* 2、停4秒在短信方法内,先打印短信还是邮件
* ------sendSMS
* ------sendEmail
* -------------------------synchronized锁的是当前对象this,所以上面两个都是一样的结果----------------------
* <p>
* 3、新增普通的hello方法,先打印短信(4s)还是hello
* ------getHello
* ------sendSMS
* -------------------------sendSMS被锁住了,但是普通方法没有被锁----------------------
* <p>
* 4、现在有两部手机,先打印短信(4s)还是邮件
* ------sendEmail
* ------sendSMS
* -------------------------synchronized锁的是当前对象this,两部手机分别为两个不同对象,所以被锁的是phone1,但是phone2没有被锁----------------------
* 5、两个静态同步方法,1部手机,先打印短信还是邮件
* ------sendSMS1
* ------sendEmail1
*
* 6、两件静态同步方法,2部手机,先打印短信还是邮件
* ------sendSMS1
* ------sendEmail1
* -------------------------以上两个方法,static+synchronized,锁的是类对象Class--------------
* 7、1个静态同步方法,1个普通同步方法,1部手机,先打印短信还是邮件
* ------sendEmail
* ------sendSMS1
* <p>
* 8、1个静态同步方法,1个普通同步方法,2部手机,先打印短信还是邮件
* ------sendEmail
* ------sendSMS1
*
*/
公平锁与非公平锁
针对于之前卖票问题,假设此时有四个线程一起卖票(AA,BB,CC,DD),如果是非公平锁(默认),就可能出现一个线程卖完所有的票,而其他线程没有票卖的情况。而公平锁则是每个资源都能有机会一起卖票,减少出现旱的旱死涝的涝死情景。
如何声明公平锁和非公平锁呢
//公平锁
private final ReentrantLock lock = new ReentrantLock(true);
//非公平锁
private final ReentrantLock lock = new ReentrantLock(true);
非公平锁:效率高、线程饿死
公平锁:效率低、人人有份
可重入锁(递归锁)
可重复进入的锁
synchronized(隐式锁)
package new_course.chp3;
/**
* @author Created by Lin Weihong
* @date on 2022/6/2 11:21
* 可重入锁
*/
public class SyncLockDemo {
public synchronized void test() {
test(); //Exception in thread "main" java.lang.StackOverflowError,如果不是可重入锁,就不会栈溢出,因为被锁住了
}
public static void main(String[] args) {
SyncLockDemo syncLockDemo = new SyncLockDemo();
syncLockDemo.test();
// Object o = new Object();
// new Thread(() -> {
// synchronized (o) {
// System.out.println(Thread.currentThread().getName() + "外层");
// synchronized (o) {
// System.out.println(Thread.currentThread().getName() + "中层");
// synchronized (o) {
// System.out.println(Thread.currentThread().getName() + "内层");
// synchronized (o) {
// System.out.println(Thread.currentThread().getName() + "底层");
// }
// }
// }
// }
// }, "aa").start();
//结果
aa外层
aa中层
aa内层
aa底层
}
}
Lock锁(显示)
package new_course.chp3;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author Created by Lin Weihong
* @date on 2022/6/2 11:21
* 可重入锁
*/
public class SyncLockDemo02 {
public static void main(String[] args) {
//Lock演示可重入锁
Lock lock = new ReentrantLock();
new Thread(() -> {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "外层");
new Thread(() -> {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "外层");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
},"a").start();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
},"a").start();
}
}
死锁
死锁的概念
产生死锁的原因
- 系统资源不足
- 进程运行推进顺序不合适
- 资源分配不当
引入死锁场景
package new_course.chp4;
import java.util.concurrent.TimeUnit;
/**
* @author Created by Lin Weihong
* @date on 2022/6/2 13:02
* 展示死锁
*/
public class DeadLock {
//创建两个对象
static Object a = new Object();
static Object b = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (a) {
System.out.println(Thread.currentThread().getName() + "持有锁a,试图获取锁b");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (b) {
System.out.println(Thread.currentThread().getName() + "获取锁b");
}
}
},"A").start();
new Thread(() -> {
synchronized (b) {
System.out.println(Thread.currentThread().getName() + "持有锁b,试图获取锁a");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (a) {
System.out.println(Thread.currentThread().getName() + "获取锁a");
}
}
},"B").start();
}
}
验证是否是死锁
- jps 类似linux的 ps -ef 展示java进程
- jstack jvm自带,能直接查看到堆栈信息,堆栈跟踪工具