目录
面试:Lock与Synchronized的区别?用新的Lock有什么好处?举例说说?
一、并发编程
JUC就是java.util.concurrent工具包的简称,这是一个处理线程的工具包,JDK1.5开始出现的。
管程:又称Monitor监视器,又是锁:是一种同步的机制,保证同一时间,只有一个线程访问被保护的数据或代码。
用户线程:自定义线程基本都是用户线程。通过Thread.currentThread().isDaemon()来判断是否是用户线程,等于 false是用户线程。等于true 是守护线程。
守护线程:特殊线程,运行在后台,比如:垃圾回收
设置守护线程: 线程.setDaemon(true); 在执行start()之前设置。
1、主线程结束了,用户线程还在运行,jvm存活。
2、没有用户线程了,都是守护线程,jvm结束。
1、Lock接口
Lock中常用方法:
1.void lock():获取锁。
2.void lockInterruptibly():如果当前线程未被中断,则获取锁。
3.Condition newCondition():返回绑定到此 Lock 实例的新 Condition 实例。
4.boolean tryLock():仅在调用时锁为空闲状态才获取该锁。
5.boolean tryLock(long time, TimeUnit unit):如果锁在给定的等待时间内空闲,
并且当前线程未被中断,则获取锁。
6.void unlock():释放锁。
Condition接口
//@since 1.5
public interface Condition {
Condition接口中常用方法
1.void await():造成当前线程在接到信号或被中断之前一直处于等待状态。
2.boolean await(long time, TimeUnit unit):造成当前线程在接到信号、被中断或到达指定
等待时间之前一直处于等待状态。
3.long awaitNanos(long nanosTimeout):造成当前线程在接到信号、被中断或到达指定等待时
间之前一直处于等待状态。
4.void awaitUninterruptibly():造成当前线程在接到信号之前一直处于等待状态。
5.boolean awaitUntil(Date deadline):造成当前线程在接到信号、被中断或到达指定最后期
限之前一直处于等待状态。
6.void signal():唤醒一个等待线程。
7.void signalAll():唤醒所有等待线程。
面试:Lock与Synchronized的区别?用新的Lock有什么好处?举例说说?
Lock不是Java语言内置的,Synchronized是Java语言的关键字,因此是内置特性。
Synchronized不需要用户手动释放锁,当Synchronized方法或者Synchronized代码块执行完之后,系统会自动让线程释放对锁的占用,而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁的现象。
1、原始构成
synchronized是关键字属于JVM层面,monitorenter(底层是通过monitor对象来完成,其实wait/notify等方法也依赖于monitor对象只有在同步块或方法中才能调wait/notify等方法)
monitorexit
Lock是具体类(java.util.concurrent.Locks.Lock)是api层面的锁。
2、使用方法
synchronized不需要用户去手动释放锁,当synchronized代码执行完成后系统会自动让线程释放对锁的占用
ReentrantLock则需要用户去手动释放锁,若没有主动释放锁,就有可能导致出现死锁现象
3、等待是否可中断
synchronized不可中断,除非抛出异常或者正常运行完成
ReentrantLock可中断
1.设置超时方法tryLock(long timeout,TimeUnit unit)
2.lockInterruptibly()放代码块中,调用interrupt()方法可中断
4、加锁是否公平
synchronized非公平锁
ReentrantLock两者都可以,默认非公平锁
5、锁绑定多个条件Condition
synchronized没有
ReentrantLock用来实现分组唤醒需要唤醒的线程们,可以精确唤醒,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程
示例:
import java.util.concurrent.locks.ReentrantLock;
public class LockSaleTicket {
public static void main(String[] args) {
LockTicket lockTicket = new LockTicket();
new Thread(()-> {
for (int i = 0; i < 40; i++) {
lockTicket.sale();
}
},"aa").start();
new Thread(()-> {
for (int i = 0; i < 40; i++) {
lockTicket.sale();
}
},"bb").start();
new Thread(()-> {
for (int i = 0; i < 40; i++) {
lockTicket.sale();
}
},"cc").start();
}
}
class LockTicket{
private int number = 30;
private final ReentrantLock lock = new ReentrantLock();
//卖票方法
public void sale() {
lock.lock();
try {
if (number >0) {
System.out.println(Thread.currentThread().getName() + ":卖出" + (number--)+" 剩余:"+number);
}
} finally {
lock.unlock();
}
}
}
2、线程间的通信
一个线程+1,一个线程-1交替运行。即线程之间通信。
2.1、synchronized通信示例
/**
* 线程通信的例子:有2个线程实现对一个初始值是0的变量
* 一个线程对值+1、一个线程对值-1
* A线程 1 B线程 0
* A线程 1 B线程 0
* A线程 1 B线程 0
* ....
*/
class Share{
//初始值
private int number = 0;
//+1方法
public synchronized void add() throws InterruptedException {
if (number != 0) {//number不是0,等待
wait();
}
number++;//number值是0就+1
System.out.println(Thread.currentThread().getName() + " :: " + number);
notifyAll();
}
//-1方法
public synchronized void subtract() throws InterruptedException {
if (number != 1) {//number不是1,等待
wait();
}
number--;//number值是1就-1
System.out.println(Thread.currentThread().getName() + " :: " + number);
notifyAll();
}
}
public class ThreadDemo1 {
public static void main(String[] args) {
Share share = new Share();
new Thread(()-> {
for (int i = 0; i <= 10; i++) {
try {
share.add();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()-> {
for (int i = 0; i <= 10; i++) {
try {
share.subtract();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
结果:
A :: 1
B :: 0
A :: 1
B :: 0
A :: 1
B :: 0
A :: 1
B :: 0
A :: 1
B :: 0
A :: 1
B :: 0
A :: 1
B :: 0
A :: 1
B :: 0
A :: 1
B :: 0
A :: 1
B :: 0
A :: 1
B :: 0
上面示例如果再增加2个线程一个+1,一个-1.结果还是正确的吗?
/**
* 线程通信的例子:有2个线程实现对一个初始值是0的变量
* 一个线程对值+1、一个线程对值-1
* A线程 1 B线程 0
* A线程 1 B线程 0
* A线程 1 B线程 0
* ....
*/
class Share{
//初始值
private int number = 0;
//+1方法
public synchronized void add() throws InterruptedException {
if (number != 0) {//number不是0,等待
wait();
}
number++;//number值是0就+1
System.out.println(Thread.currentThread().getName() + " :: " + number);
notifyAll();
}
//-1方法
public synchronized void subtract() throws InterruptedException {
if (number != 1) {//number不是1,等待
wait();
}
number--;//number值是1就-1
System.out.println(Thread.currentThread().getName() + " :: " + number);
notifyAll();
}
}
public class ThreadDemo1 {
public static void main(String[] args) {
Share share = new Share();
new Thread(()-> {
for (int i = 0; i <= 10; i++) {
try {
share.add();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()-> {
for (int i = 0; i <= 10; i++) {
try {
share.subtract();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()-> {
for (int i = 0; i <= 10; i++) {
try {
share.add();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()-> {
for (int i = 0; i <= 10; i++) {
try {
share.subtract();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
结果:
A :: 1
B :: 0
A :: 1
B :: 0
A :: 1
B :: 0
A :: 1
B :: 0
A :: 1
B :: 0
A :: 1
B :: 0
A :: 1
B :: 0
C :: 1
A :: 2
C :: 3
B :: 2
C :: 3
A :: 4
C :: 5
B :: 4
C :: 5
A :: 6
C :: 7
D :: 6
B :: 5
D :: 4
C :: 5
A :: 6
C :: 7
D :: 6
B :: 5
D :: 4
C :: 5
D :: 4
C :: 5
D :: 4
C :: 5
D :: 4
结果和预期结果不符。原因是虚假唤醒问题。
解决虚假唤醒问题
wait()在哪里睡,就在哪里醒
解决方式:把if判断换成while判断,等线程醒来之后会继续进行判断,不满足条件继续睡,直到满足条件继续往下执行。
/**
* 线程通信的例子:有2个线程实现对一个初始值是0的变量
* 一个线程对值+1、一个线程对值-1
* A线程 1 B线程 0
* A线程 1 B线程 0
* A线程 1 B线程 0
* ....
*/
class Share{
//初始值
private int number = 0;
//+1方法
public synchronized void add() throws InterruptedException {
while (number != 0) {//number不是0,等待
wait();
}
number++;//number值是0就+1
System.out.println(Thread.currentThread().getName() + " :: " + number);
notifyAll();
}
//-1方法
public synchronized void subtract() throws InterruptedException {
while (number != 1) {//number不是1,等待
wait();
}
number--;//number值是1就-1
System.out.println(Thread.currentThread().getName() + " :: " + number);
notifyAll();
}
}
public class ThreadDemo1 {
public static void main(String[] args) {
Share share = new Share();
new Thread(()-> {
for (int i = 0; i <= 10; i++) {
try {
share.add();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()-> {
for (int i = 0; i <= 10; i++) {
try {
share.subtract();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()-> {
for (int i = 0; i <= 10; i++) {
try {
share.add();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()-> {
for (int i = 0; i <= 10; i++) {
try {
share.subtract();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
结果:
A :: 1
B :: 0
A :: 1
B :: 0
A :: 1
B :: 0
A :: 1
B :: 0
A :: 1
B :: 0
C :: 1
B :: 0
A :: 1
B :: 0
C :: 1
B :: 0
A :: 1
B :: 0
C :: 1
D :: 0
A :: 1
B :: 0
C :: 1
D :: 0
A :: 1
B :: 0
C :: 1
D :: 0
A :: 1
D :: 0
C :: 1
D :: 0
A :: 1
D :: 0
C :: 1
D :: 0
C :: 1
D :: 0
C :: 1
D :: 0
C :: 1
D :: 0
C :: 1
D :: 0
2.2、Lock通信示例:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 线程通信的例子:有2个线程实现对一个初始值是0的变量
* 一个线程对值+1、一个线程对值-1
* A线程 1 B线程 0
* A线程 1 B线程 0
* A线程 1 B线程 0
* ....
*/
class Share{
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
//初始值
private int number = 0;
//+1方法
public void add() throws InterruptedException {
lock.lock();
try {
while (number != 0) {//number不是0,等待
condition.await();
}
number++;//number值是0就+1
System.out.println(Thread.currentThread().getName() + " :: " + number);
condition.signalAll();
} finally {
lock.unlock();
}
}
//-1方法
public void subtract() throws InterruptedException {
lock.lock();
try {
while (number != 1) {//number不是1,等待
condition.await();
}
number--;//number值是1就-1
System.out.println(Thread.currentThread().getName() + " :: " + number);
condition.signalAll();
} finally {
lock.unlock();
}
}
}
public class ThreadDemo2 {
public static void main(String[] args) {
Share share = new Share();
new Thread(()-> {
for (int i = 0; i <= 10; i++) {
try {
share.add();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()-> {
for (int i = 0; i <= 10; i++) {
try {
share.subtract();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()-> {
for (int i = 0; i <= 10; i++) {
try {
share.add();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()-> {
for (int i = 0; i <= 10; i++) {
try {
share.subtract();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
结果:
A :: 1
B :: 0
A :: 1
B :: 0
A :: 1
B :: 0
A :: 1
B :: 0
A :: 1
B :: 0
A :: 1
B :: 0
A :: 1
B :: 0
A :: 1
B :: 0
A :: 1
B :: 0
A :: 1
B :: 0
A :: 1
B :: 0
C :: 1
D :: 0
C :: 1
D :: 0
C :: 1
D :: 0
C :: 1
D :: 0
C :: 1
D :: 0
C :: 1
D :: 0
C :: 1
D :: 0
C :: 1
D :: 0
C :: 1
D :: 0
C :: 1
D :: 0
C :: 1
D :: 0
2.3、线程间定制化通信
线程A输出2次,线程B输出3次,线程C输出5次这种例子,相当于定制版通信
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 线程间定制化通信的例子:
* 有3个线程,按照如下进行打印
* A线程 打印2次 B线程 打印3次 C线程 打印5次
* A线程 打印2次 B线程 打印3次 C线程 打印5次
* A线程 打印2次 B线程 打印3次 C线程 打印5次
* ....
* 打印5轮
*/
class ShareResource{
private int flag = 1;//1:A、2:B、3:C
private final Lock lock = new ReentrantLock();
private final Condition c1 = lock.newCondition();
private final Condition c2 = lock.newCondition();
private final Condition c3 = lock.newCondition();
//打印2次
public void print2(int loop) throws InterruptedException {
lock.lock();
try {
while (flag != 1) {//flag不是1,等待
c1.await();
}
for (int i = 1; i <=2; i++) {
System.out.println(Thread.currentThread().getName() + " :: "+ i +" 轮数:"+ loop);
}
flag=2;
c2.signal();
} finally {
lock.unlock();
}
}
//打印3次
public void print3(int loop) throws InterruptedException {
lock.lock();
try {
while (flag != 2) {//flag不是2,等待
c2.await();
}
for (int i = 1; i <=3; i++) {
System.out.println(Thread.currentThread().getName() + " :: "+ i +" 轮数:"+ loop);
}
flag=3;
c3.signal();
} finally {
lock.unlock();
}
}
//打印5次
public void print5(int loop) throws InterruptedException {
lock.lock();
try {
while (flag != 3) {//flag不是3,等待
c3.await();
}
for (int i = 1; i <=5; i++) {
System.out.println(Thread.currentThread().getName() + " :: "+ i +" 轮数:"+ loop);
}
flag=1;
c1.signal();
} finally {
lock.unlock();
}
}
}
public class ThreadDemo3 {
public static void main(String[] args) {
ShareResource share = new ShareResource();
new Thread(()-> {
for (int i = 1; i <= 5; i++) {
try {
share.print2(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()-> {
for (int i = 1; i <= 5; i++) {
try {
share.print3(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()-> {
for (int i = 1; i <= 5; i++) {
try {
share.print5(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
}
}
结果:
A :: 1 轮数:1
A :: 2 轮数:1
B :: 1 轮数:1
B :: 2 轮数:1
B :: 3 轮数:1
C :: 1 轮数:1
C :: 2 轮数:1
C :: 3 轮数:1
C :: 4 轮数:1
C :: 5 轮数:1
A :: 1 轮数:2
A :: 2 轮数:2
B :: 1 轮数:2
B :: 2 轮数:2
B :: 3 轮数:2
C :: 1 轮数:2
C :: 2 轮数:2
C :: 3 轮数:2
C :: 4 轮数:2
C :: 5 轮数:2
A :: 1 轮数:3
A :: 2 轮数:3
B :: 1 轮数:3
B :: 2 轮数:3
B :: 3 轮数:3
C :: 1 轮数:3
C :: 2 轮数:3
C :: 3 轮数:3
C :: 4 轮数:3
C :: 5 轮数:3
A :: 1 轮数:4
A :: 2 轮数:4
B :: 1 轮数:4
B :: 2 轮数:4
B :: 3 轮数:4
C :: 1 轮数:4
C :: 2 轮数:4
C :: 3 轮数:4
C :: 4 轮数:4
C :: 5 轮数:4
A :: 1 轮数:5
A :: 2 轮数:5
B :: 1 轮数:5
B :: 2 轮数:5
B :: 3 轮数:5
C :: 1 轮数:5
C :: 2 轮数:5
C :: 3 轮数:5
C :: 4 轮数:5
C :: 5 轮数:5
3、集合的线程安全
3.1、List集合线程不安全示例
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class ThreadDemo4 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 20; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0, 4));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
执行会发生异常:java.util.ConcurrentModificationException
导致原因:
并发争抢修改导致,一个人正在写入,另外一个人过来抢夺,导致数据不一致异常,并发修改异常。
解决方式:
1、Vector
2、Collections
3、CopyOnWriteArrayList
1、Vector示例:
import java.util.*;
public class ThreadDemo4 {
public static void main(String[] args) {
List<String> list = new Vector<>();
for (int i = 0; i < 20; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0, 4));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
2、Collections示例:
import java.util.*;
public class ThreadDemo4 {
public static void main(String[] args) {
List<String> list = Collections.synchronizedList(new ArrayList<>());
for (int i = 0; i < 20; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0, 4));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
3、CopyOnWriteArrayList示例:
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
public class ThreadDemo4 {
public static void main(String[] args) {
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 20; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0, 4));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
4、CopyOnWriteArrayList源码原理:
用到了写时复制技术。add()先加锁,并把原集合进行复制,不影响读操作。写完之后重新覆盖原集合,解锁。
写时复制:
CopyOnWrite容器即写时复制的容器。往一个容器添加元素的时候,不直接往当前容器Object[]添加,而是先将当前容器Object[]进行Copy,复制出一个新的容器Object[] newElements,然后往新的容器Object[] newElements 里添加元素,添加完元素之后,再将原容器的引用指向新的容器setArray(newElements);这样做的好处是可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
...
final transient ReentrantLock lock = new ReentrantLock();
private transient volatile Object[] array;
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
final void setArray(Object[] a) {
array = a;
}
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();
}
}
3.2、HashSet线程不安全演示
import java.util.*;
public class ThreadDemo4 {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
for (int i = 0; i < 20; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0, 4));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
执行会发生异常:java.util.ConcurrentModificationException
解决方式:
1、CopyOnWriteArraySet
2、Collections.synchronizedSet(new HashSet<>());
1、CopyOnWriteArraySet示例:
import java.util.*;
import java.util.concurrent.CopyOnWriteArraySet;
public class ThreadDemo4 {
public static void main(String[] args) {
Set<String> set = new CopyOnWriteArraySet<>();
for (int i = 0; i < 20; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0, 4));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
CopyOnWriteArraySet底层还是CopyOnWriteArrayList
public class CopyOnWriteArraySet<E> extends AbstractSet<E>
implements java.io.Serializable {
...
private final CopyOnWriteArrayList<E> al;
/**
* Creates an empty set.
*/
public CopyOnWriteArraySet() {
al = new CopyOnWriteArrayList<E>();
}
...
}
3.3、HashMap线程不安全演示
import java.util.*;
public class ThreadDemo4 {
public static void main(String[] args) {
Map<String,String> map = new HashMap<>();
for (int i = 0; i < 20; i++) {
String key = String.valueOf(i);
new Thread(()->{
map.put(key,UUID.randomUUID().toString().substring(0, 4));
System.out.println(map);
},String.valueOf(i)).start();
}
}
}
执行会发生异常:java.util.ConcurrentModificationException
解决方式:
1、ConcurrentHashMap
2、Collections.synchronizedMap(new HashMap<>());
1、ConcurrentHashMap示例:
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class ThreadDemo4 {
public static void main(String[] args) {
Map<String,String> map = new ConcurrentHashMap<>();
for (int i = 0; i < 20; i++) {
String key = String.valueOf(i);
new Thread(()->{
map.put(key,UUID.randomUUID().toString().substring(0, 4));
System.out.println(map);
},String.valueOf(i)).start();
}
}
}
一个程序员最重要的能力是:写出高质量的代码!!
有道无术,术尚可求也,有术无道,止于术。
无论你是年轻还是年长,所有程序员都需要记住:时刻努力学习新技术,否则就会被时代抛弃!