多线程编程学习三(线程间通信)

一、概要

线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体,线程间的通信就是成为整体的必用方案之一。可以说,使线程进行通信后,系统之间的交互性会更强大,在大大提高cpu利用率的同时还会使程序员对各线程任务在处理过程中进行有效的把控和监督。

二、等待/通知机制

1、"wait/notify"机制:等待/通知机制,wait使线程暂停运行,而notify 使暂停的线程继续运行。用一个厨师和服务员的交互来说明:

(1) 服务员取到菜的时间取决于厨师,所以服务员就有“等待”(wait)的状态。

(2) 厨师将菜放在“菜品传递台”上,其实就相当于一种通知(notify),这时服务员才可以拿到菜并交给就餐者。

2、wait()

(1) 使当前执行代码的线程进行等待。wait()方法是Object类的方法,该方法用来将当前线程置入“预执行队列”中,并且在wait()所在的代码行处停止执行,直到接收通知或被中断为止。

(2) 在调用wait()方法之前,线程必须获得该对象的对象级别锁,即只能在同步方法或同步块中调用wait()方法,否则抛出IllegalMonitorStateException异常。(属于Runtime的一个子类,不需要try-catch 语句进行捕捉异常)

(3) 在调用wait()方法之后,当前线程释放锁,而此对象会进入线程等待池中,等待被唤醒。在从wait()返回前,线程与其他呈wait线程竞争重新获得锁。

(4) wait()方法可以被interrupt 打断并抛出InterruptedException。

(5) wait(long):带一个参数的wait(long)方法的功能是等待某一时间内是否有线程对锁进行唤醒,如果超过这个时间则自动唤醒。

3、notify()

(1) 用来通知那些可能等待该对象的对象锁的其他线程。如果有多个线程等待,则由线程规划器随机挑选出其中一个呈wait状态的线程,对其发出通知notify,并使它等待获取该对象的对象锁。(注意!这里说的是等待,即在执行完notify()方法后,当前线程不会马上释放该对象锁,即wait()状态的线程也不会马上获得对象锁,需要将synchronized 代码块中的代码执行完后才释放锁!)

(2)也要在同步方法或同步块中调用,即在调用前,线程也必须获得该对象的对象级别锁,否则也会抛出IllegalMonitorStateException.

(3)当notify()发出通知,却没有wait()线程在等待时,则不作作用。

4、notifyAll()

(1) 可以使所有正在等待队列中的 等待同一共享资源(即同一个锁) 的"全部"线程从等待状态退出,进入可运行状态。

5、

6、假死:“假死”现象其实就是线程进入WAITING等待状态。如果全部线程都进入WAITING状态,则程序就不再执行任何功能了,整个项目呈停止状态。 出现这样的原因是因为:比如多个生产者和多个消费者的问题,“生产者”可能唤醒“生产者”,“消费者”可能唤醒“消费者”,唤醒了同类,导致线程不断在等待。怎么解决这个问题呢?将notify() 改成 notifyAll()方法即可,也就是将异类一同唤醒就可以了。

7、 Jave中 管道流(pipeStream)是一种特殊的流,可用于在不同的线程中直接传送数据。一个线程发送数据到输出管道,另一个线程从输入管道中读数据。通过使用管道,实现不同线程间的通信,而无须借助于类似临时文件之类的东西。JDK中提供了四个类来使线程间可以通信,其中包括字节流(PipedOutputStream、PipedInputStream)和字符流(PipedWriter、PipedReader)。

public class Run {
    public static void main(String[] args) {
        try {
            WriteData writeData = new WriteData();
            ReadData readData = new ReadData();
            PipedOutputStream outputStream = new PipedOutputStream();
            PipedInputStream inputStream = new PipedInputStream();

            outputStream.connect(inputStream);//使两个Stream之间产生通信链接,这样才可以将数据进行输入输出

            ThreadRead threadRead = new ThreadRead(readData, inputStream);
            threadRead.start();
            Thread.sleep(1000);
            ThreadWrite threadWrite = new ThreadWrite(writeData, outputStream);
            threadWrite.start();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

三、方法join的使用

1、在很多情况下,主线程创建并启动子线程,如果子线程中要进行大量的耗时计算,主线程往往将早于子线程结束之前结束。这时,如果主线程想等待子线程执行完成之后再结束,比如子线程处理一个数据,主线程要取得这个数据中的值,就要用到join()方法了。

2、join()的作用是等待线程销毁,而使 当前线程进行无限期的阻塞,等待join()的线程销毁后再继续执行当前线程的代码。

3、同样的,join()方法可以被interrupt()方法打断并抛出InterruptedException异常。

4、join 与 synchronized 的区别?

(1) join()在内部使用wait() 方法进行等待。

(2) synchronized 关键字使用的是“对象监视器”原理作为同步。

5、方法join(long) 和 sleep(long)的区别?

(1) join(long)内部采用wait(long)方法实现,当执行wait(long)方法后,当前线程的锁被释放,那么其他线程也可以调用此线程中的同步方法了。即 join(long)之后,该线程释放锁,又需要和其他线程去争抢锁的资源。

(2) Thread.sleep(long)方法不释放锁。

四、类 ThreadLocal 的使用

1、变量值的共享可以使用public static 变量的形式,所有的线程都使用同一个public static 变量。如果想实现每一个线程都有自己的共享变量该如何解决呢?类ThreadLocal解决的就是每个线程绑定自己的值,可以将ThreadLocal类比喻成全局存放数据的盒子,盒子中可以存放每个线程的私有数据。

2、类ThreadLocal 具有隔离性,即每个线程都可以存入自己线程的数据而互不影响,而取到的也是自己线程存入的数据。

五、类InheritableThreadLocal的使用

1、InheritableThreadLocal类继承于ThreadLocal类,所以它具有ThreadLocal类的特性,但又是一种特殊的ThreadLocal,其特殊性在于InheritableThreadLocal变量值会自动传递给所有子线程,而普通ThreadLocal变量不行;而且,通过重写这个类中的 childValue 方法,子线程的值可以作为父线程值的一个任意函数。

备注:

(1)  什么是子线程?

包含在 Thread thread = new Thread(new ThreadStart(delegate{ 
}));里面均视为子线程。(个人理解)

(2) 什么是主线程?

UI界面和Main函数均为主线程,除了“不包含在Thread里面的程序”均可 视为主线程。(个人理解)

时间: 2024-10-08 10:34:21

多线程编程学习三(线程间通信)的相关文章

多线程编程学习笔记——线程同步(三)

接上文 多线程编程学习笔记——线程同步(一) 接上文 多线程编程学习笔记——线程同步(二) 七.使用Barrier类 Barrier类用于组织多个线程及时在某个时刻会面,其提供一个回调函数,每次线程调用了SignalAndWait方法后该回调函数就会被执行. 1.代码如下: using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; //

多线程编程学习笔记——线程池(一)

接上文 多线程编程学习笔记——线程同步(一) 接上文 多线程编程学习笔记——线程同步(二) 接上文 多线程编程学习笔记——线程同步(三) 创建多线程操作是非常昂贵的,所以每个运行时间非常短的操作,创建多线程进行操作,可能并不能提高效率,反而降低了效率. 如果你有非常多的执行时间非常短的操作,那么适合作用线程池来提高效率,而不是自行创建多线程. 线程池,就是我们先分配一些资源到池子里,当我们需要使用时,则从池子中获取,用完了,再放回池子里. .NET中的线程池是受CLR管理的,TheadTool类

黑马程序员——JAVA基础之Day24 多线程 ,死锁,线程间通信 ,线程组,线程池,定时器。

------- android培训.java培训.期待与您交流! ---------- Lock()实现提供了比使用synchronized方法和语句可获得更广泛的锁定操作. private Lock lock =new ReentrantLock(); 被锁的代码要用   lock.lock()                lock.unlock()    包括.其中用try   ...finally包围 同步:效率低,如果出现同步嵌套,会出现死锁.  但是安全. 死锁问题:两个或者两个以上

多线程编程-- part 4 线程间的通信

线程间的相互作用 线程之间需要一些协调通信,来共同完成一件任务. Object类相关的方法:notify(),notifyAll(),wait().会被所有的类继承,这些方法是final不能被重写.他们操控的是每个对象都会有的锁,如果不在synchronized里面调用wait,notify,notifyAll方法,运行时会报异常:要调用对象的这些方法,必须先获得这个对象的锁. wait(): 使得当前线程必须等待,进入到等待序列,等到另外一个线程调用notify()或者notifyAll()激

Java多线程之死锁与线程间通信简单案例

死锁定义 死锁是指两个或者多个线程被永久阻塞的一种局面,产生的前提是要有两个或两个以上的线程,并且来操作两个或者多个以上的共同资源:我的理解是用两个线程来举例,现有线程A和B同时操作两个共同资源a和b,A操作a的时候上锁LockA,继续执行的时候,A还需要LockB进行下面的操作,这个时候b资源在被B线程操作,刚好被上了锁LockB,假如此时线程B刚好释放了LockB则没有问题,但没有释放LockB锁的时候,线程A和B形成了对LockB锁资源的争夺,从而造成阻塞,形成死锁:具体其死锁代码如下:

Java多线程-管道流实现线程间通信

管道流 在Java语言中提供了各种各样的输入/输出流 Stream ,使我们能够很方便地对数据进行操作,其中管道流是一种特殊的流,用于在不同线程间直接传送数据.一个线程发送数据到输出管道流,另一个线程从输入管道流中读取数据. 通过使用管道,实现不同线程间的通信,而无须借助于类似临时文件之类的东西. 字节流 PipedInputStream 和 PipedOutputStream 字符流 PipedReader 和 PipedWriter 示例: public class PipeStreamTe

多线程编程学习五(线程池的创建)

一.概述 New Thread的弊端如下:       a.每次New Thread新建对象性能差.       b.线程缺乏统一的管理,可能无限制的新建线程,相互之间竞争,极可能占用过多的系统资源导致死机 或者 OOM.       c.缺乏更多功能,如定时执行.定期执行.线程中断. Java提供的四种线程池的好处在于:       a.重用存在的线程,减少对象创建.消亡的开销,性能佳.       b.可有效控制最大并发线程数.提供系统资源的使用率,同时避免过多资源竞争,避免堵塞.     

生产者消费者中多线程安全问题(即线程间通信的安全问题)

一个生产线程,一个消费线程,程序不存在安全问题 两个生产线程,两个消费线程,出现了生产两次,消费一次或者生产一次,消费两次的情况. 出现问题的原因是:线程被唤醒之后没有回去判断标记,直接继续执行后边的代码. 解决方式:让线程被唤醒之后不是直接执行后边的代码,而是回去判断标志,这个问题可以通过把if改成while实现.          但这样做会出现死锁的状况,原因是唤醒了本方线程,导致所有线程全部等待.          notify()唤醒的是任意一个线程,不能保证唤醒的是对方线程,这个问题

GCD使用三 -- 线程间通信

#define HMGlobalQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)#define HMMainQueue dispatch_get_main_queue() dispatch_async(HMGlobalQueue, ^{ NSLog(@"donwload---%@", [NSThread currentThread]); // 1.子线程下载图片 NSURL *url = [NSURL