同步:synchronized
同步的概念就是共享,我们要牢牢记住"共享"这俩个字,如果不是共享的资源,就没有必要进行同步。同步的目的就是为了线程安全,其实对于线程安全来说,需要满足俩个特性:原子性(同步),可见性。
异步:asynchronized
异步的概念就是独立,相互之间不受到任何制约。就好像我们学习http的时候,在页面发起的Ajax请求,我们还可以继续浏览或操作页面的内容,二者之间没有任何关系。
线程通信概念:线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体,线程间的通信就成为整体的必用方式之一。当线程存在通信指挥,系统间的交互性会更强大,在提高CPU利用率的同时还会使开发人员对线程任务在处理的过程中进行有效的把控与监督。
使用wait / notify 方法实现线程间的通信。(注意这两个方法都是object的类的方法,换句话说java为所有的对象都提供了这两个方法)
知识点:
1.wait 和 notify 必须配合 synchronized 关键字使用
2.wait方法释放锁,notify方法不释放锁
某大厂关于线程通信面试题:
有两个线程A、B,A线程向一个集合(List<String>) 里面依次添加元素“abc”字符串, 一共添加十次,当添加到第五次的时候,希望B线程能够收到A线程的通知,然后B线程执行相关的业务操作,我们应该如何进行设计?
方法一:是有wait()和notify()实现
public class ListAdd1 {
//1.定义要添加数据的容器
private static List list = new ArrayList();
//2.追加方法
public void add() {
list.add("abc");
}
public int size() {
return list.size();
}
public static void main(String[] args) {
final ListAdd1 list1 = new ListAdd1();
/*
* 使用wait和notify必须有一个lock(锁)对象
* 此处创建一个object的lock对象
*/
final Object lock = new Object();
//线程A
Thread A = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
synchronized(lock) {
for(int i=0;i<10;i++) {
list1.add();
System.out.println("当前线程:"+Thread.currentThread().getName()+"添加一个元素..");
Thread.sleep(500);
//A线程加到第五次的时候发出唤醒通知
if(list.size() == 5) {
System.out.println("已经发出了唤醒通知!");
//唤醒B线程
lock.notify();
}
}
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
},"A");
//线程B
Thread B = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
while(true) {
//在此处加锁,一直判断,如果容器大小不等于无我就一直等待
//如果等于无,就去锁外执行操作
synchronized(lock) {
if(list1.size() != 5) {
try {
lock.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
//if (list1.size() == 5) {
//如果等于五就打印收到通知
System.out.println("当前线程收到通知:"+Thread.currentThread().getName()+"list size = 5;B线程停止");
//抛出异常,停止B线程
throw new RuntimeException();
//}
}
}
},"B");
//先启动B线程,启动后不停的循环判断
B.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
A.start();
}
}
输出结果如下:
当前线程:A添加一个元素..
当前线程:A添加一个元素..
当前线程:A添加一个元素..
当前线程:A添加一个元素..
当前线程:A添加一个元素..
已经发出了唤醒通知!
当前线程:A添加一个元素..
当前线程:A添加一个元素..
当前线程:A添加一个元素..
当前线程:A添加一个元素..
当前线程:A添加一个元素..
当前线程收到通知:Blist size = 5;B线程停止
Exception in thread "B" java.lang.RuntimeException
at com.bfxy.thread.core.mianshi.ListAdd1$2.run(ListAdd1.java:184)
at java.lang.Thread.run(Unknown Source)
分析:A线程添加5次的时候,向B线程发出了通知,并且lock.notify()唤醒了B线程,但是B线程并没有马上执行,而是A线程继续执行到10此添加结束,然后B线程执行完成;
原因在于:B线程先start启动,进入synchronized(lock)后判断条件不符合,启动lock.wait()进入等待,wait方法释放了lock锁,然后A线程获取锁开始执行添加,当A线程添加到5次通知B线程,并且lock.notify()唤醒B线程,但是notify方法并不释放锁,所有A线程还在synchronized(lock)中继续执行,执行结束后释放锁,B线程获取锁后才开始执行,到执行完成,所有会有上述输出结果。(此方法未能完全实现需求)
方法二:CountDownLatch
public class ListAdd2 {
//volatile
//1.定义要添加数据的容器
private static List list = new ArrayList();
//2.追加方法
public void add() {
list.add("bfxy");
}
public int size() {
return list.size();
}
public static void main(String[] args) {
final ListAdd2 list2 = new ListAdd2();
//创建CountDownLatch对象,并定义要同时发出的消息数量
//如果CountDownLatch(2)填入2那么下面就用调用两次latch.countDown();
/*
* CountDownLatch(2)此处用意并非要求在同一个类中调用两次,场景为存在多个线程
* 的情况下应用,比如需要A和C线程同时发出通知,B线程才执行
*/
final CountDownLatch latch = new CountDownLatch(1);
//线程A
Thread A = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
for(int i=0;i<10;i++) {
list2.add();
System.out.println("当前线程:"+Thread.currentThread().getName()+"添加一个元素..");
Thread.sleep(500);
//A线程加到第五次的时候发出唤醒通知
if(list.size() == 5) {
System.out.println("已经发出了唤醒通知!");
//发出通知
latch.countDown();
}
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
},"A");
//线程B
Thread B = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
while(true) {
//在此处加锁,一直判断,如果容器大小不等于无我就一直等待
//如果等于无,就去锁外执行操作
if(list2.size() != 5) {
try {
//在条件不等于5的时候,阻塞B线程
latch.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("当前线程收到通知:"+Thread.currentThread().getName()+"list size = 5;B线程停止");
//抛出异常,停止B线程
throw new RuntimeException();
//}
}
}
},"B");
//先启动B线程,启动后不停的循环判断
B.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
A.start();
}
}
输出结果:
当前线程:A添加一个元素..
当前线程:A添加一个元素..
当前线程:A添加一个元素..
当前线程:A添加一个元素..
当前线程:A添加一个元素..
已经发出了唤醒通知!
当前线程:A添加一个元素..
当前线程收到通知:Blist size = 5;B线程停止
Exception in thread "B" java.lang.RuntimeException
at com.bfxy.thread.core.mianshi.ListAdd2$2.run(ListAdd2.java:112)
at java.lang.Thread.run(Unknown Source)
当前线程:A添加一个元素..
当前线程:A添加一个元素..
当前线程:A添加一个元素..
当前线程:A添加一个元素..
分析:结果显示运行过程中A线程添加5次后确实唤醒了B线程,并且B线程马上就执行完成了,但是A线程并未阻断,而是两个线程同时执行了,原因在于,CountDownLatch使用过程中的线程操作都是异步的,不做阻断;
方法三:volatile(最优解决方案)
public class ListAdd3 {
//volatile
//1.定义要添加数据的容器
private volatile static List list = new ArrayList();
//2.追加方法
public void add() {
list.add("bfxy");
}
public int size() {
return list.size();
}
public static void main(String[] args) {
final ListAdd3 list3 = new ListAdd3();
//线程A
Thread A = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
for(int i=0;i<10;i++) {
list3.add();
System.out.println("当前线程:"+Thread.currentThread().getName()+"添加一个元素..");
Thread.sleep(500);
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
},"A");
//线程B
Thread B = new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
while(true) {
if (list3.size() == 5) {
//如果等于五就打印收到通知
System.out.println("当前线程收到通知:"+Thread.currentThread().getName()+"list size = 5;B线程停止");
//抛出异常,停止B线程
throw new RuntimeException();
}
}
}
},"B");
//先启动B线程,启动后不停的循环判断
B.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
A.start();
}
}
输出结果:
当前线程:A添加一个元素..
当前线程:A添加一个元素..
当前线程:A添加一个元素..
当前线程:A添加一个元素..
当前线程:A添加一个元素..
当前线程收到通知:Blist size = 5;B线程停止
Exception in thread "B" java.lang.RuntimeException
at com.bfxy.thread.core.mianshi.ListAdd3$2.run(ListAdd3.java:73)
at java.lang.Thread.run(Unknown Source)
当前线程:A添加一个元素..
当前线程:A添加一个元素..
当前线程:A添加一个元素..
当前线程:A添加一个元素..
当前线程:A添加一个元素..
A线程不用判断是否添加5次,也不用添加通知,B线程自己可以监听判断,在A线程添加5次后启动执行,volatile体现了多线程的可见性,加上volatile关键字修饰的一个对象或者一个容器如果被修改,那么对象或者容器就在多个线程中可见,各个线程就即时的获得了通知。