多线程学习------04线程间通信

一、等待/通知机制

1、什么是等待通知机制

在单线程编程中,要执行的操作需要满足一定的条件才能执行,可以把这个操作放在 if 语句块中.

  • 在多线程编程中,可能 A 线程的条件没有满足只是暂时的, 稍后其他的线程 B 可能会更新条件使得 A 线程的条件得到满足. 可以将 A 线程暂停,直到它的条件得到满足后再将 A 线程唤醒.它的伪代码:
atomics{ 
//原子操作 
while( 条件不成立 ){ 
		等待 
	}
//当前线程被唤醒条件满足后,继续执行下面的操作 

2、等待/通知机制的实现

Object 类中的 wait()方法可以使执行当前代码的线程等待,暂停执行,直到接到通知或被中断为止

  • 注意:
  • wait()方法只能在同步代码块中由锁对象调用
  • 调用 wait()方法,当前线程会释放锁

其伪代码如下:

//在调用 wait()方法前获得对象的内部锁 
synchronized( 锁对象 ){
    while( 条件不成立 ){ 
	//通过锁对象调用 wait()方法暂停线程,会释放锁对象
	锁对象.wait(); 
    }
	//线程的条件满足了继续向下执行
} 

Object 类的 notify()可以唤醒线程,该方法也必须在同步代码块中由锁对象调用 . 没有使 用 锁 对 象 调 用 wait()/notify() 会 抛 出 IlegalMonitorStateExeption 异常. 如果有多个等待的线程,notify()方法 只能唤醒其中的一个. 在同步代码块中调用 notify()方法后,并不会立即释放锁对象,需要等当前同步代码块执行完后才会释放锁对象,一般将 notify()方法放在同步代码块的最后. 它的伪代码如下:

synchronized( 锁对象 ){ 
    //执行修改保护条件 的代码 

    //唤醒其他线程 
    锁对象.notify(); 
}
//演示wait()等待和notify()唤醒等待这两个方法

				//需要通过 notify()唤醒等待的线程 
                synchronized (lock) {
 					System.out.println("线程 1 开始等待: " + System.currentTimeMillis());
                    try {
                 	lock.wait();//线程等待,会释放锁对象,当前线程转入 blocked 阻塞状态
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
   					System.out.println("线程 1 结束等待:" + System.currentTimeMillis());
                }
            }
        });
            //notify()方法也需要在同步代码块中,由锁对象调用 
                synchronized (lock){
          		 	System.out.println("线程 2 开始唤醒 : " + System.currentTimeMillis());
                    lock.notify();
                //唤醒在 lock 锁对象上等待的某一个线程 
         			System.out.println("线程 2 结束唤醒 : " + System.currentTimeMillis());
                }
            }
        });
        t1.start();
        Thread.sleep(3000);//main 线程睡眠 3 秒,确保 t1 入睡 
        t2.start();
    }}

3、interrupt()方法会中断 wait()

当线程处于 wait()等待状态时,

调用线程对象的 interrupt()方法会中断线程的等待状态, 会产生 InterruptedException 异常

4、notify()与 notifyAll()

notify()一次只能唤醒一个线程,如果有多个等待的线程,只能随机 唤醒其中的某一个; 想要唤醒所有等待线程,需要调用 notifyAll().

5、wait(long)的使用

wait(long)带有 long 类型参数的 wait()等待,如果在参数指定的时间内没有被唤醒,超时后会自动唤醒

6、通知过早

线程 wait()等待后,可以调用 notify()唤醒线程, 如果 notify()唤醒的过早,在等待之前就调用了 notify()可能会打乱程序正常的运行逻辑.

7、wait 等待条件发生了变化(重点)

wait()方法套在while循环中,线程下次醒过来后会继续进行循环,判断条件是否满足,满足就重新等待。

在使用 wait/nofity 模式时,注意 wait 条件发生了变化,也可能会造 成逻辑的混乱

wait()方法之所以要用while而不是if是因为 :

当多个线程并发访问同一个资源的时候, 若消费者同时被唤醒,但是只有一个资源可用, 那么if会导致资源被用完后直接去获取资源(发生越界异常等),而while则会让每个消费者获取之前再去判断一下资源是否可用.可用则获取,不可用则继续wait住.

二、通过管道流实现线程间的通信

三、Join()

join有什么用? 他是用来确定线程何时结束的 .看例子

main{
	a.start();
	a.join
	b.start();
}

主线程中调用a.join(),在a线程结束前,主线程陷入阻塞状态,a线程结束后,主线程顺序执行a.join()后的代码,并不会因为a的死亡导致主线程的死亡

四、ThreadLocal的使用

除了控制资源的访问外, 还可以通过增加资源来保证线程安全. ThreadLocal 主要解决为每个线程绑定自己的值

通常情况下,我们创建的变量是可以被任何一个线程访问并修改的(在当前线程的本地内存的共享变量中)。如果想实现每一个线程都有自己的专属本地变量该如何解决呢? JDK 中提供的ThreadLocal类正是为了解决这样的问题。 ThreadLocal类主要解决的就是让每个线程绑定自己的值,可以将ThreadLocal类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。

**如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的本地副本,这也是ThreadLocal变量名的由来。他们可以使用 get()set() 方法来获取默认值或将其值更改为当前线程所存的副本的值,从而避免了线程安全

/**
 * 在多线程环境中,把字符串转换为日期对象,多个线程使用同一个 SimpleDateFormat 对象可能会产生线程安全问题,有异常 
 * 为每个线程指定自己的 SimpleDateFormat 对象, 使用 ThreadLocal 
 */

ThreadLocal指定初始值

/**
 * ThreadLocal 初始值, 定义 ThreadLocal 类的子类,在子类中重写 initialValue()方法指定初始值,
   且第一次调用 get()方法不会返回 null
 */
class SubThreadLocal extends ThreadLocal<Date> {
    // 重写 initialValue 方法,设置初始值 
    @Override
    protected Date initialValue() {
        return new Date(System.currentTimeMillis() - 1000 * 60 * 15);
    }
}

五、额外补充:操作系统中进程间的通信方式

1、管道/匿名管道(Pipes) :用于具有亲缘关系的父子进程间或者兄弟进程之间的通信。双方通信时,需要建立起两个管道。一个读一个写
2、有名管道(Names Pipes) : 匿名管道由于没有名字,只能用于亲缘关系的进程间通信。为了克服这个缺点,提出了有名管道。有名管道严格遵循先进先出。可以实现本机任意两个进程通信。
3、信号(Signal) :通知某一进程某个事件已经发生;(有终止信号、定时器信号等)
4、消息队列(Message Queuing) ;
5、信号量(Semaphores) :信号量是一个计数器,用于多进程对共享数据的访问,信号量的意图在于进程间同步。这种通信方式主要用于解决与同步相关的问题并避免竞争条件。
6、共享内存(Shared memory) :使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。可以说这是最有用的进程间通信方式。
7、套接字(Sockets) : 此方法主要用于在客户端和服务器之间通过网络进行通信。套接字是支持 TCP/IP 的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值