当前位置: 首页 > 科技观察

面试官:说说线程间的通信

时间:2023-03-19 20:19:35 科技观察

前言合理使用多线程可以更好的利用服务器资源。一般来说,每个线程内部都有自己的上下文,它们之间互不干扰。但是有时候我们需要多个线程相互配合,所以我们需要掌握线程的通信方式。锁首先,我们来了解一下锁的概念。我们之前也遇到过,只是没有详细解释。今天,我们来澄清一下这个概念。在Java多线程中,一把锁只能同时被一个线程获取。如果其他线程想要获取它,必须等待该线程释放锁。这时候就涉及到同步的概念。因为有了锁机制,我们可以让线程同步执行。下面以打印数字为例,看看区别。没有锁:publicstaticvoidmain(String[]args){Threadt1=newThread(()->{System.out.println("1");try{Thread.sleep(1000);}catch(InterruptedExceptione){e.printStackTrace();}});Threadt2=newThread(()->{System.out.println("2");try{Thread.sleep(1000);}catch(InterruptedExceptione){e.printStackTrace();}});t1.开始();t2.开始();}输出:21并且每次运行的结果都不同。下锁:publicstaticfinalObjectlock=newObject();publicstaticvoidmain(String[]args){Threadt1=newThread(()->{synchronized(lock){System.out.println("1");try{Thread.sleep(1000);}catch(InterruptedExceptione){e.printStackTrace();}}});Threadt2=newThread(()->{synchronized(lock){System.out.println("2");try{Thread.sleep(1000);}catch(InterruptedExceptione){e.printStackTrace();}}});t1.开始();t2.start();}Output:12可以看出,无论我执行多少次,结果都是一样的,而且执行过程中还是有一个等待的效果。我们这里使用synchronized关键字来给对象锁加锁。只有t1执行完释放锁,t2才能获取到锁,然后执行。这里需要注意的是,synchronized会一直尝试获取锁,直到获取到,所以有时候我们的程序出现异常,记得释放锁,否则会继续消耗服务器资源。wait¬ify上一节介绍了wait和notify,现在就来说说吧。事实上,两者都在等待通知机制。notify()方法随机唤醒一个等待线程。notifyAll()将唤醒所有等待的线程。我们还是通过上面的例子给大家演示一下。publicstaticvoidmain(String[]args){Threadt1=newThread(()->{synchronized(lock){try{lock.wait();System.out.println("1");}catch(InterruptedExceptione){e.printStackTrace();}}});Threadt2=newThread(()->{synchronized(lock){System.out.println("2");try{Thread.sleep(1000);}catch(InterruptedExceptione){e.printStackTrace();}}});t1.开始();t2.开始();}实际输出:发现2先输出2,然后线程阻塞,执行不到1。到这里大家可以猜到这个wait的作用是什么了。我们可以大致猜想,这个等待实际上执行的是释放锁的操作。调用后进入等待阶段。T2获得锁并开始执行。这个时候t1还在等待,所以我们需要唤醒它。publicstaticvoidmain(String[]args){Threadt1=newThread(()->{synchronized(lock){try{lock.wait();System.out.println("1");}catch(InterruptedExceptione){e.printStackTrace();}}});Threadt2=newThread(()->{synchronized(lock){System.out.println("2");try{Thread.sleep(1000);//唤醒当前等待的线程lock.notify();}catch(InterruptedExceptione){e.printStackTrace();}}});t1.开始();t2.start();}output:21foundnormal,有输出。这里大家要注意的是,这个机制需要依赖同一个对象锁,也就是这里的锁对象,底层的wait和notify调用都是native方法。publicfinalnativevoidwait(longtimeout)throwsInterruptedException;publicfinalnativevoidnotify();semaphore我们也可以使用semaphore让线程之间相互协作。这里我就介绍一下volatile实现的信号量。volatile关键字可以保证内存的可见性。如果用volatile关键字声明一个变量,并且变量的值在一个线程中改变,那么其他线程可以立即看到改变的值。classA{//privatestaticvolatileintnum=0;私有静态整数=0;静态类ThreadA实现Runnable{@Overridepublicvoidrun(){while(num<5){if(num==4){System.out.println("threadA:"+num);}}}}staticclassThreadBimplementsRunnable{@Overridepublicvoidrun(){while(num<5){System.out.println("threadB:"+num);数=数+1;}}}}//运行publicstaticvoidmain(String[]args)throwsInterruptedException{newThread(newA.ThreadA()).start();线程.睡眠(1000);newThread(newA.ThreadB()).start();}首先不加volatile。threadB:0threadB:1threadB:2threadB:3threadB:4加volatile。threadB:0threadB:1threadB:2threadB:3threadB:4threadA:5我们可以发现A可以实时看到num值并输出。其实我们在使用volatile的时候,需要进行原子操作。这里只是给大家演示一下。不要在实践中这样使用它。说了这么多,它是用来做什么场景的呢?有时候我们线程很多,需要共享同一个资源,用之前的wait和notify显然有点麻烦,这时候就可以用了。Channel其实我们也可以使用channel来实现通信。其实这个属于IO的知识。这里给大家简单演示一下,如何在多线程中使用,主要是借助PipedWriter和PipedReader。publicstaticvoidmain(String[]args)throwsIOException{PipedWriterwriter=newPipedWriter();PipedReaderreader=newPipedReader();writer.connect(读者);Threadt1=newThread(()->{intrec=0;try{while((rec=reader.read())!=-1){System.out.print("\nt1收到----->"+(char)rec);}}catch(IOExceptione){e.printStackTrace();}});Threadt2=newThread(()->{try{writer.write("你好,我是t2");}catch(IOExceptione){e.printStackTrace();}finally{try{writer.close();}catch(IOExceptione){e.printStackTrace();}}});t1.开始();t2.开始();}输出:t1收到----->ht1收到----->et1收到----->lt1收到----->lt1收到----->ot1收到----->t1收到----->我的t1收到----->它isreceivedbyt1----->Receivedbyt1----->Receivedbytt1----->2进程结束,退出码为0ThreadLocalThreadLocal是本地线程拷贝变量工具类内部是要维护的“弱参考”地图。它为每个线程创建一个“副本”。每个线程都可以访问自己的内部副本变量。最常用的方法是set和get。让我为您演示一下。publicstaticvoidmain(String[]args)throwsInterruptedException{ThreadLocallocal=newThreadLocal<>();Threadt1=newThread(()->{local.set("t1");System.out.println(local.get());});线程t2=newThread(()->{local.set("t2");System.out.println(local.get());});t1。开始();t2.start();}Output:t2t1othermethods其实就是我们之前讲的join(),sleep()...这些其实都是一部分内容,总的来说是相互配合的,具体的用法在上一篇文章中可以看到,这里就不一一介绍了。