1.线程通信方法
①同步
线程B需要等待线程A执行完了methodA()方法之后,它才能执行methodB()方法。这样,线程A和线程B就实现了 通信。
这种方式,本质上就是“共享内存”式的通信。多个线程需要访问同一个共享变量,谁拿到了锁(获得了访问权限),谁就可以执行。
②while轮询的方式
在这种方式下,线程A不断地改变条件,线程ThreadB不停地通过while语句检测这个条件(list.size()==5)是否成立 ,从而实现了线程间的通信。但是这种方式会浪费CPU资源。之所以说它浪费资源,是因为JVM调度器将CPU交给线程B执行时,它没做啥“有用”的工作,只是在不断地测试 某个条件是否成立。就类似于现实生活中,某个人一直看着手机屏幕是否有电话来了,而不是: 在干别的事情,当有电话来时,响铃通知TA电话来了。
③wait/notify机制
wait使线程停止运行,而notify使停止的线程继续运行。
④管道通信就是使用java.io.PipedInputStream 和 java.io.PipedOutputStream进行通信
java.io.PipeReaderWriter
具体就不介绍了。分布式系统中说的两种通信机制:共享内存机制和消息通信机制。感觉前面的①中的synchronized关键字和②中的while轮询 “属于” 共享内存机制,由于是轮询的条件使用了volatile关键字修饰时,这就表示它们通过判断这个“共享的条件变量“是否改变了,来实现进程间的交流。
2.等待/通知机制
2.1不使用等待/通知机制实现线程间的通信,while()轮询。轮询时间短,造成资源浪费,轮询时间长可能等不到数据。
2.2 等待通知 wait(),notify()
wait()是object类方法,将当前线程直入“预执行队列”中,在wait()所在的代码行处停止执行,知道接待通知或被中断为止。调用wait()前,线程必须获得该对象的对象级别锁,执行完后释放锁。
notify()也是在同步代码块中执行,调用前获取该对象的对象锁,执行完后锁不释放。
1)创建新线程后调用start()方法,系统会分配CPU资源,使其处于Runnable(可运行),如果抢占到CPU资源线程将处于Running状态。
2)Runnable和Running可以相互切换,因为运行一段时间后高优先级的线程抢占了CPU资源,这时候Running变成Runnable。
3)阻塞状态,阻塞状态结束后进入Runnable状态,等待系统重新分配资源。
1. 调用join()和sleep()方法,sleep()时间结束或被打断,join()中断,IO完成都会回到Runnable状态,等待JVM的调度。
2. 调用wait(),使该线程处于等待池(wait blocked pool),直到notify()/notifyAll(),线程被唤醒被放到锁定池(lock blocked pool ),释放同步锁使线程回到可运行状态(Runnable)
3. 对Running状态的线程加同步锁(Synchronized)使其进入(lock blocked pool ),同步锁被释放进入可运行状态(Runnable)。
4)run方法运行结束后进入销毁阶段,整个线程执行完成。
2.3 当interrupt方法遇到wait()时,会出现异常。
2.4 notify()只通知一个线程,,而notifyAll()唤醒所有线程。
2.5 生产者/消费者模式
等待通知是典型的生产者消费者模式。
1)一个生产者一个消费者,代码的操作值可以进行正常的匹配。
2)如果多个生产者消费者,可能出现“假死”,线程进入waiting等待状态。原因:wait()与notify(),notify()唤醒的可能是同类,如“生产者”唤醒“生产者”的现象。解决办法是将notify()变成notifyAll()。
3)操作栈现象:用栈,生产者加入,消费者取出
3.1)一生成一消费正常
3.2)一生产多消费,解决wait条件改变和假死。假死用notifyAll()唤醒。
3.3)多生产一个消费,无问题。
3.4)多生产与多消费
2.6 通过管道进行线程间的通信:字节流
管道:pipeStream流,一种特殊流.
java提高四种方法:pipedInputStream和pipedOutputStream
PipedReader和PipedWriter
3.方法join的使用
主线程创建启动子线程,子线程要耗费大量时间运算,主线程往往早于子线程结束前结束。如果想让主线程在子线程执行完以后再结束,调用join()。
特点:在join过程中,如果当前线程对象被中断,则当前线程会抛出异常。同时语句interrupt也会出现异常。
join(long)的内部实现是使用wait(long),因此具有释放锁的特点。
A.join,在API中的解释是,堵塞当前线程B,直到A执行完毕并死掉,再执行B。
3.类ThreadLocal的使用
Synchronized实现内存共享,ThreadLocal为每个线程维护一个本地变量。
采用空间换时间,它用于线程间的数据隔离,为每一个使用该变量的线程提供一个副本,每个线程都可以独立地改变自己的副本,而不会和其他线程的副本冲突。
ThreadLocal类中维护一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值为对应线程的变量副本。
ThreadLocal在spring中发挥着巨大的作用,在管理Request作用域中的Bean、事务管理、任务调度、AOP等模块都出现了它的身影。
Spring中绝大部分Bean都可以声明成Singleton作用域,采用ThreadLocal进行封装,因此有状态的Bean就能够以singleton的方式在多线程中正常工作了。
ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是threadlocalvariable(线程局部变量)。也许把它命名为ThreadLocalVar更加合适。线程局部变量(ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是Java中一种较为特殊的线程绑定机制,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。
从线程的角度看,每个线程都保持一个对其线程局部变量副本的隐式引用,只要线程是活动的并且 ThreadLocal 实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)。
通过ThreadLocal存取的数据,总是与当前线程相关,也就是说,JVM 为每个运行的线程,绑定了私有的本地实例存取空间,从而为多线程环境常出现的并发访问问题提供了一种隔离机制。
ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单,在ThreadLocal类中有一个Map,用于存储每一个线程的变量的副本。
概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。
4.类InheritableThreadLocal的使用
使用InheritableThreadLocal可以在子线程中获取父线程继承下的值。
如果子线程获取值的同时,主线程修改了值,子线程获得还是旧值。