您的位置:首页 > 编程语言 > Java开发

Java多线程知识点整理(线程间通信)

2017-12-24 00:00 447 查看
摘要: 摘要: 首先从API开始,然后是线程之间的通信,其次是synchronize,然后是锁Lock,最后是线程池。

一.作用

使线程间通信后,系统之间的交互性会更强大,在大大提高CPU利用率的同时还会使程序员对个线程任务在处理的过程进行有效的把控与监督。

二.等待/通知机制

对于传统的,我们可以不使用wait/notify机制,采用while不断的轮询。但是不听地通过while语句轮询机制来检测某一个条件,这样会浪费CPU资源。

代码:

public class test2 implements Runnable{
public String uname;

public void setUname(String uname) {
this.uname = uname;
}

@Override
public void run() {

while(true){
if(uname.equals("")){
System.out.println("退出");
try {
throw new InterruptedException();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

}

}

2.1、什么是等待/通知机制

比如在就餐时,厨师和服务业之间的交互。厨师通知服务员才做好了,服务员等待厨师菜做好。

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

方法wait()的作用是使当前执行代码的线程进行等待,wait()方法是Object类的方法,该方法用来将当线程置入“预执行队列”中,并且在wait()所在的代码行处停止执行,直到接到通知或被中断为止。在调用之前,线程必须获得该对象的对象级别锁,即只能在同步方法或者同步快中调用wait()方法。在执行wait()方法后,当前线程释放锁。在wait()返回前,线程与其他线程竞争重新获得锁。

插一句:面试经常会问,wait()方法和sleep()方法之间的区别。

补充答案: a.sleep 是线程类(Thread)的方法,wait 是Object 类的方法。

b.最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。

c.wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用(使用范围)。

d.sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常。

方法notify()也要在同步方法或者同步代码块中调用,即在调用前,线程也必须获得该对象的对象级别锁。如果没有获得锁,抛出异常。如果有多个线程等待,则由线程规划器随机挑选出其中一个呈wait状态的线程,对其发出通知notify,并使它等待获取该对象的对象锁。注意:在执行notify()方法后,当前线程不会马上释放该对象锁,呈wait状态的线程也并不能马上获取该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出synchronized代码块后,当前线程才会释放锁,而呈wait状态所在的线程才可以获取该对象锁。注意:notify()一次只随机通知一个线程进行唤醒。为了唤醒全部线程可以使用notifyAll()方法。

为什么wait(),notify()方法要和synchronized一起使用?

因为wait()方法是通知当前线程等待并释放对象锁,notify()方法是通知等待此对象锁的线程重新获得对象锁,然而,如果没有获得对象锁,wait方法和notify方法都是没有意义的,即必须先获得对象锁,才能对对象锁进行操作,于是,才必须把notify和wait方法写到synchronized方法或是synchronized代码块中了。如果没有获取到锁,会抛出IllegalMonitorStateException,它是RuntimeException的一个子类。

总结:wait使线程停止运行,而notify使停止的线程继续运行。

代码:等待代码

public class test2 implements Runnable{

private Object lock;

public test2(Object lock) {
super();
this.lock = lock;
}
@Override
public void run() {

synchronized (lock) {
System.out.println("开始");
try {
lock.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("结束");
}

}

}

通知代码

public class test3 implements Runnable{

private Object lock;

public test3(Object lock) {
super();
this.lock = lock;
}
@Override
public void run() {

synchronized (lock) {
System.out.println("开始natify");
try {
lock.notify();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("结束notify");
}

}

}

调用代码:

public static void main(String[] args) {
Object lock = new Object();
test2 test2 = new test2(lock);
test2.start();
test3 test3 = new test3(lock);
test3.start();
}

2.2.1、方法wait/notify/notifyAll的使用

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

lock.wait(5000);

wait等待的条件发生了变化,也容易造成程序逻辑的 混乱。

public void add(){
synchronized (lock) {
ValueObject.list.add("name");
lock.notifyAll();
}
}

通知方法:

public void notifyAdd(){
synchronized (lock) {
if(ValueObject.list.size() == 0){
lock.wait();
System.out.println("等待---------");
}
ValueObject.list.remove(0);
}
}

创建两个线程,进行调用。但第一个线程实现了减操作的线程能正确地删除list中索引为0的数据,但第二个线程实现减操作的线程则出现索引溢出,因为list中仅仅添加了索引为0的数据,也只能删除一个数据,所以没有第二个数据可供删除。

解决:

public void notifyAdd(){
synchronized (lock) {
while(ValueObject.list.size() == 0){
lock.wait();
System.out.println("等待---------");
}
ValueObject.list.remove(0);
}
}

总结:线程很怕通知不到,导致线程出现死锁。

2.3、生产与消费者模式

2.3.1、一生产者与一消费者

生产者:

public class test2 implements Runnable{

private Object lock;
private String name;
public test2(Object lock,String name) {
super();
this.lock = lock;
this.name = name;
}

public void setValue(){
try {
synchronized (lock) {
if(!name.equals("")){
lock.wait();
}
System.out.println(name);
lock.notify();
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

消费者代码:

public void getValue(){
try {
synchronized (lock) {
if(name.equals("")){
lock.wait();
}
System.out.println(name);
lock.notify();
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

2.3.2、多生产者与多消费者

这个模式很容易假死,因为条件改变时并没有得到及时的响应时,很容易出现。把上面的if语句改为while,就是多消费者和多生产者了。解决假死很简单就是把notify()改为notifyAll()方法。

2.3.3、生产和消费者这两个线程交替执行(面试有的时候,会问怎么实现)

完整代码:

import java.util.ArrayList;
import java.util.List;

public class test3 {
private Object lock;
private List list = new ArrayList();

public test3(Object lock) {
super();
this.lock = lock;
}

synchronized public void add(){
try{
if(list.size() == 1){
this.wait();
}
list.add("name:"+Math.random());
this.notify();
System.out.println(list.size());
}catch(Exception e){
e.printStackTrace();
}
}

synchronized public String get(){
String returnNamle="";
try {
if(list.size() == 0){
System.out.println("线程等待");
this.wait();
}
returnNamle =""+list.get(0);
list.remove(0);
this.notify();
System.out.println(list.size());
} catch (Exception e) {
// TODO: handle exception
}
return returnNamle;
}

}

多生产者和消费者:

if(list.size() == 0 )条件语句,改为while(list.size() == 0 );同时把两处的notify()改为notifyAll()方法,不改,容易出现假死的状态。

2.3.4 等待/通知之交备份

案例:创建20个线程,其中10个线程数据备份到A库,10个线程备份到B redis缓存中。

public class test4 {
volatile private boolean prevIsA = false;

synchronized public void addA(){
try {
while(prevIsA == true){
wait();
}
for(int i=0;i<5;i++){
System.out.println("#######");
}
prevIsA = true;
notifyAll();
} catch (Exception e) {
// TODO: handle exception
}
}

synchronized public void addB(){
try {
while(prevIsA == false){
wait();
}
for(int i=0;i<5;i++){
System.out.println("$$$$$$");
}
prevIsA = false;
notifyAll();
} catch (Exception e) {
// TODO: handle exception
}
}

public static void main(String[] args) {

}
}

注意:volatile private boolean prevIsA = false;

2.4、join方法的使用

在很多情况下,主线程创建并启动子线程,如果子线程中要进行大量的耗时运算,主线程往往将早于子线程结束之前。这时,如果主线程要用到子线程的值,就要用到join()方法了。方法join()的作用是等待对象销毁。

join的作用是使所属的线程对象X正常执行run()方法中的任务,而使当前线程Z进行无限期的阻塞,等待线程X销毁后在继续执行线程Z后面的代码。方法join具有使线程排队运行的作用(注意:面试的时候,怎么使t1,t2,t3线程有序的执行,使用哪个方法?),有些类似同步的运行效果。

join与synchronized的区别是:join在内部使用wait()方法进行等待,而synchronized关键字使用的是“对象监视器”原理作为同步。

在join过程中,如果当前线程对象被中断,则当前线程出现异常。同时join(long),可以使线程等待多少时间。

join(long)与sleep(long)的区别:join在内部使用wait()方法进行等待,所以join(long)方法具有释放锁的特点,那么其他线程就可以调用此线程中的同步方法了;而Thread.sleep(long)方法却不释放锁。

2.5、类ThreadLocal

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

Class test{
static ThreadLocal threadLocal =new ThreadLocal();
public static void main(String[] args) {
threadLocal.set("12");
threadLocal.get()
}
}

类InheritableThreadLocal 可以在子线程中取得父线程继承下来的值。

2.6、线程的运行状态



资料链接:http://blog.csdn.net/hangelsing/article/details/44037675
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Java 多线程通知