zoukankan      html  css  js  c++  java
  • java多线程编程

    线程的概念

    进程与线程的区别

    (1)多进程

    进程是操作系统中最重要的概念。现在的操作系统基本上都是多进程的,也就是有多个进程操作系统可以同时在电脑电脑上运行。你可以在写文档的时候听歌,在听歌的时候浏览网页等。一个进程对于程序来说是一个实例。

    (2)多线程

    线程也称为轻型进程。线程活动的范围只在创建它的进程中,而且创建线程所需的资源要比创建进程少得多。一个进程中可以创建多个线程,而且这些线程可以共享进程数据,协同工作。

    下面的图可以辅助一下理解:

    iljzp

    main-qimg-8b5f561ef8a2b580354b1a3693d45b5b

    Process vs Thread in Java

    image

    threadmodel

    (3)并发和并行

    Concurrency and parallelism are two related but distinct concepts.

    Concurrency means, essentially, that task A and task B both need to happen independently of each other, and A starts running, and then B starts before A is finished.

    There are various different ways of accomplishing concurrency. One of them is parallelism--having multiple CPUs working on the different tasks at the same time. But that's not the only way. Another is by task switching, which works like this: Task A works up to a certain point, then the CPU working on it stops and switches over to task B, works on it for a while, and then switches back to task A. If the time slices are small enough, it may appear to the user that both things are being run in parallel, even though they're actually being processed in serial by a multitasking CPU.

    image

    image

    线程的概念模型

    线程是彼此相互独立的、能独立运行的子任务,并且每个线程都有自己的调用栈(当然也有自己的程序计数器)。所谓的多任务是通过周期性地将CPU时间片切换到不同地子任务,虽然从微观上看来,单核地CPU上只运行了一个子任务,但是从宏观上来看,每个子任务似乎是同时连续运行地。

    (1)分时

    当你在一边听歌,一边聊天还看着网页的时候,如果是对于单个CPU而言并不是同时在执行这些程序,CPU只将时间切割为时间片,然后将时间片分配给这些程序,获得时间片的程序开始执行,不等执行完毕,下个程序又获得时间片开始执行,这样多个程序轮流执行一段时间,由于现在CPU的高速计算能力,给人的感觉就像是多个程序在同时执行一样。

    一般可以在同一时间内执行多个程序的操作系统都有进程的概念,一个进程就是执行中的程序,而每一个进程都有自己独立的一块内存空间、一组系统资源。在进程概念中,每一个进程内部数据和状态都是完全独立的。因此可以想象创建并执行一个进程的开销是比较大的,所以线程出现了。在Java中,程序通过控制流来执行程序流,程序中单个顺序的流控制称为线程,多个线程则指的是单个程序中可以同时运行多个不同的线程,执行不同的任务。多线程意味着一个程序的多行语句可以看上去几乎在同一时间内同时运行。

    线程与进程相似,是完成某个特定功能的一段代码,是程序中单个顺序的流控制;但与进程不同的是,同类的多个线程是共享一块内存空间和一组系统资源,而线程本身的数据通常只有CPU的寄存器数据,以及一个供程序执行时使用的堆栈。所以系统在产生一个线程,或者在各个线程之间切换时,负担要比进程小得多,正因如此,线程也被称为轻负荷进程(light-weight process)。一个进程中可以包含多个线程。

    image

    image


    (2)多任务

    多任务是指在一个系统中可以同时运行多个程序,即有多个独立运行的任务,每个任务对应一个进程,同进程一样,一个线程也有从创建、运行到消亡的过程,称为线程的生命周期。用线程的状态(state)表明线程处在声明周期的哪个阶段。线程有创建、可运行、运行中、阻塞、死亡5中状态。通过线程的控制与调度可使线程在这几种状态间转化为每个程序至少自动拥有一个线程,称为主线程。当程序加载到内存时,启动主线程。

    image


    线程的运行状态

    在Java中,多线程就是一个类或一个程序执行或管理多个线程执行任务的能力,每个线程可以独立于其他线程而独立运行,当然也可以和其他线程协同运行,一个类控制者它的所有线程,可以决定哪个线程得到优先级,哪个线程可以访问其他类的资源,哪个线程开始执行,哪个保持休眠状态。

    根据线程的度过程,线程可以处于不同的执行状态。

    image

    image


    线程的状态表示线程正在进行的活动及在此时间段内所能完成的任务。线程有创建、可运行、运行中、阻塞、死亡5种状态。一个具有生命的线程,总是处于这5种状态之一。

    • 新建状态:使用new运算符创建一个线程后,该线程仅仅是一个空对象,系统没有分配资源,该线程处于创建状态(new thread)。
    • 可运行状态:使用start()方法启动一个线程后,系统为该线程分配除了CPU外的所需资源,使该线程处于可运行状态(Runnable)。
    • 运行状态:JVM通过调度选中一个Runnable的线程,使其占有CPU并转为运行中的状态(Running),此时系统真正执行执行线程的run()方法。
    • 阻塞状态:一个正在运行的线程因某种原因不能继续运行时,进入阻塞状态(Blocked)。
    • 死亡状态:线程之后是死亡状态(Dead)。

    线程的优先级

    从概念上来说,线程是并发运行的,但是从计算机的运行角度来说,却是穿行执行的。当系统中只有一个CPU时,线程会以某种顺序执行,这称作线程调度(scheduling)。

    Java采用的是一种简单、固定的调度法,即固定优先级调度。这种算法是根据处于可运行状态线程的相对优先级来实行调度的。当线程产生时,它继承原线程的优先级。在需要时可对优先级进行修改。在任何时刻,如果有多条线程等待运行,系统选择优先级最高的可运行线程运行。只有当它停止、自动放弃或由于某种原因成为非运行态,低优先级的线程才能运行。如果两个线程具有相同的优先级,它们将被交替地运行。

    Java实时系统的线程调度算法还是强制性的,在任何时刻,如果一个比其他线程优先级都高的线程的状态变为可运行状态,实时系统将选择该线程来运行。

    同一时刻如果有多个线程处于可运行状态,则它们需要排队等候CPU资源。此时每个线程自动获得一个线程的优先级(priority),优先级的高低反映线程的重要或紧急程度。可运行状态的线程按优先级排队,线程调度依据优先级基础上的"先到先服务"原则。

    线程调度管理器负责线程排队和CPU在线程间的分配,并由线程调度算法进行调度。当线程调度管理器选中某个线程时,该线程获得CPU资源而进入运行状态。线程调度是先占式调度,即如果在当前线程执行过程中一个更高优先级的线程进入可运行状态,则这个线程立即被调度执行。先占式调度分为:独占方式和分时方式。

    • 独占方式:当前执行线程将一直执行下去,直到执行完毕或由于某种原因主动放弃CPU,或者CPU被一个更高优先级的线程抢占。
    • 分时方式:当前运行线程获得一个时间片,当时间到时,即使没有执行完也要让出CPU,进入可运行状态,等待下一个时间片的调度,系统选中其他可运行状态的线程执行。

    分时方式的系统使每个线程工作若干步,实现多线程同时运行。

    线程的开发方法

    线程的创建和启动

    在Java中实现一个线程的两种方法。

    第一种实现Runnable接口,实现它的run()方法。

    第二种是继承Thread类,覆盖它的run()方法。

    这两种方法的区别是,如果你的类已经继承了其他的类,那么你只能选择实现Runnable接口了,因为Java只允许单继承。

    继承Thread类

    public class TestThread extends Thread {
        private int time;
        private String user;
    
        TestThread(int time, String user) {
            this.time = time;
            this.user = user;
        }
    
        public void run() {
            while(true) {
                try {
                    System.out.println(user + "休息" + time + "ms-" + new Date());
                    Thread.sleep(time);
                } catch (InterruptedException e) {
                    System.out.println(e);
                }
            }
        }
    
        public static void main(String[] args) {
            TestThread thread1 = new TestThread(1000, "Jack");
            thread1.start();
    
            TestThread thread2 = new TestThread(1500, "Mike");
            thread2.start();
        }
    }

    实现Runnable接口

    上面使用了Thread的方法来创建一个线程类,但是有时要作为线程运行的类可能已经是某个类的子类,Java中只允许单一继承,所以就不能再按这种机制创建线程。Java提供的解决方法就是实现Runnable接口。Runnable为非Thread子类的类提供了一种激活方式。通过实例化某个Thread实例并将自身作为运行目标,就可以运行实现Runnable的类而无须创建Thread的子类。在大多数情况下,如果只想重写run()方法,而不重写其他Thread方法,那么应该Runnable接口。

    public class TestThread implements Runnable {
        private int time;
        private String user;
    
        TestThread(int time, String user) {
            this.time = time;
            this.user = user;
        }
    
        public void run() {
            while(true) {
                try {
                    System.out.println(user + "休息" + time + "ms-" + new Date());
                    Thread.sleep(time);
                } catch (InterruptedException e) {
                    System.out.println(e);
                }
            }
        }
    
        public static void main(String[] args) {
            Thread thread1 = new Thread(new TestThread(1000, "Jack"));
            thread1.start();
    
            Thread thread2 = new Thread(new TestThread(3000, "Mike"));
            thread2.start();
        }
    }

    线程的运行控制

    上面通过继承Thread类或者实现Runnable来创建并运行线程是最基本的,但是实际在写多线程的时候远不止这些。线程的概念之所以出现,就是为了实现多个任务同时运行,那么多个线程同时运行,就存在多个线程之间的调度控制、资源分配等。

    • 线程的启动start()、join()与停止stop()。
    • 线程的休眠sleep()与挂起yield()。
    • 线程的同步synchronized。
    • 线程的同步锁机制:wait()、notify()和notifyAll()。

    线程的启动start()、join()与停止stop()

    start()

    在介绍线程的创建时,学会了使用start()来启动一个线程,这个线程就会一直运行下去,然而我们也可以根据需要来随时停止这个线程。

    join()

    Thread的非静态方法join()让线程B加入到另一个线程A的尾部。在A执行完毕前B不能工作。另外,join()方法还带有超时限制的重载版本。例如 t.join(5000); 则让线程等待5000毫秒,如果超过这个时间,则停止等待,变成可运行状态。

    public class TestJoin {
    
        public static void main(String[] args) {
            MyThread2 t1 = new MyThread2("TestJoin");
            t1.start();
            try {
                // join合并线程, 子线程运行完之后, 主线程才开始时
                t1.join();
            } catch (InterruptedException e) { }
    
            for(int i=0; i<10; i++) {
                System.out.println("I am Main Thread");
            }
        }
    }
    
    class MyThread2 extends Thread {
        MyThread2(String s) {
            // 设定线程的名称
            super(s);
        }
    
        @Override
        public void run() {
            for(int i=0; i<=10; i++) {
                System.out.println("I am " + getName());
                try {
                    sleep(1000);
                } catch (InterruptedException e) {
                    return;
                }
            }
        }
    }

    额join是让谁等待谁呢?一个线程如果执行join语句(调用其他的线程类的join方法的线程),那么就有新的线程加入,执行该语句的线程必须要让步给新加入的线程完成任务,然后才能继续执行。相当于把原来需要并行执行的线程变成了串行。join方法中如果传入参数,则表示这样的意思:如果A线程中调用B线程的join(10),则表示A线程会等待B线程执行10毫秒,10毫秒过后,A、B线程并行执行。需要注意的是,jdk规定,join(0)的意思不是A线程等待B线程0秒,而是A线程等待B线程无限时间,直到B线程执行完毕,即join(0)等价于join()。这里从https://blog.csdn.net/oguro/article/details/53325425 中借鉴一个例子,比较生动:

    public class Mom extends Thread {
        @Override
        public void run() {
            System.out.println("妈妈洗菜");
            System.out.println("妈妈切菜");
            System.out.println("妈妈准备炒菜,发现酱油没了");
            // 叫儿子去打酱油
            Son s = new Son();
            s.start();
            try {
                s.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("妈妈继续炒菜");
            System.out.println("全家一起吃饭");
        }
    }
    
    public class Son extends Thread {
        @Override
        public void run() {
            System.out.println("儿子下楼");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("儿子一直往前走");
            System.out.println("儿子打完酱油了");
            System.out.println("上楼,把酱油交给老妈");
        }
    }
    
    public class JoinDemo {
        public static void main(String[] args) {
            Mom m = new Mom();
            m.start();
        }
    }

    看完了上面个的两个例子之后,会发现每次都是在start调用完了之后,再调用join。其实是有道理的,如果一个线程都没有start起来,那么调用join没有意义啊。我在思考的一个问题是如果要保证在A中调用B.join()的话,如果B.start之后,并没有被调度立即执行,而是等待了一会儿,然后A执行了一段代码,然后才是B.join呢?其实我想到这里倒是有些多余了,因为只要在B.start之后马上调用B.join就可以保证B的全部代码执行完了之后A才执行后面的代码。如果不马上调用join的话,调用线程在调用start之后会马上执行后面的代码。

    join的原理:其实,join方法是通过调用线程的wait方法来达到同步的目的的。例如,A线程中调用了B线程的join方法,则相当于A线程调用了B线程的wait方法,在调用了B线程的wait方法后,A线程就会进入阻塞状态,具体看下面的源码。

    (那么这里的意思其实是说,线程A直接或者间接调用了线程B的wait方法,就会放弃线程B的锁么?)I get it。其实不是线程而是对象,对象才会有锁控制,wait是Object对象中的方法。

    public final void join() throws InterruptedException {
        join(0);
    }
    
    public final synchronized void join(long millis) throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;
    
        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
    
        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
    
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

    其实我们调用某个对象的wait方法时,当前线程会阻塞,原理大概如下:每个对象其实都有个队列管理竞争该对象的所有线程对象,线程阻塞(直接调用这个对象的wait方法或者请求该对象的锁,wait 意味着放弃该对象的锁)其实就是把对象加入到该队列;notify就是把阻塞队列中线程唤醒。从代码上看,如果线程被生成了,但还未被起动,调用它的 join() 方法是没有作用的,将直接继续向下执行。同时join还有join(millis)方法,可以加入等待时间,效果上类似sleep,但是还是有实际区别的。join底层是wait方法,所以它是会释放对象锁的,而sleep在同步的方法中是不释放对象锁的,只有同步方法执行完毕,其他线程才可以执行。join(0)方法一直在调用native方法isAlive()检测子线程B的状态,如果子线程B是active的状态,调用子线程B的wait方法,此时主线程A释放了锁(wait方法会释放锁),其他线程可以竞争子线程B的实例锁。如果检测到子线程B的状态不是active,主线程A继续运行。

    使用场景: 一个业务需要给对方暴露接口,他们调用我们的接口,我们进行业务处理后,再返回结果,接口要求是同步的,实时返回。如果异步的就可以用消息队列解决了,把整个业务逻辑中比较费时间的都放在了子线程中运行,子线程跑完后在交由主线程返回结果。当时用的是java中的栅栏 CyclicBarrier ,现在想想用join也是可以实现的。

    然后就是join的时间参数的设定问题,这个问题是在join设定了时间之后,不同情况下调用线程等待的时间。main 线程调用t.join时,必须能够拿到线程t对象的锁,如果拿不到它是无法wait的 ,刚开的例子t.join(1000)不是说明了main线程等待1 秒,如果在它等待之前,其他线程获取了t对象的锁,它等待时间可不就是1毫秒了 。

    调用线程等待的时间。

    我怎么感觉join的话,只能等待一个线程运行,那要是想等待多个线程的执行呢?

    stop()

    public class Test extends Thread {
        int count = 1;
        public void run() {
            while(true) {
                System.out.println((count++) + "-" +new Date());
                try {
                    Thread.sleep(328);
                } catch (Exception e) {
    
                }
            }
        }
    
        public static void main(String[] args) {
            Test t = new Test();
            t.start();
            // 主线程休眠4秒之后停止子线程
            try {
                Thread.sleep(4000);
            } catch (Exception e) {
    
            }
            t.stop();
        }
    }

    在这里用Thread的stop来终止了这个线程,但是stop是一个被废弃的方法,这里只是想演示一下对线程的控制。也就是说要拿到这个线程的句柄(引用)来调用对应的方法对其进行控制。关于Thread的stop方法被废弃,可以参考这个:https://www.cnblogs.com/DreamDrive/p/5623804.html

    1. stop方法是过时的

    从Java编码规则来说,已经过时的方式不建议采用.

    2. stop方法会导致代码逻辑不完整

    stop方法是一种"恶意"的中断,一旦执行stop方法,即终止当前正在运行的线程,不管线程逻辑是否完整,这是非常危险的。

    public class Client {
        public static void main(String[] args) throws Exception {
            // 子线程
            Thread thread = new Thread() {
                @Override
                public void run() {
                    try {
                        // 该线程休眠1秒
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        //异常处理
                    }
                    System.out.println("此处代码不会执行");
                }
            };
            // 启动线程
            thread.start();
            // 主线程休眠0.1秒
            Thread.sleep(100);
            // 子线程停止
            thread.stop();
    
        }
    }

    这里子线程是一个匿名内部类,它的run方法在执行时会休眠1秒钟,然后再执行后续的逻辑,而主线程则是休眠0.1秒后终止子线程的运行,也就是说,JVM在执行thread.stop()时。子线程还在执行sleep(1000),此时stop方法会清除栈内信息,结束该线程。这也导致了run方法的逻辑不完整,输出语句println代表的是一段逻辑,可能非常重要:比如子线程的主逻辑,资源回收,情景初始化等等;但是因为stop线程了,这些就都不再执行了,于是就产生了业务逻辑不完整的情况。

    这是极度危险的,因为我们不知道子线程会在什么时候停止,stop连基本的逻辑完整性都无法保证,而且此种操作也是非常隐蔽的,子线程执行到何处会被关闭很难定位,这为以后的维护带来了很多的麻烦。

    3. stop方法会破坏原子逻辑

    多线程为了解决共享资源抢占的问题,使用了锁的概念,避免资源不同步,但是正是因为此原因,stop方法却会带来更大的麻烦,它会丢弃所有的锁,导致原子逻辑受损。例如 有这样一段程序:

    public class Client {
        public static void main(String[] args) {
            MultiThread t = new MultiThread();
            Thread t1 = new Thread(t);
            // 启动t1线程
            t1.start();
            for (int i = 0; i < 5; i++) {
                new Thread(t).start();
            }
            // 停止t1线程
            t1.stop();
        }
    }
    
    class MultiThread implements Runnable {
        int a = 0;
    
        @Override
        public void run() {
            // 同步代码块,保证原子操作
            synchronized ("") {
                // 自增
                a++;
                try {
                    // 线程休眠0.1秒
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 自减
                a--;
                String tn = Thread.currentThread().getName();
                System.out.println(tn + ":a =" + a);
            }
        }
    }

    MultiThread实现了Runnable接口,具备多线程的能力,run方法中加入了synchronized代码块,表示内部是原子逻辑,a的值会先增加后减少,按照synchronized的规则,无论启动多少个线程,打印出来的结果都应该是a=0,但是如果有一个正在执行的线程被stop,就会破坏这种原子逻辑。(上面main方法中代码)

    首先说明的是所有线程共享了一个MultThread的实例变量t,其次由于在run方法中加入了同步代码块,所以只能有一个线程进入到synchronized块中。

    此段代码的执行顺序如下:

    1. 线程t1启动,并执行run方法,由于没有其他线程同步代码块的锁,所以t1线程执行自加后执行到sleep方法开始休眠,此时a=1。
    2. JVM又启动了5个线程,也同时运行run方法,由于synchronized关键字的阻塞作用,这5个线程不能执行自增和自减操作,等待t1线程释放线程锁。
    3. 主线程执行了t1.stop方法,终止了t1线程,注意由于a变量是线程共享的,所以其他5个线程获得的a变量也是1。
    4. 其他5个线程获得CPU的执行机会,打印出a的值。
    如何优雅地中止线程呢?

    在线程主体中判断是否需要停止运行,即可保证线程体的逻辑完整性而且也不会破坏原值逻辑。Thread还提供了一个interrupt中断线程的方法,这个不是过时的方法,是否可以使用这个中断线程?

    很明确的说,interrupt不能终止一个正在执行着的线程,它只是修改中断标志位而已。例如:

    public class Client {
        public static void main(String[] args) {
            Thread t1 = new Thread() {
                public void run() {
                    //线程一直运行
                    while (true) {
                        System.out.println("Running……");
                    }
                }
            };
            // 启动t1线程
            t1.start();
            System.out.println(t1.isInterrupted());//false
            // 中断t1线程
            t1.interrupt();
            System.out.println(t1.isInterrupted());//true
        }
    }


    为什么thread.stop被废弃了呢?

    因为它是天生不安全的。停止一个线程会导致它解锁它所锁定的所有monitor(当一个ThreadDeath Exception沿着栈向上传播时会解锁monitor),如果这些被释放的锁所保护的objects有任何一个进入一个不一致的状态,其他将要访问该objects的线程也会以一种不一致的状态来访问这些objects。这种objects称为"被损坏了"。当线程对被损坏的objects上做操作时,可能会产生意想不到的结果,这些行为可能是很严重的,并且难以探测到,不像其他 uncheck exception,ThreadDeath Exception静默的杀死进程,因此,用户不会被警告他的程序会崩溃,这会在"损坏"之后的任何时候发生,甚至几小时或者几天后。


    有人或许会想到捕获ThreadDeath Exception。理论上,或许可以。但是它会极大地将多线程代码编写复杂化,以下两个原因,让这项工作变得几乎不可能完成:

    1. 一个线程会在几乎任何地方抛出ThreadDeath Exception,考虑到这一点,所有的同步方法和代码块将必须进行详细的考察。
    2. 线程可能在处理第一个异常的时候(在catch,finally语句块里)抛出第二个异常,处理语句必须将不得不重新开始反复如此直到成功,来保证这一过程的代码将会非常复杂。

    总结一下,这是不切实际的。

    如果不用Thread.stop(),我们应该使用什么方法?

    大多数对stop方法的调用应该用指示目标线程是否应该停止运行的一些变量的简单代码来替换,目标线程应该定时的检查这些变量(注意这些变量是否要设置为volatile),当发现这些变量指示该线程应该停止运行时,有序地从它的run方法来return。(这是java tutorial中经常要求的方式)

    JDK1.0定义了stop和suspend方法,stop用来直接终止线程,suspend会阻塞线程直到另一个线程调用resume。
    stop和suspend都有一些共同的点:都试图专横的控制一个给定了的线程的行为。

    从JDK1.2开始,这两个方法都被弃用了。stop天生就不安全,而经验告诉我们呢suspend方法会经常导致死锁。

    反对使用stop(),是因为它不安全。它会解除由线程获取的所有锁定,当在一个线程对象上调用stop()方法时,这个线程对象所运行的线程就会立即停止,假如一个线程正在执行:synchronized void { x = 3; y = 4;} 由于方法是同步的,多个线程访问时总能保证x,y被同时赋值,而如果一个线程正在执行到x = 3;时,被调用了stop()方法,即使在同步块中,它也干脆地stop了,这样就产生了不完整的残废数据。而多线程编程中最最基础的条件要保证数据的完整性,所以请忘记线程的stop方法,以后我们再也不要说"停止线程"了。而且如果对象处于一种不连贯状态,那么其他线程能在那种状态下检查和修改它们。结果 很难检查出真正的问题所在。

    suspend()方法容易发生死锁。调用suspend()的时候,目标线程会停下来,但却仍然持有在这之前获得的锁定。此时,其他任何线程都不能访问锁定的资源,除非被"挂起"的线程恢复运行。对任何线程来说,如果它们想恢复目标线程,同时又试图使用任何一个锁定的资源,就会造成死锁。所以不应该使用suspend(),而应在自己的Thread类中置入一个标志,指出线程应该活动还是挂起。若标志指出线程应该挂起,便用 wait()命其进入等待状态。若标志指出线程应当恢复,则用一个notify()重新启动线程。

    线程的休眠sleep()与挂起yield()

    线程可以使用sleep()让当前线程暂停一定的时间之后继续执行。sleep()方法的作用是,会使当前的线程暂停执行一段时间,以给其他线程运行的机会。

    可以使用下面两种休眠方法:

    static void sleep(long millis)

    static void sleep(long millis, int nanos);

    指定的毫秒数加上指定的纳秒数让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。

    对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是它的监控状态依然保持着,当指定的时间到了又会自动恢复运行状态。在调用sleep()方法的过程中,线程不会释放对象锁。而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。

    1. 线程睡眠是帮助所有线程获得运行机会的最好方法。
    2. 线程睡眠到期自动苏醒,并返回到可运行状态,不是运行状态。sleep()中指定的时间是线程不会运行的最短时间。因此,sleep()方法不能保证该线程睡眠到期后就开始执行。
    3. sleep()是静态方法,只能控制当前正在运行的线程。

    优先使用TimeUnit类中的sleep()


    这两个方法来自不同的类分别是Thread和Object。最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法(锁代码块和方法锁)。 
    wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用(使用范围) 
    sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常。
    sleep方法属于Thread类中方法,表示让一个线程进入睡眠状态,等待一定的时间之后,自动醒来进入到可运行状态,不会马上进入运行状态,因为线程调度机制恢复线程的运行也需要时间,一个线程对象调用了sleep方法之后,并不会释放他所持有的所有对象锁,所以也就不会影响其他进程对象的运行。但在sleep的过程中过程中有可能被其他对象调用它的interrupt(),产生InterruptedException异常,如果你的程序不捕获这个异常,线程就会异常终止,进入TERMINATED状态,如果你的程序捕获了这个异常,那么程序就会继续执行catch语句块(可能还有finally语句块)以及以后的代码。 
    注意sleep()方法是一个静态方法,也就是说他只对当前对象有效,通过t.sleep()让t对象进入sleep,这样的做法是错误的,它只会是使当前线程被sleep而不是t线程
    wait属于Object的成员方法,一旦一个对象调用了wait方法,必须要采用notify()和notifyAll()方法唤醒该进程;如果线程拥有某个或某些对象的同步锁,那么在调用了wait()后,这个线程就会释放它持有的所有同步资源,而不限于这个被调用了wait()方法的对象。wait()方法也同样会在wait的过程中有可能被其他对象调用interrupt()方法而产生InterruptedException。
     

    如果线程A希望立即结束线程B,则可以对线程B对应的Thread实例调用interrupt方法。如果此刻线程B正在wait/sleep/join,则线程B会立刻抛出InterruptedException,在catch() {} 中直接return即可安全地结束线程。需要注意的是,InterruptedException是线程自己从内部抛出的,并不是interrupt()方法抛出的。对某一线程调用interrupt()时,如果该线程正在执行普通的代码,那么该线程根本就不会抛出InterruptedException。但是,一旦该线程进入到wait()/sleep()/join()后,就会立刻抛出InterruptedException。

    waite()和notify()因为会对对象的"锁标志"进行操作,所以它们必须在synchronized函数或synchronized block中进行调用。如果在non-synchronized函数或non-synchronizedblock中进行调用,虽然能编译通过,但在运行时会发生illegalMonitorStateException的异常。

    补充两个重要的方法:yield()和join()

    yield方法:暂停当前正在执行的线程对象。 

    yield()方法是停止当前线程,让同等优先权的线程或更高优先级的线程有执行的机会。如果没有的话,那么yield()方法将不会起作用,并且由可执行状态后马上又被执行。  

    join方法是用于在某一个线程的执行过程中调用另一个线程执行,等到被调用的线程执行结束后,再继续执行当前线程。如:t.join();。主要用于等待t线程运行结束,若无此句,main则会执行完毕,导致结果不可预测。 


    说一下为什么使用wait()方法时,一般是需要while循环而不是if?

    while(!执行条件) {
        wait();
    }
    
    if(!执行条件) {
        wait();
    }

    while会一直执行循环,直到条件满足,执行条件才会继续往下执行。if只会执行一次判断条件,不满足就会等待。这样就会出现问题。

    我们知道用notify() 和notifyAll()可以唤醒线程,一般我们常用的是notifyAll(),因为notify(),只会随机唤醒一个睡眠线程,并不一定是我们想要唤醒的线程。如果使用的是notifyAll(),唤醒所有的线程,那你怎么知道他想唤醒的是某个正在等待的wait()线程呢,如果用while()方法,就会再次判断条件是不是成立,满足执行条件了,就会接着执行,而if会直接唤醒wait()方法,继续往下执行,根本不管这个notifyAll()是不是想唤醒的是自己还是别人,可能此时if的条件根本没成立。

    举个例子:

    while去水果店买苹果,没有了,然后while就和水果店老板说,有水果的时候通知我,我先回去了。if也去水果店买苹果,没有了,然后if就和水果店老板说,有水果的时候通知我,我先回去了。过一段时间,水果店老板发短信告诉while和if,有水果了,while去一看,水果店只是进了香蕉,并不是苹果,所以不是想要的水果,于是回去继续等水果店老板通知,而if根本就不看是不是自己想要的苹果,直接就叫老板送10斤水果过来,这样会导致你得到错误的结果。

    yield()方法与sleep()方法相似,只是它不能由用户指定线程暂停多长时间。sleep()方法可以使低优先级的线程得到执行机会,当然也可以让同优先级和高优先级的线程有执行机会。而yield()方法只能使同优先级的线程有执行机会。

    线程同步synchronized

    许多线程在执行中必须考虑到与其他线程之间共享数据或协调执行状态,这就需要同步机制。在Java中每个对象都有一把锁与其对应。但Java不提供单独的lock和unlock操作。它由高层的结构隐士实现,来保证操作的对应。

    synchronized语句计算一个对象引用,试图对该对象完成锁操作,并且在完成锁操作前停止处理。当锁操作完成synchronized语句得到执行。当语句体执行完毕(无论正常或异常),解锁操作自动完成。作为面向对象的语言,synchronized经常与方法连用,如下所示:

    public synchronized void fun() {
        // 保护代码块
    }

    一种比较好的办法,如果某个变量由一个线程赋值,并由别的线程引用或赋值,那么所有对该变量的访问都必须在某个synchronized语句或者synchronized方法内。

    在可能的情况下,应该把保护范围缩到最小,可以使用代码块保护,使用this代表当前对象。

    java的对象锁和类锁:java的对象锁和类锁在锁的概念上基本上和内置锁是一致的,但是,两个锁实际是有很大的区别的,对象锁是用于对象实例方法,或者一个对象实例上的,类锁是用于类的静态方法或者一个类的class对象上的。我们知道,类的对象实例可以有很多个,但是每个类只有一个class对象,所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。但是有一点必须注意的是,其实类锁只是一个概念上的东西,并不是真实存在的,它只是用来帮助我们理解锁定实例方法和静态方法的区别的。

    其实,类锁修饰方法和代码块的效果和对象锁是一样的,因为类锁只是一个抽象出来的概念,只是为了区别静态方法的特点,因为静态方法是所有对象实例共用的,所以对应着synchronized修饰的静态方法的锁也是唯一的,所以抽象出来个类锁。其实这里的重点在下面这块代码,synchronized同时修饰静态和非静态方法。

    上面代码synchronized同时修饰静态方法和实例方法,但是运行结果是交替进行的,这证明了类锁和对象锁是两个不一样的锁,控制着不同的区域,它们是互不干扰的。同样,线程获得对象锁的同时,也可以获得该类锁,即同时获得两个锁,这是允许的。
    到这里,对synchronized的用法已经有了一定的了解。
    打个比方:一个object就像一个大房子,大门永远打开。房子里有很多房间(也就是方法)。这些房间有上锁的(synchronized方法),和不上锁之分(普通方法)。房门口放着一把钥匙(key),这把钥匙可以打开所有上锁的房间。另外我把所有想调用该对象方法的线程比喻成想进入这房子某个房间的人。所有的东西就这么多了,下面我们看看这些东西之间如何作用的。在此我们先来明确一下我们的前提条件。该对象至少有一synchronized方法,否则这个key还有啥意义。当然也就不会有我们的这个主题了。一个人想进入某间上了锁的房间,他来到房子门口,看见钥匙在那儿(说明暂时还没有其他人要使用上锁的房间)。于是他走上去拿到了钥匙,并且按照自己 的计划使用那些房间。注意一点,他每次使用完一次上锁的房间后会马上把钥匙还回去。即使他要连续使用两间上锁的房间,中间他也要把钥匙还回去,再取回来。因此,普通情况下钥匙的使用原则是:“随用随借,用完即还。”这时其他人可以不受限制的使用那些不上锁的房间,一个人用一间可以,两个人用一间也可以,没限制。但是如果当某个人想要进入上锁的房间,他就要跑到大门口去看看了。有钥匙当然拿了就走,没有的话,就只能等了。要是很多人在等这把钥匙,等钥匙还回来以后,谁会优先得到钥匙?Not guaranteed。像前面例子里那个想连续使用两个上锁房间的家伙,他中间还钥匙的时候如果还有其他人在等钥匙,那么没有任何保证这家伙能再次拿到。(JAVA规范在很多地方都明确说明不保证,像Thread.sleep()休息后多久会返回运行,相同优先权的线程那个首先被执行,当要访问对象的锁被释放后处于等待池的多个线程哪个会优先得到,等等。我想最终的决定权是在JVM,之所以不保证,就是因为JVM在做出上述决定的时候,绝不是简简单单根据 一个条件来做出判断,而是根据很多条。而由于判断条件太多,如果说出来可能会影响JAVA的推广,也可能是因为知识产权保护的原因吧。SUN给了个不保证就混过去了。无可厚非。但我相信这些不确定,并非完全不确定。因为计算机这东西本身就是按指令运行的。即使看起来很随机的现象,其实都是有规律可寻。学过计算机的都知道,计算机里随机数的学名是伪随机数,是人运用一定的方法写出来的,看上去随机罢了。另外,或许是因为要想弄的确太费事,也没多大意义,所 以不确定就不确定了吧。)

    再来看看同步代码块。和同步方法有小小的不同。

    1. 从尺寸上讲,同步代码块比同步方法小。你可以把同步代码块看成是没上锁房间里的一块用带锁的屏风隔开的空间。
    2. 同步代码块还可以人为的指定获得某个其它对象的key。就像是指定用哪一把钥匙才能开这个屏风的锁,你可以用本房的钥匙;你也可以指定用另一个房子的钥匙才能开,这样的话,你要跑到另一栋房子那儿把那个钥匙拿来,并用那个房子的钥匙来打开这个房子的带锁的屏风。

    记住你获得的那另一栋房子的钥匙,并不影响其他人进入那栋房子没有锁的房间。

    为什么要使用同步代码块呢?我想应该是这样的:首先对程序来讲同步的部分很影响运行效率,而一个方法通常是先创建一些局部变量,再对这些变量做一些 操作,如运算,显示等等;而同步所覆盖的代码越多,对效率的影响就越严重。因此我们通常尽量缩小其影响范围。

    如何做?同步代码块。我们只把一个方法中该同步的地方同步,比如运算。

    另外,同步代码块可以指定钥匙这一特点有个额外的好处,是可以在一定时期内霸占某个对象的key。还记得前面说过普通情况下钥匙的使用原则吗。现在不是普通情况了。你所取得的那把钥匙不是永远不还,而是在退出同步代码块时才还。

    还用前面那个想连续用两个上锁房间的家伙打比方。怎样才能在用完一间以后,继续使用另一间呢。用同步代码块吧。先创建另外一个线程,做一个同步代码 块,把那个代码块的锁指向这个房子的钥匙。然后启动那个线程。只要你能在进入那个代码块时抓到这房子的钥匙,你就可以一直保留到退出那个代码块。也就是说 你甚至可以对本房内所有上锁的房间遍历,甚至再sleep(10*60*1000),而房门口却还有1000个线程在等这把钥匙呢。很过瘾吧。

    我们知道静态 方法被整个类拥有,所有的类的对象共用这一个方法,那么synchronized关键字修饰static方法所获得的锁是这个类的锁,而不是这个对象的锁。那么一个类的锁和一个类的对象的锁有什么关系呢?

    每个类都有一个互斥锁,每个类可能会有多个不同的对象,每个对象都有一个它自己的对象锁。自然,类的锁的管辖范围比对象的锁的管辖范围大,不同的对象之间的锁互不影响,但是他们都受类的锁的控制,如果一个类的锁被一个线程获得,那么这个类的所有的对象都不能访问需要获得类的锁的方法,但是可以访问不需要锁的方法和需要某个对象锁的方法。

    两个方法是不是互斥的关键是看两个方法取得的锁是不是互斥的,如果锁是互斥的,那么方法也是互斥访问的,如果锁不是互斥的,那么不同的锁之间是不会有什么影响的,所以这时方法是可以同时访问的。

    一个类里面有两个synchronized方法是否可以同步执行?

    (这个题目要分情况回答么,修饰的方法都是static或者非static,或者者一个static的一个非static的)

    我回答说可以同步执行,因为synchronized是在方法上锁定,又不是在类上锁定,可面试官说我错了,回家后写测试代码如下: 今天面试,一个类里面有两个synchronized方法是否可以同步执行?我回答说可以同步执行,因为synchronized是在方法上锁定,又不是在类上锁定,可面试官说我错了,回家后写测试代码如下:
    翻看相关书籍,发现jvm在执行方法以前,如果发现该方法前面有对象的synchronized关键字,就现在该对象的ID上加锁,当其他线程执行同时执行这个方法时,会检测改对象ID上是否加锁,如果加锁时就等待锁释放。

    1. 初始化一个对象时,自动有一个对象锁。synchronized {普通方法}依靠对象锁工作,多线程访问synchronized方法,一旦某个进程抢得锁之后,其他的进程只有排队对待。
      synchronized {普通方法}依靠对象锁工作,多线程访问synchronized方法,一旦某个进程抢得锁之后,其他的进程只有排队对待。
    2. synchronized {修饰代码块}的作用不仅于此,synchronized void method{}整个函数加上synchronized块,效率并不好。在函数内部,可能我们需要同步的只是小部分共享数据,其他数据,可以自由访问,这时候我们可以用 synchronized(表达式){//语句}更加精确的控制。
    3. synchronized {static方法}此代码块等效于
      void method{
          synchronized(Obl.class)
      }
      使用该类的类对象的锁定去做线程的共享互斥。

    今天写java的时候,在一个类中写入了2个synchronized 方法。而方法1调用方法2,执行的时候老是卡住,无法往下走。经过查阅资料,发现 synchronized方法执行的时候,synchronized方法影响的范围并不是单单方法本身,而是这个类中所有带有synchronized 的方法,synchronized线程都会等待其执行完成。这样子a调用了b,而b又要等待a执行完成才能执行,造成了死锁。(这段话不太正确)

    public class TestLock implements Runnable {
        @Override
        public void run() {
            print1();
        }
    
        private synchronized void print1() {
            System.out.println("111111");
            // 如果是对象锁中synchronized的方法调用的话, 因为线程拿到的其实是同一把锁并不会锁住呀
            print2();
        }
    
        private synchronized void print2() {
            System.out.println("222222");
        }
    
        public static void main(String[] args) {
            new Thread(new TestLock()).start();
        }
    }


    线程的同步锁机制:wait()、notify()和notifyAll()

    在synchronized代码执行期间,线程可以调用对象的wait()方法,释放对象锁标志,进入等待状态,并且可以调用notify()或者notifyAll()方法通知正在等待的其他线程。notify()通知的是等待队列中的第一个线程,notifyAll()通知的是等待队列中的所有线程。

    现在假设一种情况:线程1与线程2都要访问某个数据变量,并且要求线程1的访问先于线程2,则这时仅用synchronized是不能解决问题的。在UNIX或Windows NT中可用Simaphore来实现,而Java并不提供。在Java中提供的是wait()和notify机制,使用如下:

    wait()notify()notifyAll()方法由java.lang.Object类提供,这3个方法用于协调多个线程对共享数据的存取,所以必须在synchronized语句块内使用这3个方法。wait()必须在synchronized函数或者代码块里面,wait()让已经获得synchronized函数或者代码块控制权的Thread暂时休息,并且丧失控制权,这个时候,由于该线程丧失控制权并且进入等待,其他线程就能取得控制权,并且在适当的情况下调用notifyAll()唤醒wait()的线程。

    需要注意的是,被唤醒的线程由于已经丧失了控制权,所以需要等待唤醒它的线程结束操作,从而才能重新获得控制权。所以,wait()的确是马上让当前线程丧失控制权,其他的线程可以趁虚而入。所以wait的使用,必须存在两个以上的线程,而且必须在不同的条件下唤醒wait()中的线程。

    public class WaitTest {
        public static void main(String[] args) {
            ThreadA t1 = new ThreadA("t1");
            // 获取线程t1的锁, 并锁住
            synchronized (t1) {
                try {
                    System.out.println(Thread.currentThread().getName() + " start t1");
                    // 线程t1在此代码块中已经被锁住了, 此时调用t1.start会等待锁
                    t1.start();
                    // sleep方法不会释放锁
                    Thread.sleep(2000);
                    System.out.println(Thread.currentThread().getName() + " wait() ");
                    // 主线程主动放弃锁, t1对象的等待队列会获取锁
                    // 本例中ThreadA中的run方法会执行
                    t1.wait();
                    System.out.println(Thread.currentThread().getName() + " continue");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    class ThreadA extends Thread {
        public ThreadA(String name) {
            super(name);
        }
    
        @Override
        public void run() {
            // 以该线程的类实例为锁
            synchronized (this) {
                try {
                    Thread.sleep(2000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "call notify()");
                this.notify();
            }
        }
    }

    下面是wait中设置超时,自己唤醒

    public class WaitTest {
        public static void main(String[] args) {
            ThreadA t1 = new ThreadA("t1");
            // 获取线程t1的锁, 并锁住
            synchronized (t1) {
                try {
                    System.out.println(Thread.currentThread().getName() + " start t1");
                    // 线程t1在此代码块中已经被锁住了, 此时调用t1.start会等待锁
                    t1.start();
                    System.out.println(Thread.currentThread().getName() + " wait() ");
                    // 主线程主动放弃锁, t1对象的等待队列会获取锁
                    // 本例中ThreadA中的run方法会执行
                    t1.wait(3000);
                    System.out.println(Thread.currentThread().getName() + " continue");
                    t1.stop();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    class ThreadA extends Thread {
        public ThreadA(String name) {
            super(name);
        }
    
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + " run");
            while(true) {
            }
        }
    }
    总结wait,notify和notifyAll
    1. wait()、notify/notifyAll() 方法是Object的本地final方法,无法被重写。
    2. wait()使当前线程阻塞,前提是必须先获得锁,一般配合synchronized关键字使用,一般在synchronized 同步代码块里使用 wait()、notify/notifyAll() 方法。
    3. 由于wait()、notify/notifyAll()在synchronized代码块执行,说明当前线程一定是获取了锁的。当线程执行wait()方法时候,会释放当前的锁,然后让出CPU,进入等待状态。只有当notify/notifyAll()被执行时候,才会唤醒一个或多个正处于等待状态的线程,然后继续往下执行,直到执行完synchronized代码块的代码或是中途遇到wait(),再次释放锁。也就是说,notify/notifyAll()的执行只是唤醒沉睡的线程,而不会立即释放锁,锁的释放要看代码块的具体执行情况。所以在编程中,尽量在使用了notify/notifyAll()后立即退出临界区,以唤醒其他线程
    4. wait()需要被try-catch包围,中断也可以使wait等待的线程唤醒。
    5. notify和wait的顺序不能错,如果A线程先执行notify方法,B线程在执行wait方法,那么B线程是无法被唤醒的。
    6. notify 和 notifyAll的区别
      notify方法只唤醒一个等待(对象的)线程并使该线程开始执行。所以如果有多个线程等待一个对象,这个方法只会唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管理的实现。notifyAll会唤醒所有等待(对象的)线程,尽管哪一个线程将会第一个处理取决于操作系统的实现。如果当前情况下有多个线程需要被唤醒,推荐使用notifyAll方法。比如在生产者-消费者里面的使用,每次都需要唤醒所有的消费者或是生产者,以判断程序是否可以继续往下执行。
    7. 在多线程中要测试某个条件的变化,使用if 还是while?
      要注意,notify唤醒沉睡的线程后,线程会接着上次的执行继续往下执行。所以在进行条件判断时候,可以先把wait语句忽略不计来进行考虑,显然,要确保程序一定要执行,并且要保证程序直到满足一定的条件再执行,要使用while来执行,以确保条件满足和一定执行。
    生产者-消费者模型的实现


    产品:Product

    public class Product {
        private String producedBy = "N/A";
        private String consumedBy = "N/A";
    
        // 构造函数,指明生产者名字
        Product(String producedBy) {
            this.producedBy = producedBy;
        }
    
        public void consume(String consumedBy) {
            this.consumedBy = consumedBy;
        }
    
        @Override
        public String toString() {
            return "产品, 生产者 = " + producedBy + ", 消费者 = " + consumedBy;
        }
    
        public String getProducedBy() {
            return producedBy;
        }
    
        public void setProducedBy(String producedBy) {
            this.producedBy = producedBy;
        }
    
        public String getConsumedBy() {
            return consumedBy;
        }
    
        public void setConsumedBy(String consumedBy) {
            this.consumedBy = consumedBy;
        }
    }

    仓库:ProductList

    public class ProductList {
    
        private int index = 0;
        Product[] productList = new Product[6];
    
        // push是用来让生产者放置产品的
        public synchronized void push(Product product) {
            // 如果仓库满了
            while(index == productList.length) {
                try {
                    System.out.println(product.getProducedBy() + " is waiting.");
                    // 等待, 并且从这里退出push()
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            productList[index] = product;
            index++;
            System.out.println(index + " " + product.getProducedBy() + " 生产了:" + product);
            // 因为不确定有没有线程在wait, 所以既然生产了产品, 就唤醒可能等待的消费者
            notifyAll();
            System.out.println(product.getProducedBy() + " send a notifyAll()");
        }
    
        // pop用来让消费者取出产品
        public synchronized Product pop(String consumerName) {
            // 如果仓库空了
            while(index == 0) {
                try {
                    System.out.println(consumerName + " is waiting.");
                    // 等待, 并且从这里退出pop
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // 注意notifyAll以后并没有退出,而是继续执行完毕
            index--;
            Product product = productList[index];
            product.consume(consumerName);
            System.out.println(index + " " + product.getConsumedBy() + "消费了: " + product);
            notifyAll();
            System.out.println(consumerName + " send a notifyAll().");
            return product;
        }
    }

    生产者:Producer

    public class Producer implements Runnable {
        private String name;
        private ProductList ps = null;
        Producer(ProductList ps, String name) {
            this.ps = ps;
            this.name = name;
        }
    
        @Override
        public void run() {
            while(true) {
                Product product = new Product(name);
                ps.push(product);
                try {
                    Thread.sleep((int)(Math.random() * 200));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }


    消费者:Consumer

    public class Consumer implements Runnable {
        private String name;
        ProductList ps = null;
        Consumer(ProductList ps, String name) {
            this.ps = ps;
            this.name = name;
        }
        @Override
        public void run() {
            while(true) {
                ps.pop(name);
                try {
                    Thread.sleep((int) (Math.random() * 1000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }


    测试类:TestModel

    public class TestModel {
        public static void main(String[] args) {
            ProductList ps = new ProductList();
            Producer px = new Producer(ps, "生产者X");
            Producer py = new Producer(ps, "生产者Y");
            Consumer ca = new Consumer(ps, "消费者A");
            Consumer cb = new Consumer(ps, "消费者B");
            Consumer cc = new Consumer(ps, "消费者C");
            new Thread(px).start();
            new Thread(py).start();
            new Thread(ca).start();
            new Thread(cb).start();
            new Thread(cc).start();
        }
    }


    感觉这个代码结构十分清楚,自己之前也写了一个生产者和消费者的模型,但是感觉自己都快忘了,好像是用了单独的两把锁,并没有用仓库当作锁。http://www.cnblogs.com/tuhooo/p/7736436.html

  • 相关阅读:
    页面元素定位-CSS元素基本定位
    自动化测试理论知识以及相关框架
    flask自学
    关于测试报告的一些理解
    jenkins、k8s、docker学习
    常用装饰器
    正则
    pycharm设置展示运行结果行数以及python一些好用的模块
    python相关知识点3,线程锁、单例模式
    python相关知识点2(各种内置方法)
  • 原文地址:https://www.cnblogs.com/tuhooo/p/9057802.html
Copyright © 2011-2022 走看看