美文网首页
java多线程

java多线程

作者: 蛋炒饭_By | 来源:发表于2018-01-16 17:06 被阅读4次

    <pre>

    理解程序、进程、线程的概念

    程序可以理解为静态的代码

    进程可以理解为执行中的程序

    线程可以理解为进程的进一步细分,程序的一条执行路径

    使用多线程的优点:

    提高应用程序的响应。对图形化界面更有意义,可增强用户体验。

    提高计算机系统CPU的利用率

    改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改

    在java中要想实现多线程,有两种手段,一种是继续Thread类,另外一种是实现Runable接口

    继承java.lang.Thread类

    下面来看一个简单的实例:

    class Thread1 extends Thread{
        private String name;
        public Thread1(String name) {
           this.name=name;
        }
        public void run() {
            for (int i = 0; i < 5; i++) {
                System.out.println(name + "运行  :  " + i);
                try {
                    sleep((int) Math.random() * 10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    
        }
    }
    public class Main {
    
        public static void main(String[] args) {
            Thread1 mTh1=new Thread1("A");
            Thread1 mTh2=new Thread1("B");
            mTh1.start();
            mTh2.start();
    
        }
    
    }
    
    

    输出

    输出:
    A运行  :  0
    B运行  :  0
    A运行  :  1
    A运行  :  2
    A运行  :  3
    A运行  :  4
    B运行  :  1
    B运行  :  2
    B运行  :  3
    B运行  :  4
    再运行一下:
    A运行  :  0
    B运行  :  0
    B运行  :  1
    B运行  :  2
    B运行  :  3
    B运行  :  4
    A运行  :  1
    A运行  :  2
    A运行  :  3
    A运行  :  4
    
    

    说明:

    程序启动运行main时候,java虚拟机启动一个进程,主线程main在main()调用时候被创建。随着调用Thread1的两个对象的start方法,另外两个线程也启动了,这样,整个应用就在多线程下运行。

    以下是关系到线程运行状态的几个方法:

    1)start方法

    start()用来启动一个线程,当调用start方法后,系统才会开启一个新的线程来执行用户定义的子任务,在这个过程中,会为相应的线程分配需要的资源。

    2)run方法

    run()方法是不需要用户来调用的,当通过start方法启动一个线程之后,当线程获得了CPU执行时间,便进入run方法体去执行具体的任务。注意,继承Thread类必须重写run方法,在run方法中定义具体要执行的任务。

    3)sleep方法

      sleep相当于让线程睡眠,交出CPU,让CPU去执行其他的任务。
    
    

    实现java.lang.Runnable接口

    用Runnable也是非常常见的一种,我们只需要重写run方法即可。下面也来看个实例:

    class Thread2 implements Runnable{
        private String name;
    
        public Thread2(String name) {
            this.name=name;
        }
    
        @Override
        public void run() {
              for (int i = 0; i < 5; i++) {
                    System.out.println(name + "运行  :  " + i);
                    try {
                        Thread.sleep((int) Math.random() * 10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
    
        }
    
    }
    public class Main {
    
        public static void main(String[] args) {
            new Thread(new Thread2("C")).start();
            new Thread(new Thread2("D")).start();
        }
    
    }
    
    

    输出

    输出:
    C运行  :  0
    D运行  :  0
    D运行  :  1
    C运行  :  1
    D运行  :  2
    C运行  :  2
    D运行  :  3
    C运行  :  3
    D运行  :  4
    C运行  :  4
    
    

    说明:

    Thread2类通过实现Runnable接口,使得该类有了多线程类的特征。run()方法是多线程程序的一个约定。所有的多线程代码都在run方法里面。Thread类实际上也是实现了Runnable接口的类。

    在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象,然后调用Thread对象的start()方法来运行多线程代码。

    实际上所有的多线程代码都是通过运行Thread的start()方法来运行的。因此,不管是扩展Thread类还是实现Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的,熟悉Thread类的API是进行多线程编程的基础。

    Thread和Runnable的区别

    对比一下继承的方式 vs 实现的方式

    1.联系:public class Thread implements Runnable(继承的方式的Thread也实现了Runnable接口)

    2.哪个方式好?

    实现的方式优于继承的方式 why?

    ① 避免了java单继承的局限性

    ② 如果多个线程要操作同一份资源(或数据),更适合使用实现的方式

    看一个例子:

    //模拟火车站售票窗口,开启三个窗口售票,总票数为100张
    //存在线程的安全问题
    class Window extends Thread {
        int ticket = 100;
    
        public void run() {
            while (true) {
                if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName() + "售票,票号为:"+ ticket--);
                } else {
                    break;
                }
            }
        }
    }
    
    public class TestWindow {
        public static void main(String[] args) {
            Window w1 = new Window();
            Window w2 = new Window();
            Window w3 = new Window();
    
            w1.setName("窗口1");
            w2.setName("窗口2");
            w3.setName("窗口3");
    
            w1.start();
            w2.start();
            w3.start();
        }
    
    }
    
    
    class Window implements Runnable {
         int ticket = 100;//要将全局变量声明为静态,不然每个对象都有这个属性,会卖出300张票
    
        public void run() {
            while (true) {
                if (ticket > 0) {
                    try {
                        Thread.sleep(20);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "售票,票号为:"+ ticket--);
                } else {
                    break;
                }
            }
        }
    }
    
    public class Main {
    
        //模拟火车站售票窗口,开启三个窗口售票,总票数为100张
    //存在线程的安全问题
    
        public static void main(String[] args) {
            Window w1 = new Window();
            Thread t1 = new Thread(w1, "t1");
            Thread t2 = new Thread(w1, "t2");
            Thread t3 = new Thread(w1, "t3");
    
            t1.start();
            t2.start();
            t3.start();
        }
    
    }
    
    

    线程的生命周期

    image

    线程调度

    1、调整线程优先级:Java线程有优先级,优先级高的线程会获得较多的运行机会。

    Java线程的优先级用整数表示,取值范围是1~10,Thread类有以下三个静态常量:

    static int MAX_PRIORITY

    线程可以具有的最高优先级,取值为10。

    static int MIN_PRIORITY

    线程可以具有的最低优先级,取值为1。

    static int NORM_PRIORITY

    分配给线程的默认优先级,取值为5。

    Thread类的setPriority()和getPriority()方法分别用来设置和获取线程的优先级。

    2、线程睡眠:Thread.sleep(long millis)方法,使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(Runnable)状态。sleep()平台移植性好。

    3、线程等待:Object类中的wait()方法,导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 唤醒方法。

    4、线程让步:Thread.yield() 方法,暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。

    5、线程加入:join()方法,等待其他线程终止。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。

    6、线程唤醒:Object类中的notify()方法,唤醒在此对象监视器上等待的单个线程。

    注意:Thread中suspend()和resume()两个方法在JDK1.5中已经废除,不再介绍。因为有死锁倾向。

    代码示例

    join():指等待某线程终止。

    为什么要用join()方法

    在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。

    class Thread1 extends Thread{
        private String name;
        public Thread1(String name) {
            super(name);
           this.name=name;
        }
        public void run() {
            System.out.println(Thread.currentThread().getName() + " 线程运行开始!");
            for (int i = 0; i < 5; i++) {
                System.out.println("子线程"+name + "运行 : " + i);
                try {
                    sleep((int) Math.random() * 10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + " 线程运行结束!");
        }
    }
    
    public class Main {
    
        public static void main(String[] args) {
            System.out.println(Thread.currentThread().getName()+"主线程运行开始!");
            Thread1 mTh1=new Thread1("A");
            Thread1 mTh2=new Thread1("B");
            mTh1.start();
            mTh2.start();
            System.out.println(Thread.currentThread().getName()+ "主线程运行结束!");
    
        }
    
    }
    
    
    输出结果:
    main主线程运行开始!
    main主线程运行结束!
    B 线程运行开始!
    子线程B运行 : 0
    A 线程运行开始!
    子线程A运行 : 0
    子线程B运行 : 1
    子线程A运行 : 1
    子线程A运行 : 2
    子线程A运行 : 3
    子线程A运行 : 4
    A 线程运行结束!
    子线程B运行 : 2
    子线程B运行 : 3
    子线程B运行 : 4
    B 线程运行结束!
    发现主线程比子线程早结束
    
    

    加join

    public class Main {
    
        public static void main(String[] args) {
            System.out.println(Thread.currentThread().getName()+"主线程运行开始!");
            Thread1 mTh1=new Thread1("A");
            Thread1 mTh2=new Thread1("B");
            mTh1.start();
            mTh2.start();
            try {
                mTh1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                mTh2.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+ "主线程运行结束!");
    
        }
    
    }
    
    
    运行结果:
    main主线程运行开始!
    A 线程运行开始!
    子线程A运行 : 0
    B 线程运行开始!
    子线程B运行 : 0
    子线程A运行 : 1
    子线程B运行 : 1
    子线程A运行 : 2
    子线程B运行 : 2
    子线程A运行 : 3
    子线程B运行 : 3
    子线程A运行 : 4
    子线程B运行 : 4
    A 线程运行结束!
    主线程一定会等子线程都结束了才结束
    
    

    yield

    Thread.yield()方法作用是:暂停当前正在执行的线程对象,并执行其他线程。

    yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。

    结论:yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。

    class ThreadYield extends Thread{
        public ThreadYield(String name) {
            super(name);
        }
    
        @Override
        public void run() {
            for (int i = 1; i <= 50; i++) {
                System.out.println("" + this.getName() + "-----" + i);
                // 当i为30时,该线程就会把CPU时间让掉,让其他或者自己的线程执行(也就是谁先抢到谁执行)
                if (i ==30) {
                    this.yield();
                }
            }
    
    }
    }
    
    public class Main {
    
        public static void main(String[] args) {
    
            ThreadYield yt1 = new ThreadYield("张三");
            ThreadYield yt2 = new ThreadYield("李四");
            yt1.start();
            yt2.start();
        }
    
    }
    
    

    运行结果:

    第一种情况:李四(线程)当执行到30时会CPU时间让掉,这时张三(线程)抢到CPU时间并执行。

    第二种情况:李四(线程)当执行到30时会CPU时间让掉,这时李四(线程)抢到CPU时间并执行。

    sleep()和yield()的区别

    sleep()和yield()的区别):sleep()使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会被执行;yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。

    sleep 方法使当前运行中的线程睡眼一段时间,进入不可运行状态,这段时间的长短是由程序设定的,yield 方法使当前线程让出 CPU 占有权,但让出的时间是不可设定的。实际上,yield()方法对应了如下操作:先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把 CPU 的占有权交给此线程,否则,继续运行原来的线程。所以yield()方法称为“退让”,它把运行机会让给了同等优先级的其他线程。

    线程的同步

    1、线程安全问题存在的原因:

    由于一个线程在操作共享数据过程中,未执行完毕的情况下,另外的线程参与进来,导致共享数据存在了安全问题。

    2、如何解决线程安全问题

    必须让一个线程操作共享数据完毕以后,其它线程才有机会参与共享数据的操作。

    3、java如何实现线程安全:线程的同步机制

    方式一:同步代码块

    synchronized(同步监视器){

    //需要被同步的代码块(即为操作共享数据的代码)

    }

    1、共享数据:多个线程共同操作的同一个数据(变量)

    2、同步监视器:由任何一个类的对象来充当。哪个线程获取此监视器,谁就执行大括号里被同步的代码。俗称:锁

    注:在实现Runnable接口的方式中,考虑同步的话,可以使用this来充当锁。但是在继承的方式中,慎用this

    class Window2 implements Runnable {
        int ticket = 1000;// 共享数据
    
        public void run() {
    
            while (true) {
                synchronized (this) {//this表示当前对象,本题中即为w
                    if (ticket > 0) {
                        try {
                            Thread.currentThread().sleep(10);
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName()
                                + "售票,票号为:" + ticket--);
                    }
                }
            }
        }
    }
    
    public class TestWindow2 {
        public static void main(String[] args) {
            Window2 w = new Window2();
            Thread t1 = new Thread(w);
            Thread t2 = new Thread(w);
            Thread t3 = new Thread(w);
    
            t1.setName("窗口1");
            t2.setName("窗口2");
            t3.setName("窗口3");
    
            t1.start();
            t2.start();
            t3.start();
        }
    }
    
    

    方式二:同步方法

    将操作共享数据的方法声明为synchronized. 即此方法为同步方法,能够保证当其中一个线程执行此方法时,其他线程在外等待直至此线程执行完此方法。

    同步方法的锁:this(不用显式的写)

    class Window4 implements Runnable {
        int ticket = 1000;// 共享数据
    
        public void run() {
            while (true) {
                show();
            }
        }
    
        public synchronized void show() {
            if (ticket > 0) {
                try {
                    Thread.currentThread().sleep(10);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "售票,票号为:"
                        + ticket--);
            }
    
        }
    }
    
    public class TestWindow4 {
        public static void main(String[] args) {
            Window4 w = new Window4();
            Thread t1 = new Thread(w);
            Thread t2 = new Thread(w);
            Thread t3 = new Thread(w);
    
            t1.setName("窗口1");
            t2.setName("窗口2");
            t3.setName("窗口3");
    
            t1.start();
            t2.start();
            t3.start();
        }
    }
    
    

    练习

    银行有一个账户。有两个储户分别向同一个账户存3000元,每次存1000,存3次。每次存完打印账户余额。

    class Account{
        double balance;//余额
        public Account(){
    
        }
        //存钱
        public synchronized void deposit(double amt){
            notify();
            balance+=amt;
            try {
                Thread.currentThread().sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+":"+balance);
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    class Customer extends Thread{
        Account account;
        public Customer(Account account){
            this.account=account;
        }
        public void run(){
            for(int i=0;i<3;i++){
                account.deposit(1000);
            }
        }
    }
    public class TestAccount {
        public static void main(String[] args) {
            Account acct = new Account();
            Customer c1 = new Customer(acct);
            Customer c2 = new Customer(acct);
    
            c1.setName("甲");
            c2.setName("乙");
    
            c1.start();
            c2.start();
        }
    }
    
    

    小结:

    释放锁的操作

    当前线程的同步方法、同步代码块执行结束

    当前线程在同步代码块、同步方法中遇到break、return终止了该代码块、该方法的继续执行。

    当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束

    当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。

    不会释放锁的操作

    线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行

    线程的死锁问题

    死锁:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁

    解决方法:专门的算法、原则;尽量减少同步资源的定义

    /死锁的问题:处理线程同步时容易出现。
    //不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
    //写代码时,要避免死锁!
    public class TestDeadLock {
        static StringBuffer sb1 = new StringBuffer();
        static StringBuffer sb2 = new StringBuffer();
    
        public static void main(String[] args) {
            new Thread() {
                public void run() {
                    synchronized (sb1) {
                        try {
                            Thread.currentThread().sleep(10);//问题放大
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        sb1.append("A");
                        synchronized (sb2) {
                            sb2.append("B");
                            System.out.println(sb1);
                            System.out.println(sb2);
                        }
                    }
                }
            }.start();
    
            new Thread() {
                public void run() {
                    synchronized (sb2) {
                        try {
                            Thread.currentThread().sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        sb1.append("C");
                        synchronized (sb1) {
                            sb2.append("D");
                            System.out.println(sb1);
                            System.out.println(sb2);
                        }
                    }
                }
            }.start();
        }
    
    }
    
    

    线程通信

    wait() 与 notify() 和 notifyAll()

    wait():令当前线程挂起并放弃CPU、同步资源,使别的线程可访问并修改共享资源,而当前线程排队等候再次对资源的访问

    notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待

    notifyAll ():唤醒正在排队等待资源的所有线程结束等待.

    wait是指在一个已经进入了同步锁的线程内,让自己暂时让出同步锁,以便其他正在等待此锁的线程可以得到同步锁并运行,只有其他线程调用了notify方法(notify并不释放锁,只是告诉调用过wait方法的线程可以去参与获得锁的竞争了,但不是马上得到锁,因为锁还在别人手里,别人还没释放),调用wait方法的一个或多个线程就会解除wait状态,重新参与竞争对象锁,程序如果可以再次得到锁,就可以继续向下运行。

    1)wait()、notify()和notifyAll()方法是本地方法,并且为final方法,无法被重写。

    2)当前线程必须拥有此对象的monitor(即锁),才能调用某个对象的wait()方法能让当前线程阻塞,

    (这种阻塞是通过提前释放synchronized锁,重新去请求锁导致的阻塞,这种请求必须有其他线程通过notify()或者notifyAll()唤醒重新竞争获得锁)

    3)调用某个对象的notify()方法能够唤醒一个正在等待这个对象的monitor的线程,如果有多个线程都在等待这个对象的monitor,则只能唤醒其中一个线程;

    (notify()或者notifyAll()方法并不是真正释放锁,必须等到synchronized方法或者语法块执行完才真正释放锁)

    4)调用notifyAll()方法能够唤醒所有正在等待这个对象的monitor的线程,唤醒的线程获得锁的概率是随机的,取决于cpu调度

    例子1(错误使用导致线程阻塞):三个线程,线程3先拥有sum对象的锁,然后通过sum.notify()方法通知等待sum锁的线程去获得锁,但是这个时候线程1,2并没有处于wait()导致的阻塞状态,而是在synchronized方法块处阻塞了,所以,这次notify()根本没有通知到线程1,2。然后线程3正常结束,释放掉sum锁,这个时候,线程1就立刻获得了sum对象的锁(通过synchronized获得),然后调用sum.wait()方法释放掉sum的锁,线程2随后获得了sum对象的线程锁(通过synchronized获得),这个时候线程1,2都处于阻塞状态,但是悲催的是,这之后再也没有线程主动调用sum.notify()或者notifyAll()方法显示唤醒这两个线程,所以程序阻塞.

    public class CyclicBarrierTest {  
    
        public static void main(String[] args) throws Exception {  
            final Sum sum=new Sum();  
    
            new Thread(new Runnable() {  
                @Override  
                public void  run() {  
                    try {  
                        synchronized (sum) {  
                            System.out.println("thread3 get lock");  
                            sum.sum();  
                            sum.notifyAll(); //此时唤醒没有作用,没有线程等待  
                            Thread.sleep(2000);  
                            System.out.println("thread3 really release lock");  
                        }  
    
                    } catch (Exception e) {  
                        e.printStackTrace();  
                    }  
                }  
            }).start();  
    
            new Thread(new Runnable() {  
                @Override  
                public void  run() {  
                    try {  
                        synchronized (sum) {  
                            System.out.println("thread1 get lock");  
                            sum.wait();//主动释放掉sum对象锁  
                            System.out.println(sum.total);  
                            System.out.println("thread1 release lock");  
                        }  
                    } catch (Exception e) {  
                        e.printStackTrace();  
                    }  
                }  
            }).start();  
    
            new Thread(new Runnable() {  
                @Override  
                public void  run() {  
                    try {  
                        synchronized (sum) {  
                            System.out.println("thread2 get lock");  
                            sum.wait();  //释放sum的对象锁,等待其他对象唤醒(其他对象释放sum锁)  
                            System.out.println(sum.total);  
                            System.out.println("thread2 release lock");  
                        }  
                    } catch (Exception e) {  
                        e.printStackTrace();  
                    }  
                }  
            }).start();  
        }  
    
    }  
    
    class Sum{  
        public Integer total=0;  
    
        public void  sum() throws Exception{  
            total=100;  
            Thread.sleep(5000);  
        }  
    
    }  
    
    

    运行结果:

    thread3 get lock  
    thread3 really release lock  
    thread2 get lock  
    thread1 get lock  
    //程序后面一直阻塞  
    
    

    例子2:还是上面程序,顺序不同,把线程3放到最下面。最后线程1,2都因为没有再次获得线程导致线程阻塞

    运行过程:

    线程1先运行获得sum对象锁(通过synchronized),但是随后执行了sum.wait()方法,主动释放掉了sum对象锁,然后线程2获得了sum对象锁(通过synchronized),也通过sum.wait()失去sum的对象锁,最后线程3获得了sum对象锁(通过synchronized),主动通过sum.notify()通知了线程1或者2,假设是1,线程1重新通过notify()/notifyAll()的方式获得了锁,然后执行完毕,随后线程释放锁,然后这个时候线程2成功获得锁,执行完毕。

    public class CyclicBarrierTest {  
    
        public static void main(String[] args) throws Exception {  
            final Sum sum=new Sum();  
    
            new Thread(new Runnable() {  
                @Override  
                public void  run() {  
                    try {  
                        synchronized (sum) {  
                            System.out.println("thread1 get lock");  
                            sum.wait();//主动释放sum对象锁,等待唤醒  
                            System.out.println(sum.total);  
                            System.out.println("thread1 release lock");  
                        }  
                    } catch (Exception e) {  
                        e.printStackTrace();  
                    }  
                }  
            }).start();  
    
            new Thread(new Runnable() {  
                @Override  
                public void  run() {  
                    try {  
                        synchronized (sum) {  
                            System.out.println("thread2 get lock");  
                            sum.wait();  //主动释放sum对象锁,等待唤醒  
                            System.out.println(sum.total);  
                            System.out.println("thread2 release lock");  
                        }  
                    } catch (Exception e) {  
                        e.printStackTrace();  
                    }  
                }  
            }).start();  
    
            new Thread(new Runnable() {  
                @Override  
                public void  run() {  
                    try {  
                        synchronized (sum) {  
                            System.out.println("thread3 get lock");  
                            sum.sum();  
                            sum.notifyAll();//唤醒其他等待线程(线程1,2)  
                            Thread.sleep(2000);  
                            System.out.println("thread3 really release lock");  
                        }  
    
                    } catch (Exception e) {  
                        e.printStackTrace();  
                    }  
                }  
            }).start();  
    
        }  
    
    }  
    
    class Sum{  
        public Integer total=0;  
    
        public void  sum() throws Exception{  
            total=100;  
            Thread.sleep(5000);  
        }  
    
    }  
    
    

    执行结果

    thread1 get lock  
    thread2 get lock  
    thread3 get lock  
    thread3 really release lock  
    100  
    thread2 release lock  
    100  
    thread1 release lock  
    
    

    经典例题:生产者/消费者问题

    生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。

    分析:

    1、是否涉及到多线程的问题?是!生产者、消费者

    2、是否涉及到共享数据?有!考虑线程安全问题

    3、此共享数据是谁?产品的数量

    4、是否涉及到线程的通信呢?存在生产者与消费者的通信

    class Clerk{//店员
        int product;
    
        public synchronized void addProduct(){//生产产品
            if(product >= 20){
                try {
                    wait();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }else{
                product++;
                System.out.println(Thread.currentThread().getName() + ":生产了第" + product + "个产品");
                notifyAll();
            }
        }
        public synchronized void consumeProduct(){//消费产品
            if(product <= 0){
                try {
                    wait();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }else{
                System.out.println(Thread.currentThread().getName() + ":消费了第" + product + "个产品");
                product--;
                notifyAll();
            }
        }
    }
    
    class Producer implements Runnable{//生产者
        Clerk clerk;
    
        public Producer(Clerk clerk){
            this.clerk = clerk;
        }
        public void run(){
            System.out.println("生产者开始生产产品");
            while(true){
                try {
                    Thread.currentThread().sleep(100);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                clerk.addProduct();
    
            }
        }
    }
    class Consumer implements Runnable{//消费者
        Clerk clerk;
        public Consumer(Clerk clerk){
            this.clerk = clerk;
        }
        public void run(){
            System.out.println("消费者消费产品");
            while(true){
                try {
                    Thread.currentThread().sleep(10);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                clerk.consumeProduct();
            }
        }
    }
    
    public class TestProduceConsume {
        public static void main(String[] args) {
            Clerk clerk = new Clerk();
            Producer p1 = new Producer(clerk);
            Consumer c1 = new Consumer(clerk);
            Thread t1 = new Thread(p1);//一个生产者的线程
            Thread t3 = new Thread(p1);
            Thread t2 = new Thread(c1);//一个消费者的线程
    
            t1.setName("生产者1");
            t2.setName("消费者1");
            t3.setName("生产者2");
    
            t1.start();
            t2.start();
            t3.start();
        }
    }
    

    相关文章

      网友评论

          本文标题:java多线程

          本文链接:https://www.haomeiwen.com/subject/uvxeoxtx.html