这篇关于多线程的博客,其中有不当之处,请及时评论告知,相互学习,共同进步。
从事Web开发的朋友肯定或多或少有接触过Ajax这个技术。百度百科是这样说Ajax的:Ajax 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。在我第一次接触Ajax的时候,感觉打开了一扇新大门,前端界面刷新竟然可以不是同步的!!原来我在刷新界面的时候可以只刷新一小块的区域。是不是很神奇呢?
Ajax是怎么实现可以只刷新一小部分的呢?可能很多朋友已经猜到了,没错,就是我今天要分享的其中一个内容——异步!!
我们先看一下百度的解释:异步处理:与同步处理相对,异步处理不用阻塞当前线程来等待处理完成,而是允许后续操作,直至其它线程将处理完成,并回调通知此线程
在这句话中有一个很重要词语:回调!!在我的理解中,异步最大的特征就是回调。那么回调是啥呢?回调就是执行完某个操作之后给出一个通知信息。我们来看一段Ajax代码
function Click() { $.ajax({ cache: false, //缓存 type: 'post', //传参方式 url: '/AjaxAdminCenter-' + null, //传到哪里去,调用后台的程序将参数传递过去,通过路由的形式传递 data: { inquire: $("#inquire").val() },//传参数据,通过QueryString的方式传递?/eee的方式 dataType: 'html', //后台返回参数的格式,返回的是一个含有html的网页 success: function (data) { $("#Contenter").html(data); }, error: function () { $("#Contenter").html("Ajax调用失败了!") } });
在这段代码中我们通过Url字段说明了我们要将这和Post请求传给哪个函数(AjaxAdminCenter)进行处理,然后程序将Data中的数据通过Jquery的形式传给了这个函数(AjaxAdminCenter)做一些处理。当AjaxAdminCenter函数将任务执行完之后再将结果返回到我们的这个Ajax函数中,我们规定了AjaxAdminCenter这个函数给我们返回一个html类型的数据,之后我们在success中对返回来的数据进行一些处理。
后台函数AjaxAdminCenter结果返回来就是回调。而异步就是通过回调来实现的。说了这么多,异步到底是啥呀!
从一个小场景中来讲解一下异步吧
有一天,公司接到了一个网站定制开发,你的项目经理对你的经理说,这个项目不算难搞,就由你们小组来完成这个小项目吧。我去跟我们的大Boss汇报一下这个项目的情况,做完记着告诉我。
项目很小,一上午就完成了,组长去跟经理汇报的时候经理正在跟老板绘声绘色的讲解怎么拿下的这个项目,有多么艰难,你小组长一看,这势头我现在进去也没法汇报呀,先等等吧!项目经理说了好一会儿,累了,正喝水休息呢!小组长进去了,跟项目经理说,经理,我们的项目完工了,跟你汇报一下!。经理说:小伙子干得不错。
我们现在将这个小场景转换成程序的设计思路来理解。
我们把项目经理看成一个主线程,他的任务就是拿下项目,开发项目,向BOss汇报接项目情况。假设他拿下项目之后直接参与了项目的开发,等到项目开发完之后再一起向大Boss汇报情况。像不像我们没使用用Ajax处理时候的界面传值方式?整个一大坨数据全一股脑地返回去!
而当他将这个项目分给小组长之后,自己可以接着去向大Boss汇报项目的情况(不包括开发情况)。当他在跟大Boss汇报间歇的时候,小程序开发完了,通知小组长一声,小组长回答一个真棒,这个动作对应着Ajax中的回调success块的代码。对应着界面只刷新一小部分
你可能也会说了,这不就是多线程嘛,一个主线程跟大Boss汇报,一个线程开发程序。这哪是异步呀,忽悠人!!
对,这就是我接下来要说的,开多线程的目的是什么?提高工作效率吧。怎么提高呀,一人干一件吧。所以,开多线程的目的是为了实现异步,而多线程只是异步的一种实现方式!!异步也可以使用第三方插件来实现,例如:消息队列
ok,讲清楚了异步与多线程的关系,我们来看一下多线程。
在Java中有两种实现方式:一个是继承Thread类,一个是实现Runnable接口
当我们去看源码实现时,会发现Thread类还是实现的Runnable接口。
public class Runable implements Runnable{ @Override public void run() { for (int i=0;i<100;i++) { //Runable接口没有Thread类中的get方法,所以需要先拿到这个线程才能获得名称 System.out.println(Thread.currentThread().getName()+":"+i); } } }
public class myRunableDemo { public static void main(String[] args) { //创建MyRunable类的对象 Runable my = new Runable(); //创建Thread类的对象,把MyRunable类对象作为构造方法的参数 // Thread t1 = new Thread(my); // Thread t2 = new Thread(my); Thread t1 = new Thread(my,"高铁"); Thread t2 = new Thread(my,"飞机"); //启动线程 t1.start(); t2.start(); } }
上面的代码是实现一个简单的多线程。
ok,接下来让我们看看多线程是不是真的能提升工作效率呢?
定义一个场景:我刚毕业出来,想着自己做饭,但是锅碗瓢盆啥的都没有,正好赶上了双十一购物节,我打算在网上买锅碗瓢盆,去实体店买点蔬菜,别问我为啥不都在实体店买!因为我任性!!!哼,小拳拳锤你胸口!
我得等所有的东西都齐了才能做饭对吧
ok,上代码!
单线程:
public class ThreadDemo1 { public static void main(String[] args) throws InterruptedException { long startTime = System.currentTimeMillis();//计时方法 //第一步:网购厨具 OnlineShopping thread = new OnlineShopping(); thread.start(); thread.join(); // ,阻塞主线程,保证厨具送到,相当于单线程 // 第二步 去超市购买食材 Thread.sleep(2000); // 模拟购买食材时间 Shicai shicai = new Shicai(); System.out.println("第二步:食材到位"); // 第三步 用厨具烹饪食材 System.out.println("第三步:开始展现厨艺"); cook(thread.chuju, shicai); System.out.println("总共用时" + (System.currentTimeMillis() - startTime) + "ms"); } //网购厨具线程 static class OnlineShopping extends Thread { private Chuju chuju; @Override public void run() { System.out.println("第一步:下单"); System.out.println("第一步:等待送货"); try { Thread.sleep(5000); // 模拟送货时间 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("第一步:快递送到"); chuju = new Chuju(); } } //用出局烹饪食材 static void cook(Chuju chuju,Shicai shicai){} //厨具类 static class Chuju{} //食材类 static class Shicai{} }
第一步:下单
第一步:等待送货
第一步:快递送到
第二步:食材到位
第三步:开始展现厨艺
总共用时7003ms
接下来时多线程同时操作的代码
import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class ThreadDemo2 { public static void main(String[] args) throws InterruptedException, ExecutionException { //打一个计时 long startTime = System.currentTimeMillis(); // 第一步 网购厨具,,,,Callable计算一个结果,如果不能这样做,就会抛出一个异常。 Callable<Chuju> onlineShopping = new Callable<Chuju>() { @Override public Chuju call() throws Exception { System.out.println("第一步:下单"); System.out.println("第一步:等待送货"); Thread.sleep(5000); // 模拟送货时间 System.out.println("第一步:快递送到"); return new Chuju(); } }; FutureTask<Chuju> task = new FutureTask<>(onlineShopping);//创建一个 FutureTask ,它将在运行时执行给定的 Callable new Thread(task).start(); // 第二步 去超市购买食材 Thread.sleep(2000); // 模拟购买食材时间 Shicai shicai = new Shicai(); System.out.println("第二步:食材到位"); // 第三步 用厨具烹饪食材 if (!task.isDone()) { // 联系快递员,询问是否到货 System.out.println("第三步:厨具还没到,我正在等着厨具,心情好就等着(心情不好就调用cancel方法取消订单)"); } Chuju chuju = task.get();//获取任务执行结果的方法,这里就类似与一个回调函数。 System.out.println("第三步:厨具到位,开始展现厨艺"); cook(chuju, shicai); System.out.println("总共用时" + (System.currentTimeMillis() - startTime) + "ms"); } // 用厨具烹饪食材 static void cook(Chuju chuju, Shicai shicai) {} // 厨具类 static class Chuju {} // 食材类 static class Shicai {} }
第一步:下单
第一步:等待送货
第二步:食材到位
第三步:厨具还没到,我正在等着厨具,心情好就等着(心情不好就调用cancel方法取消订单)
第一步:快递送到
第三步:厨具到位,开始展现厨艺
总共用时5006ms
很多朋友可能在这里看不出异步与多线程之间的联系,我在整理的时候准备了一个小的异步Demo
在看到下面代码之前你还是可以将异步跟多线程看成两个有联系的但是不明确的东西,接下来的代码是实现异步的,
场景很简单:A要问B一个问题,B给A做出回答,在B做出回答之前,A要出去玩耍
ok,看代码
首先我们定义一个回调函数的接口CallBack
public interface CallBack { public void solve(String result); }
然后我们定义A类,它实现了回调函数接口
public class A implements CallBack{ private B b; public A(B b) { this.b=b; } public void ask(final String question) { System.out.println("A问了B一个问题"); //启动一个线程执行B里的方法来解答这个问题 new Thread(()->{ //B想要帮A处理东西,就必须知道谁让自己处理的,因为回调这个A中的solve方法来返回结果 //也要传入问题呀,所以传的参数就是this,question b.executeMessage(A.this,question); }).start(); play(); } public void play() { System.out.println("我要出去玩了"); } //回调函数 @Override public void solve(String result) { System.out.println("B告诉A的答案是---》"+result); } }
再接下来我们定义B类,回答A的问题
public class B { public void executeMessage(CallBack callBack,String question) { System.out.println(callBack.getClass()+"问的问题--》"+question); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } String result ="答案是2"; callBack.solve(result); } }
接下来,我们弄一个测试类
public class test { public static void main(String[] args) { B b = new B(); A a = new A(b); a.ask("1+1=?"); } }
ok,我们来看执行结果
A问了B一个问题 我要出去玩了 class itThread2.A问的问题--》1+1=? B告诉A的答案是---》答案是2
有没有发现,多线程的实现跟这个异步的实现是一样的呀,所以我说:多线程实现的目的是异步,异步的方法之一是多线程
ok,到这里差不多这篇文章就要结束了,还有一个小问题,线程之间的信息是不互通的,线程之间的通信才是难点,今天先理清楚异步与多线程的关系,关于线程之间的通信,我会在下一篇博客中简单的探讨一下。
最后一点题外话,这篇文章选择全程使用Java是因为在C#中对异步做了一个封装,不利于了解异步与多线程之间的关系,我们说完C#中的异步函数就结束文章。
C#中的异步函数
在C#中异步是用async/await来修饰的,await后面的代码就相当于回调函数,这种实现方式在底层是使用状态机的转换实现异步的,我还没品明白具体是怎么实现异步的。上代码,你们自己品品~~
public static class AwaitAsyncILSpy { public static void Show() { Console.WriteLine($"start1 {Thread.CurrentThread.ManagedThreadId.ToString("00")}"); Async(); Console.WriteLine($"aaa2 {Thread.CurrentThread.ManagedThreadId.ToString("00")}"); //Console.WriteLine($"aaa3 {Thread.CurrentThread.ManagedThreadId.ToString("00")}"); } public static async void Async() { Console.WriteLine($"ddd5 {Thread.CurrentThread.ManagedThreadId.ToString("00")}"); await Task.Run(() => { //Thread.Sleep(500); Console.WriteLine($"bbb3 {Thread.CurrentThread.ManagedThreadId.ToString("00")}"); //Console.WriteLine($"bbb4 {Thread.CurrentThread.ManagedThreadId.ToString("00")}"); //Console.WriteLine($"bbb5 {Thread.CurrentThread.ManagedThreadId.ToString("00")}"); }); Console.WriteLine($"ccc4 {Thread.CurrentThread.ManagedThreadId.ToString("00")}"); } }
class Program { static void Main(string[] args) { AwaitAsyncILSpy.Show(); Console.WriteLine("Hello word"); Console.ReadLine(); } }
执行结果:
start1 08 ddd5 08 aaa2 08 Hello word bbb3 09 ccc4 09
@2020/01/06修改:
async声明这个函数是异步的,await表示等待后面的后面的代码块(函数)执行完,拿到返回值之后,再执行await这一行代码下面的代码,在async修饰的函数中,虽然修饰为异步的,但是在函数内部是同步的,对于调用这个函数的函数来说这个函数是异步的。因为在等待期间,调用这个函数的函数可以执行,这个函数下面的代码。
// 2s 之后返回双倍的值 function doubleAfter2seconds(num) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(2 * num) }, 2000); } ) }
async function testResult() { let result = await doubleAfter2seconds(30); console.log(result); } testResult(); console.log('先执行');
这行代码会在等待doubleAfter2seconds(30)的实惠执行console.log('先执行')。
最后引一篇大学兄弟写的async/await实战代码
https://blog.csdn.net/weixin_45286744/article/details/103399219?utm_source=app&from=timeline
ok,今天的分享结束喽,下一篇讲解线程间的通信,加不加锁的问题