同步与异步的定义
同步:同步是指一个进程在执行某个请求的时候,如果该请求需要一段时间才能返回信息,那么这个进程会一直等待下去,直到收到返回信息才继续执行下去。
异步:异步是指进程不需要一直等待下去,而是继续执行下面的操作,不管其他进程的状态,当有信息返回的时候会通知进程进行处理,这样就可以提高执行的效率了,即异步是我们发出的一个请求,该请求会在后台自动发出并获取数据,然后对数据进行处理,在此过程中,我们可以继续做其他操作,不管它怎么发出请求,不关心它怎么处理数据。
举例说明:
setTimeout(function cbFn(){
console.log('learnInPro');
}, 1000);
console.log('sync things');
setTimeout就是一个异步任务,当JS引擎顺序执行到setTimeout的时候发现他是个异步任务,则会把这个任务挂起,继续执行后面的代码。直到1000ms后,回调函数cbFn才会执行,这就是异步,在执行到setTimeout的时候,JS并不会傻呵呵的等着1000ms执行cbFn回调函数,而是继续执行了后面的代码。
JavaScript
为什么要在JS中使用异步
由于javascript是单线程的,只能在JS引擎的主线程上运行的,所以js代码只能一行一行的执行,不能在同一时间执行多个js代码任务,这就导致如果有一段耗时较长的计算,或者是一个ajax请求等IO操作,如果没有异步的存在,就会出现用户长时间等待,并且由于当前任务还未完成,所以这时候所有的其他操作都会无响应。
为什么JS不设计成多线程的
这主要跟javascript的历史有关,js最开始只是为了处理一些表单验证和DOM操作而被创造出来的,所以主要为了语言的轻量和简单采用了单线程的模式。多线程模型相比单线程要复杂很多,比如多线程需要处理线程间资源的共享问题,还要解决状态同步等问题。
如果JS是多线程的话,当你要执行往div中插入一个DOM的操作的同时,另一个线程执行了删除这个div的操作,这个时候就会出现很多问题,我们还需要为此增加锁机制等。
那么常见的异步模式有哪些呢?
- 回调函数
- 事件监听
- 发布/订阅模式(又称观察者模式)
- promise
后来ES6中,引入了Generator函数;ES7中,async/await更是将异步编程带入了一个全新的阶段。
具体JS是如何实现异步操作的呢?
答案:就是JS的事件循环机制(Event Loop)。
那么什么是JS的事件循环机制?
当JS解析执行时,会被引擎分为两类任务,同步任务(synchronous) 和 异步任务(asynchronous)。
对于同步任务来说,会被推到执行栈按顺序去执行这些任务。
对于异步任务来说,当其可以被执行时,会被放到一个 任务队列(task queue) 里等待JS引擎去执行。
当执行栈中的所有同步任务完成后,JS引擎才会去任务队列里查看是否有任务存在,并将任务放到执行栈中去执行,执行完了又会去任务队列里查看是否有已经可以执行的任务。这种循环检查的机制,就叫做事件循环(Event Loop)。
对于任务队列,其实是有更细的分类。其被分为 微任务(microtask)队列 & 宏任务(macrotask)队列
宏任务: setTimeout、setInterval等,会被放在宏任务(macrotask)队列。
微任务: Promise的then、Mutation Observer等,会被放在微任务(microtask)队列。
Event Loop的执行顺序是:
首先执行执行栈里的任务。 执行栈清空后,检查微任务(microtask)队列,将可执行的微任务全部执行。
取宏任务(macrotask)队列中的第一项执行。 回到第二步。 注意: 微任务队列每次全执行,宏任务队列每次只取一项执行。
举个例子:
setTimeout(() => {
console.log('我是第一个宏任务');
Promise.resolve().then(() => {
console.log('我是第一个宏任务里的第一个微任务');
});
Promise.resolve().then(() => {
console.log('我是第一个宏任务里的第二个微任务');
});
}, 0);
setTimeout(() => {
console.log('我是第二个宏任务');
}, 0);
Promise.resolve().then(() => {
console.log('我是第一个微任务');
});
console.log('执行同步任务');
最后的执行结果是:
// 执行同步任务
// 我是第一个微任务
// 我是第一个宏任务
// 我是第一个宏任务里的第一个微任务
// 我是第一个宏任务里的第二个微任务
// 我是第二个宏任务
java中的异步
volatile关键字
volatile:作用主要有如下两个:
1.线程的可见性:当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。
2. 顺序一致性:禁止指令重排序。
应用场景:检查一个应用执行关闭或中断状态。因为此关键字拒绝了虚拟对一个变量多次赋值时的优化从而保证了虚拟机一定会检查被该关键字修饰的变量的状态变化。
CountDownLatch类
CountDownLatch:CountDownLatch类主要有两个方法:
1. countDown()和await()。countDown()方法用于使计数器减一,其一般是执行任务的线程调用,
2. await()方法则使调用该方法的线程处于等待状态,其一般是主线程调用。
3. 这里需要注意的是,
4. countDown()方法并没有规定一个线程只能调用一次,当同一个线程调用多次countDown()方法时,每次都会使计数器减一;
5. await()方法也并没有规定只能有一个线程执行该方法,如果多个线程同时执行await()方法,那么这几个线程都将处于等待状态,并且以共享模式享有同一个锁。
应用场景:控制在一组线程操作执行完成之前当前线程一直处于等待。例如在主线程中执行await()方法阻塞主线程,在工作线程执行完逻辑后执行countDown()方法。
原文链接:https://blog.csdn.net/yangtongli2012/article/details/51626284
本文示例场景:
1,从控制台发送消息到消息服务器(由一个队列模拟)。
2,将消息队列写入到文件(对写文件的操作设置延时以模拟性能瓶颈)。
3,消息服务器作为控制台和文件写入之间的缓冲区。
示例代码:
注:往消息队列添加消息可以通过for循环一次性加入,本文为了便于观察文件和队列的变化而采用了控制台输入,实际写一行文件记录速度应该高于手速,所以本文示例中增加了线程sleep时间。
package org.wit.ff.ch2;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
/**
*
* <pre>
* 简单异步处理示例.
* </pre>
*
* @author F.Fang
* @version $Id: AsyncHandler.java, v 0.1 2014年10月23日 下午11:37:54 F.Fang Exp $
*/publicclass AsyncHandler {
/**
* 控制资源释放.
*/private CountDownLatch latch;
/**
* 处理完成标识.
*/privatevolatile boolean handleFinish;
/**
* 消息写入本地文件完成.
*/privatevolatile boolean sendFinish;
/**
* 阻塞队列.
*/private BlockingQueue<String> queue;
private BufferedWriter bw;
public AsyncHandler(CountDownLatch latch) {
this.latch = latch;
/**
* 使用链表实现.
*/
queue = new LinkedBlockingQueue<String>();
File file = new File("E:/hello.txt");
try {
bw = new BufferedWriter(new FileWriter(file));
} catch (IOException e) {
thrownew RuntimeException(e);
}
}
publicvoid handle() {
// 模拟性能瓶颈的执行过程,3s处理一条消息.new Thread() {
publicvoid run() {
while (!handleFinish) {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e1) {
// 不做处理. }
String s = queue.peek();
if (s != null) {
queue.poll();
try {
bw.write(s);
bw.newLine();
} catch (IOException e) {
}
}
// 若队列为空并且消息发送完成.if (queue.isEmpty() && sendFinish) {
// 计数器1->0 latch.countDown();
// 让处理过程结束.
handleFinish = true;
break;
}
}
}
}.start();
}
/**
*
* <pre>
* 给出消息发送完成的标识.
* </pre>
*
*/publicvoid sendFinish() {
sendFinish = true;
}
/**
*
* <pre>
* 资源释放.
* </pre>
*
*/publicvoid release() {
System.out.println("release!");
if (bw != null) {
try {
bw.close();
} catch (IOException e) {
// TODO 打印日志. }
}
//其实使用queue = null就够了.if (queue != null) {
queue.clear();
queue = null;
}
}
/**
*
* <pre>
* 往队列发送消息.
* </pre>
*
* @param text
*/publicvoid sendMsg(String text) {
if (text != null && !text.isEmpty()) {
queue.add(text);
}
}
publicstaticvoid main(String[] args) {
CountDownLatch latch = new CountDownLatch(1);
AsyncHandler handler = new AsyncHandler(latch);
handler.handle();
// 做一次检查.
Scanner scanner = new Scanner(System.in);
while (true) {
String text = scanner.next();
// 若用户选择退出.if ("exit".equals(text)) {
// 表示消息已经发送完成. handler.sendFinish();
break;
}
handler.sendMsg(text);
}
try {
// 阻塞主线程等待消息写入到本地文件完成.
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 释放资源 文件流,队列. handler.release();
// 关闭控制台输入. scanner.close();
}
}