Java并发编程程序员

Java并发编程基础-线程间通信

2018-05-20  本文已影响13人  markfork

章节目录

1. volatile 与 synchronized 关键字

线程开始运行,拥有自己的栈空间,就如同一个脚本一样,按照既定的代码一行一行的执行,直到终止。如果每个运行中的线程,仅仅是孤立的运行,那么没有价值,或者说价值很少,如果多个线程能够 相互配合 完成工作,这将带来巨大的价值。

1.1 Java 线程操作的共享变量是对共享内存变量的一份拷贝

Java支持多个线程同时访问一个对象或者对象的成员变量,由于每个线程可以拥有这个共享变量的一份拷贝
(虽然对象以及成员变量分配的内存是在共享内存中,但是每个执行的线程还是可以拥有一份拷贝,这样做的目的是
加速程序的执行)。这是现代多核处理器的一个显著特性,
所以在程序执行过程中,(未同步的程序代码块),一个线程看到的变量并不一定是最新的。

1.2 volatile 关键字-线程间通信

关键字volatile可以用来修饰字段(成员变量),就是告知任何对该变量的访问均
需要从共享内存中获取,而对它的改变必须同步刷新到共享内存,
它能保证虽有线程对共享变量的可见性。
举个例子,定义一个程序是否运行的成员变量,boolean on = true; 那么另一个
线程可能对它执行关闭动作(on = false),这涉及多个线程对变量的访问,因此
需要将其定义为 volatile boolean on = true,这样其他线程对他进行改变时,可
以让所有线程感知到变化,因为所有对共享变量的访问(load)和修改(store)都需
要以共享内存为准。但是过多的使用volatile是不必要的,因为它会降低程序执行的效率。

1.3 synchronized 关键字-线程间通信

关键字 synchronized 可以修饰方法 或者以同步块的形来进行使用,它主要确
保多个线程在同一时刻,只能有一个线程执行同步方法或同步块,它保证了线
程对变量访问的可见性、排他性。

如下所示,类中使用了同步块和同步方法,通过使用javap 工具查看生成的class文件信息来分析synchronized关键字实现细节,示例如下:

package org.seckill.Thread;

public class Synchronized {
    public static void ls(String[] args) {
        synchronized (Synchronized.class) {

        }//静态同步方法,对Synchronized Class对象进行加锁

        m();
    }

    public static synchronized void m(){

    }
}

执行 javap -v Synchronized.class
输出如下所示:

public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: ldc           #2                  // class org/seckill/Thread/Synchronized
         2: dup
         3: astore_1
         4: monitorenter
         5: aload_1
         6: monitorexit
         7: goto          15
        10: astore_2
        11: aload_1
        12: monitorexit
        13: aload_2
        14: athrow
        15: invokestatic  #3                  // Method m:()V
        18: return

public static synchronized void m();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
    Code:
      stack=0, locals=0, args_size=0
         0: return

对上述汇编指令进行解读

如下是对上述解读过程的图示:


对象、监视器、同步队列、执行线程之间的关系

2.等待/通知机制

等待通知相关方法

方法名称 描述
wait() 调用lock.wait()(lock是充当锁的对象)的线程将进入waiting状态,只有等待另外线程的通知或者线程对象.interrupted()才能返回,wait()调用后,会释放对象的锁
wait(long) 超时一段时间,这里的参数是毫秒,也就是等待n毫秒,如果没有通知就超时返回
wait(long,int) 对于超时间的更细粒度控制,可以达到纳秒级别
notify() 通知一个在锁对象上等待的线程,使其从wait()方法返回,而返回的前提是该线程获取到了对象的锁(其实是线程获取到了该对象的monitor对象的控制权)
notifyAll() 通知所有等待在充当锁的对象上的线程

对等待通知机制的解释

如下代码清单所示,创建两个线程 WaitThread & NotifyThread,前者检查flag是否为false,如果符合要求,进行后续操作,否则在lock上wait,后者在睡眠一段时间后对lock进行通知。

package org.seckill.Thread;

public class WaitNotify {
    static boolean flag = true;
    static Object lock = new Object();//充当锁的对象

    public static void main(String[] args) {
        //新建wait线程
        Thread waitThread = new Thread(new WaitThread(),"waitThread");
        Thread notifyThread = new Thread(new NotifyThread(),"notifyThread");
        waitThread.start();//等待线程开始运行
        Interrupted.SleepUnit.second(5);//主线程sleep 5s
        notifyThread.start();
    }

    //wait线程
    static class WaitThread implements Runnable {
        public void run() {
            synchronized (lock) {
                //判定flag
                while (flag) {
                    try {
                        System.out.println(Thread.currentThread().getName() + "获取flag 信息" + flag);
                        //判定为true 直接wait
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                System.out.println(Thread.currentThread().getName() + "获取flag 信息 为" + flag);
            }
        }
    }

    static class NotifyThread implements Runnable {

        public void run() {
            synchronized (lock) {
                while (flag) {
                    System.out.println(Thread.currentThread().getName() + "获取flag 信息 为" + flag+"可以运行");
                    lock.notify();//唤醒wait在lock上的线程,此时wait线程只能能从waiting队列进入阻塞队列,但还没有开始重新进行monitorenter的动作
                    // 因为锁没有释放
                    flag = false;
                    Interrupted.SleepUnit.second(5);
                }
            }

            synchronized (lock){//有可能获取到lock对象monitor,获取到锁
                System.out.println(Thread.currentThread().getName()+" hold lock again");
                Interrupted.SleepUnit.second(5);
            }
        }
    }

}

运行结果如下所示:

运行结果

对如上程序运行流程的解释如下所示:
上图中"hold lock again 与 最后一行输出"的位置可能互换,上述例子说明调用wait()、notify()、notifyAll需要注意的细节

3.等待/通知经典范式

等待/通知经典范式
该范式分为两部分,分别针对等待方(消费方)、和通知方(生产方)
等待方遵循如下原则:

syntronized (lock) {
  while( !条件满足 ){
      lock.wait();
  }
  //对应的处理逻辑
}

通知方遵循如下原则:

syntronized(lock) {
       //1.执行逻辑
       //2.更新条件
       lock.notify();
 }

4.管道输入输出流

5.Thread.join() 的 使用

如果使用了一个线程A执行了thread.join ,其含义是线程A等待thread线程终止之后才从thread.join()返回。
如下笔试题:
有A、B、C、D四个线程,在main线程中运行,要求 执行顺序是A->B->C->D->mian
变种->main等待A、B、C、D四个线程顺序执行,且进行sum,之后main线程打印sum
解法1-join()
其实就是插队

package org.seckill.Thread;

public class InOrderThread {
    static  int num = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread previous = null;
        for (int i = 0; i < 4; i++) {
            char threadName = (char) (i + 65);
            Thread thread = new Thread(new RunnerThread(previous), String.valueOf(threadName));
            previous = thread;
            thread.start();
        }
        previous.join();
        System.out.println("total num=" + num);
        System.out.println(Thread.currentThread().getName() + "terminal");
    }

    static class RunnerThread implements Runnable {
        Thread previous;//持有前一个线程引用

        public RunnerThread(Thread previous) {
            this.previous = previous;
        }

        public void run() {
            if (this.previous == null) {
//                num += 25;
                System.out.println(Thread.currentThread().getName() + " terminate ");
            } else {
                try {
                    previous.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
//                num += 25;
                System.out.println(Thread.currentThread().getName() + " terminate ");
            }

        }
    }
}

解法2-wait/notify

package org.seckill.Thread;

//wait/notify
public class InOrderThread2 {
//    static int state = 0;//运行标志
//    static Object lock = new Object();

    public static void main(String[] args) {
//        RunnerThread runnerThreadA = new RunnerThread();
//        RunnerThread runnerThreadB = new RunnerThread();
//        RunnerThread runnerThreadC = new RunnerThread();
//        RunnerThread runnerThreadD = new RunnerThread();
//        Thread threadA = new Thread(runnerThreadA, "A");
//        Thread threadB = new Thread(runnerThreadB, "B");
//        Thread threadC = new Thread(runnerThreadC, "C");
//        Thread threadD = new Thread(runnerThreadD, "D");
        RunnerThread runnerThread = new RunnerThread();
        Thread threadA = new Thread(runnerThread, "A");
        Thread threadB = new Thread(runnerThread, "B");
        Thread threadC = new Thread(runnerThread, "C");
        Thread threadD = new Thread(runnerThread, "D");

        threadD.start();
        threadA.start();
        threadB.start();
        threadC.start();
    }

    static class RunnerThread implements Runnable {
//        private  boolean flag = true;
        static int state = 0;//运行标志
        static Object lock = new Object();

        public void run() {
            String threadName = Thread.currentThread().getName();
//            while (flag) {
//                synchronized (lock) {
//                    if (state % 4 == threadName.charAt(0) - 65) {
//                        state++;
//                        flag = false;
//                        System.out.println(threadName + " run over");
//                    }
//                }
//            }

            synchronized (lock) {
                while (state % 4 != threadName.charAt(0) - 65) {
                    try {
                        lock.wait();
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                }
                state++;
                System.out.println(threadName+" run over ");
                lock.notifyAll();
            }
        }
    }
}

等待/通知范式做线程同步 是非常方便的。

解法3-循环获取锁

package org.seckill.Thread;

//wait/notify
public class InOrderThread2 {
    static int state = 0;//运行标志
    static Object lock = new Object();

    public static void main(String[] args) {
        RunnerThread runnerThreadA = new RunnerThread();
        RunnerThread runnerThreadB = new RunnerThread();
        RunnerThread runnerThreadC = new RunnerThread();
        RunnerThread runnerThreadD = new RunnerThread();
        Thread threadA = new Thread(runnerThreadA, "A");
        Thread threadB = new Thread(runnerThreadB, "B");
        Thread threadC = new Thread(runnerThreadC, "C");
        Thread threadD = new Thread(runnerThreadD, "D");
//        RunnerThread runnerThread = new RunnerThread();
//        Thread threadA = new Thread(runnerThread, "A");
//        Thread threadB = new Thread(runnerThread, "B");
//        Thread threadC = new Thread(runnerThread, "C");
//        Thread threadD = new Thread(runnerThread, "D");

        threadD.start();
        threadA.start();
        threadB.start();
        threadC.start();
    }

    static class RunnerThread implements Runnable {
        private  boolean flag = true;//每个线程的私有变量
//        static int state = 0;//运行标志
//        static Object lock = new Object();

        public void run() {
            String threadName = Thread.currentThread().getName();
            while (flag) {//主动循环加锁
                synchronized (lock) {
                    if (state % 4 == threadName.charAt(0) - 65) {
                        state++;
                        flag = false;
                        System.out.println(threadName + " run over");
                    }
                }
            }
//
//            synchronized (lock) {
//                while (state % 4 != threadName.charAt(0) - 65) {
//                    try {
//                        lock.wait();
//                    }catch (InterruptedException e){
//                        e.printStackTrace();
//                    }
//                }
//                state++;
//                System.out.println(threadName+" run over ");
//                lock.notifyAll();
//            }
        }
    }
}

开销是极大的、难以确保及时性

解法4-CountDownLatch

package org.seckill.Thread;


import java.util.concurrent.CountDownLatch;


public class InOrderThread3 {
//    static int state = 0;//运行标志
//    static Object lock = new Object();

    public static void main(String[] args) throws  InterruptedException{
        CountDownLatch countDownLatchA = new CountDownLatch(1);
        CountDownLatch countDownLatchB = new CountDownLatch(1);
        CountDownLatch countDownLatchC = new CountDownLatch(1);
        CountDownLatch countDownLatchD = new CountDownLatch(1);
        RunnerThread runnerThreadA = new RunnerThread(countDownLatchA);
        RunnerThread runnerThreadB = new RunnerThread(countDownLatchB);
        RunnerThread runnerThreadC = new RunnerThread(countDownLatchC);
        RunnerThread runnerThreadD = new RunnerThread(countDownLatchD);

        Thread threadA = new Thread(runnerThreadA, "A");
        Thread threadB = new Thread(runnerThreadB, "B");
        Thread threadC = new Thread(runnerThreadC, "C");
        Thread threadD = new Thread(runnerThreadD, "D");


//        RunnerThread runnerThread = new RunnerThread();
//        Thread threadA = new Thread(runnerThread, "A");
//        Thread threadB = new Thread(runnerThread, "B");
//        Thread threadC = new Thread(runnerThread, "C");
//        Thread threadD = new Thread(runnerThread, "D");

        threadA.start();
        countDownLatchA.await();//主线程阻塞,待countDownLatch 减为0即可继续向下运行
        threadB.start();
        countDownLatchB.await();
        threadC.start();
        countDownLatchC.await();
        threadD.start();
        countDownLatchD.await();

        System.out.println(Thread.currentThread().getName()+" run over ");

    }

    static class RunnerThread implements Runnable {
//        private  boolean flag = true;
//        static int state = 0;//运行标志
//        static Object lock = new Object();
        CountDownLatch countDownLatch;

        RunnerThread(CountDownLatch countDownLatch){
            this.countDownLatch = countDownLatch;
        }

        public void run() {
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName+" run over");
            countDownLatch.countDown();

//            while (flag) {
//                synchronized (lock) {
//                    if (state % 4 == threadName.charAt(0) - 65) {
//                        state++;
//                        flag = false;
//                        System.out.println(threadName + " run over");
//                    }
//                }
//            }
//
//            synchronized (lock) {
//                while (state % 4 != threadName.charAt(0) - 65) {
//                    try {
//                        lock.wait();
//                    }catch (InterruptedException e){
//                        e.printStackTrace();
//                    }
//                }
//                state++;
//                System.out.println(threadName+" run over ");
//                lock.notifyAll();
//            }
        }
    }
}

countDownLatch 的使用场景 :比如系统完全开启需要等待系统软件全部运行之后才能开启。最终的结果一定是发生在子(部分)结果完成之后的。也可作为线程同步的一种方式
Thread join() 源码

public final synchronized void join() throws InterruptedException {
   while (isAlive) {
       wait(0);
   }
}

当被调用thread.join() 的线程(thread)终止运行时,会调用自身的notifyAll()方法,会通知所有等待该线程对象上完成运行的线程,可以看到join方法的逻辑结构与等待/通知经典范式一致,即加锁、循环、处理逻辑3个步骤。

上一篇下一篇

猜你喜欢

热点阅读