Java SE 10_Multi-threading

来源:互联网 发布:mac php集成开发环境 编辑:程序博客网 时间:2024/06/05 16:20

Java SE 10th day:Multi-threading

——线程操作案例

 ——生产者和消费者

1、本次课程知识点

1、掌握多线程的两种实现手段

2、了解多线程的基本操作方法

3、理解同步及死锁的概念

2、具体内容

2.1 进行与线程

从计算机操作系统的发展来看,经历了这样的两个阶段。

● 单进程处理:最传统的DOS系统中只要有病毒出现,则立刻有反映,因为在DOS系统中属于单线程处理,即:在同一个时间段上只能有一个程序在执行。

● 多线程处理:windows操作系统是一个多进程,例如,假设在windows中出现病毒了,则系统照样可以使用。

那么对于资源来讲,所有的IO设备、CPU等等都只有一个,那么对于多线程的处理来讲,在同一个时间段上会有多个程序运行,但是在同一个时间点只能有一个程序运行。

线程是在进程的基础上的进一步划分,举一个不恰当的例子来说,word中的拼写检查,是在word整个程序运行中运行的。

所以,如果进程消失了,则线程就消失,而如果线程消失的话,则进程依然会执行,未必会消失。

Java本身是属于多线程的操作语言,所以提供了线程的处理机制。

2.2 线程实现的两种方式(理解

在java中有两种方式实现多线程操作,一种是继承Thread类,另外一种是实现Runnable接口。

2.2.1 Thread

Thread类是在java.lang包中定义的。

一个类只要继承了Thread类,同时覆写了本类中的run()方法,则就可以实现多线程的操作了。

package org.threaddemo;

public class MyThread extends Thread {

    private String name; // 定义name属性

 

    publicMyThread(String name) {

       this.name = name;

    }

 

    public void run() { // 覆写run()方法

       for (int i = 0; i < 10; i++) {

           System.out.println("Thread运行:" +name + ",i = " + i);

       }

    }

}

以上的类已经完成了多线程的操作类,那么下面就启动线程。

package org.threaddemo;

public class ThreadDemo {

    public static void main(String[] args) {

       MyThread mt1 = new MyThread("线程A");

       MyThread mt2 = new MyThread("线程B");

       mt1.run(); // 调用线程体

       mt2.run(); // 调用线程体

    }

}

Thread运行:线程A,i = 0

Thread运行:线程A,i = 1

Thread运行:线程A,i = 2

Thread运行:线程A,i = 3

Thread运行:线程A,i = 4

Thread运行:线程A,i = 5

Thread运行:线程A,i = 6

Thread运行:线程A,i = 7

Thread运行:线程A,i = 8

Thread运行:线程A,i = 9

Thread运行:线程B,i = 0

Thread运行:线程B,i = 1

Thread运行:线程B,i = 2

Thread运行:线程B,i = 3

Thread运行:线程B,i = 4

Thread运行:线程B,i = 5

Thread运行:线程B,i = 6

Thread运行:线程B,i = 7

Thread运行:线程B,i = 8

Thread运行:线程B,i = 9

但是,此时的执行可以发现非常有规律,先执行玩第一个对象,再执行第二个对象,也就是说并没有实现交互的运行。

从JDK的文档中可以发现,一旦调用start()方法,则会通过JVM找run()方法

● public void start()

使用start()方法启动线程看一下

package org.threaddemo;

public class ThreadDemo {

    public static void main(String[] args) {

       MyThread mt1 = new MyThread("线程A");

       MyThread mt2 = new MyThread("线程B");

       mt1.start(); // 调用线程体

       mt2.start(); // 调用线程体

    }

}

Thread运行:线程A,i = 0

Thread运行:线程A,i = 1

Thread运行:线程A,i = 2

Thread运行:线程A,i = 3

Thread运行:线程B,i = 0

Thread运行:线程B,i = 1

Thread运行:线程B,i = 2

Thread运行:线程B,i = 3

Thread运行:线程B,i = 4

Thread运行:线程B,i = 5

Thread运行:线程B,i = 6

Thread运行:线程B,i = 7

Thread运行:线程B,i = 8

Thread运行:线程B,i = 9

Thread运行:线程A,i = 4

Thread运行:线程A,i = 5

Thread运行:线程A,i = 6

Thread运行:线程A,i = 7

Thread运行:线程A,i = 8

Thread运行:线程A,i = 9

此时,程序已经可以正常的进行交互式的运行了。

但是,需要思考的是,为什么非要使用start()方法启动多线程呢?

在JDK的安装路径下,src.zip是全部的java源程序,通过此代码找到Thread类中的start()方法的定义:

--jdk1.7.0_03版本(参考):

    public synchronized void start() {

        /**

         * This method is not invoked for the main method thread or "system"

         * group threads created/set up by the VM. Any new functionality added

         * to this method in the future may have to also be added to the VM.

         *

         * A zero status value corresponds to state "NEW".

         */

        if (threadStatus != 0)

            throw new IllegalThreadStateException();

 

        /* Notify the group that this thread is about to be started

         * so that it can be added to the group's list of threads

         * and the group's unstarted count can be decremented. */

        group.add(this);

 

        boolean started = false;

        try {

            start0();

            started = true;

        } finally {

            try {

                if (!started) {

                    group.threadStartFailed(this);

                }

            } catch (Throwable ignore) {

                /* do nothing. If start0 threw a Throwable then

                  it will be passed up the call stack */

            }

        }

    }

 

    private native void start0();

jdk1.5.0_04版本:

public synchronized void start(){  //定义的start()方法

    if(started)   //判断线程是否已经启动

       throw new IllegalThreadStateException();

    started = true;   //如果没有启动项修改状态

    start0();  //调用start0()方法

}

 

private native void start0();   //使用native关键字声明的方法,没有方法体

可以发现在start()方法上现在抛出了一个“IllegalThreadStateException”异常,之所以现在在此方法之中没有处理此异常,肯定是因为这个异常是RuntimeException的子类,观察此类的继承结构:

java.lang.Object

  java.lang.Throwable

      java.lang.Exception

          java.lang.RuntimeException

              java.lang.IllegalArgumentException

                  java.lang.IllegalThreadStateException

这个异常主要是发生在线程重复启动的情况下,所以每一个线程对象只能启动一次。

操作系统有很多,Windows、Linux、UNIX,既然多线程操作中要进行CPU资源的抢占,也就是说要等待CPU调度,那么这些调度的操作是由各个操作系统的底层实现的,所以在java程序中根本就没法实现,那么此时java的设计者定义了native关键字,使用此关键字表示可以调度操作系统的底层函数,那么这样的技术又称为JNI技术(Java Native Interface)。

而且,此方法在执行的时候将调度run()方法完成,由系统默认调度的。

而JNT的开发技术,指的是使用Java程序调度操作系统的底层函数,但是对于这种操作一般不建议使用,也基本上不会再开发之中出现,因为Java的最大特点是可移植性,如果真的这样开发的话,那么就意味着此特性将彻底丧失。

之所以会在start0()方法上使用此关键字,主要是表示这个方法将交给JVM实现,对于线程的资源分配一定要有CPU等资源的支持,而这些资源要被操作系统所调度,所以才会在不同的操作系统上植入不同的JVM,这样的话就可以由JVM去适应各个操作系统的资源调配,因此此时的start0()实际上九表示交给JVM进行处理了。

以后一定要记住:只要是线程的启动就一定要由Thread类中的start()方法完成

但是,第一种操作中有一个最大的限制,一个类只能继承一个父类。

2.2.2 Runnable接口

在实际的开发中一个多线程的操作类很少去使用Thread类完成,而是通过Runnable接口完成。

观察Runnable接口的定义。

public interface Runnable

       public void run(){

}

所以,一个类只要实现了此接口,并覆写run()方法。

package org.runnabledemo;

public class MyThread implements Runnable {// 实现Runnable接口

    private String name; // 定义name属性

 

    public MyThread(String name) {

       this.name = name;

    }

 

    public void run() { // 覆写run()方法

       for (int i = 0; i < 10; i++) {

           System.out.println("Thread运行:" +name + ",i = " + i);

       }

    }

}

完成之后,下面继续启动多线程。

但是在现在使用Runnable定义的子类中并没有start()方法,而只有Thread类中才有。

在Thread类中存在以下的一个构造方法:

public Thread(Runnable target)

此构造方法接收Runnable的子类实例。也就是说现在可以通过Thread类来启动Runnable实现的多线程。

package org.runnabledemo;

public class RunnableDemo01 {

    public static void main(String[] args) {

       MyThread mt1 = new MyThread("线程A");

       MyThread mt2 = new MyThread("线程B");

       new Thread(mt1).start();// 调用线程体

       new Thread(mt2).start();// 调用线程体

    }

}

以上的操作代码也属于交替的运行,所以此时程序也同样实现了多线程的操作。

2.2.3 两种实现方式的区别及联系

在程序的开发中只要是多线程则肯定永远以实现Runnable接口为正统操作,因为实现Runnable接口相比继承Thread类有如下的好处:

● 避免单继承的局限,一个类可以同时实现多个接口。

● 适合于资源的共享(重要

为了确定Runable和Thread类的关系,观察Thread类的定义结构:

public class Thread extends Object implements Runnable

       可以发现Thread类本身就是Runable接口的子类,那么如果这样的话,那么上一程序的关系如下:

       这样的设计思路非常类似于“代理模式”但是又不是代理,因为如果是代理的话,则代理所调用的方法依然是run()方法,而此处却是start()方法。

范例:以卖票的程序为例:

package org.differencedemo;

public class MyThread extends Thread {// 继承Thread

    private int ticket = 5; // 一共才5张票

 

    public void run() { // 覆写run()方法

       for (int i = 0; i < 50; i++) {

           if (this.ticket > 0) {

              System.out.println("卖票:ticket = " +this.ticket--);

           }

       }

    }

}

下面建立三个线程,同时卖票。

package org.differencedemo;

public class ThreadTicket {

    public static void main(String args[]) {

       MyThread mt1 = new MyThread();// 一个线程

       MyThread mt2 = new MyThread();// 一个线程

       MyThread mt3 = new MyThread();// 一个线程

       mt1.start(); // 开始卖票

       mt2.start(); // 开始卖票

       mt3.start(); // 开始卖票

    }

}

发现现在一共卖了15张票,但是实际上只有5张票,所以证明每一个线程都卖自己的票,没有达到资源共享的目的。

那么,如果现在实现的是Runnable接口的话,则就可以实现资源的共享:

package org.differencedemo02;

public class MyThread implements Runnable {// 实现Runnable接口

    private int ticket = 5; // 一共才5张票

 

    public void run() { // 覆写run()方法

       for (int i = 0; i < 50; i++) {

           if (this.ticket > 0) {

              System.out.println("卖票:ticket = " +this.ticket--);

           }

       }

    }

}

编写多个线程进行卖票的操作

package org.differencedemo02;

 

public class ThreadTicket {

    public static void main(String args[]) {

       MyThread mt = new MyThread();// 一个线程

       new Thread(mt).start();// 开始卖票

       new Thread(mt).start();// 开始卖票

       new Thread(mt).start();// 开始卖票

    }

}

卖票:ticket = 5

卖票:ticket = 4

卖票:ticket = 2

卖票:ticket = 1

卖票:ticket = 3

虽然现在程序中有三个线程,但是三个线程一共才卖出了5张票。也就说明使用Runnable实现的多线程可以到达资源共享的目的。

可是如上程序如果直接继承了Thread类,如下所示,效果也一样,因为Thread类也是Runable接口子类。

……

class MyThread extends Thread {

……

但是这种概念只是相对的,因为即使本程序继承Thread类功能实现效果也是完全一样的,但是有一点不合适,因为一旦MyThread类继承了Thread类之后就已经具备start()方法,那么继续依靠其他类的start()方法操作救你不太合适了。

面试题:请解释多线程的两种实现方式及区别?

1)       多线程的实现可以通过继承Thread类和Runable接口来实现,不管使用何种方式都要覆写run()方法;

2)       继承Thread类与实现Runable接口相比,会有继承的局限;

3)       Thread类也是Runable接口的子类,不管通过何种方式实现的多线程,最终都要依靠Thread类的start()方法启动;

4)       使用Runable接口可以更加清楚的表示数据共享的概念。

手记:在实现多个线程进行资源共享时,不要添加Thread.sleep()方法,否则也会导致票数超过5张(多出一张,根据设置的票数不同而不同)的问题。因为如果让进程休眠,从控制台输出可知,是多个进程(由定义的进程数决定)一齐蹦出来的,所以会导致问题的产生,时而正常,时而不正常。

实际测试代码如下:

package course_2;

 

class MyThread implements Runnable {

    private int num = 5;

 

    public void run() {

       for (int i = 0; i < 50; i++) {

           try {

              Thread.sleep(500); // 每个休眠500毫秒

           } catch (InterruptedException e) {

              e.printStackTrace();

           }

if (num > 0) {

              System.out.println(Thread.currentThread().getName() +

"运行-->"+num--);

           }

       }

    }

}

 

public class Demo {

    public static void main(String args[]) {

       MyThread mt = new MyThread();

       Thread t1 = new Thread(mt,"线程A");

       Thread t2 = new Thread(mt,"线程B");

       t1.start();

       t2.start();

 

    }

}

线程A运行-->5

线程B运行-->5

线程A运行-->4

线程B运行-->3

线程B运行-->1

线程A运行-->2

手记:经过多次不断测试,即使没有Thread.sleep()也会出错。所以此时需要使用同步操作!

实际上Runnable接口和Thread类之间还是存在联系的:

public class Thread extends Object implements Runnable

发现Thread类也是Runnable接口的子类。

2.3 线程的操作方法

对于线程来讲所有的操作方法都是在Thread类中定义的,所以如果想要明确的理解操作方法,则肯定要从Thread类着手。

2.3.1 设置和取得名字

在Thread类中存在以下的几个方法可以设置和取得名字:

● 设置名字:

|-  set : public final void setName(String name) →fina表示子类不可覆写

|-  构造:

|-  public Thread(Runnable target,String name)

|-  public Thread(String name)

● 取得名字:

|-  public final String getName()

在线程的操作中因为其操作的不确定性,所以提供了一个方法,可以取得当前的操作线程:

● publicstatic Thread currentThread()

对于线程的名字一般是在启动前进行设置,最好不要设置相同的名字,最好也不要为一个线程改名字。

package org.namedemo;

public class MyThread implements Runnable {

    public void run() {

        for (int i = 0; i < 10; i++) {

            System.out.println(Thread.currentThread().getName() +"线程正在运行");

        }

    }

}

那么此时测试一下,上面取得的线程的名字。

package org.namedemo;

public class ThreadNameDemo01 {

    public static void main(String[] args) {

       MyThread mt = new MyThread();// Runnable子类实例

       Thread thread1 = new Thread(mt,"线程A");

       Thread thread2 = new Thread(mt,"线程B");

       Thread thread3 = new Thread(mt,"线程C");

       thread1.start();

       thread2.start();

       thread3.start();

    }

}

线程B线程正在运行

线程B线程正在运行

…… 

线程C线程正在运行

线程C线程正在运行

……

线程A线程正在运行

线程A线程正在运行

……

明白代码的作用之后,在来看如下代码:

package org.namedemo;

 

public class ThreadNameDemo02 {

    public static void main(String[] args) {

       MyThread mt = new MyThread();// Runnable子类实例

       new Thread(mt, "自定义线程").start();

       mt.run(); // 直接通过对象调用

    }

}

main线程正在运行

main线程正在运行

自定义线程线程正在运行

main线程正在运行

……

得出一个结论:在程序运行时主方法实际上就是一个主线程。

一直强调java是多线程的操作语言,那么它是怎么实现多线程的呢?

实际上对于java来讲,每一次执行java命令对于操作系统来讲都将启动一个JVM的进程,那么主方法实际上只是这个进程上的进一步划分。

问题:在java执行中一个java程序至少启动几个线程?

两个:main、gc(垃圾收集线程)

2.3.2 线程的休眠(重要)

让一个线程稍微休息一下,之后起来继续工作,称为休眠:

● publicstatic void sleep(long millis) throwsInterruptedException

在使用此方法时需要进行try……catch处理

package org.sleepdemo;

public class MyThread implements Runnable {

    public void run() {

        for (int i = 0; i < 10; i++) {

            try {

                Thread.sleep(500); // 每个休眠500毫秒

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            System.out.println(Thread.currentThread().getName() +"线程正在运行");

        }

    }

}

主程序:

package org.sleepdemo;

public class ThreadSleepDemo {

    public static void main(String[] args) {

       MyThread mt = new MyThread();

       new Thread(mt, "线程A").start();

       new Thread(mt, "线程B").start();

    }

}

程序的执行将变得缓慢起来。

2.3.3 线程的中断

在sleep()方法中存在InterruptedException,那么会造成此异常的方法就是中断:

●public void interrupt()

一个线程需要被另外一个线程中断。

package org.interruptdemo;

class MyThread implements Runnable {

    public void run() {

       System.out.println("1、进入run()方法体");

       try {

           System.out.println("2、线程休眠20秒钟");

           Thread.sleep(20000); // 休眠20

           System.out.println("3、线程正常休眠20秒钟");

       } catch (InterruptedException e) {

           System.out.println("4、线程休眠被中断");

           return; // 返回方法调用处

       }

       System.out.println("5、正常结束run()方法体");

    }

}

 

public class InterruptDemo {

    public static void main(String[] args) {

       MyThread mt = new MyThread();

       Thread thread = new Thread(mt,"线程A");

       thread.start(); // 启动线程

       try {

           Thread.sleep(2000); // 保证程序至少执行2

       } catch (InterruptedException e) {

           e.printStackTrace();

       }

       thread.interrupt(); // 中断

    }

}

1、进入run()方法体

2、线程休眠20秒钟

4、线程休眠被中断

2.3.4 设置线程的优先级

那么在多线程操作中,所有的代码实际上都是存在优先级的,优先级高的就有可能先执行。在线程中使用以下的方法设置:public final voidsetPriority(int newPriority)

对于优先级来说在线程中有如下三种:

●最低:public static final int MIN_PRIORITY = 1

●中等:public static final int NORM_PRIORITY = 5

●最高:public static final int MAX_PRIORITY = 10

package org.prioritydemo;

class MyThread implements Runnable {

    public void run() {

        for (int i = 0; i < 10; i++) {

            try {

                Thread.sleep(500); // 延迟操作

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            System.out.println(Thread.currentThread().getName() +"线程正在运行");

        }

    }

}

 

public class PriorityDemo01 {

    public static void main(String args[]) {

        MyThread mt = new MyThread();

        Thread t1 = new Thread(mt,"线程A");

        Thread t2 = new Thread(mt,"线程B");

        Thread t3 = new Thread(mt,"线程C");

        t1.setPriority(Thread.NORM_PRIORITY);

        t3.setPriority(Thread.MAX_PRIORITY);

        t2.setPriority(Thread.MIN_PRIORITY);

        t1.start();

        t2.start();

        t3.start();

    }

}

各个线程可以设置优先级,那么主方法的优先级是什么呢?

package org.prioritydemo;

public class PriorityDemo02 {

    public static void main(String args[]) {

        System.out.println(Thread.currentThread().getPriority());

        System.out.println("Thread.MAX_PRIORITY = " + Thread.MAX_PRIORITY);

        System.out.println("Thread.NORM_PRIORITY = " + Thread.NORM_PRIORITY);

        System.out.println("Thread.MIN_PRIORITY = " + Thread.MIN_PRIORITY);

    }

}

5

Thread.MAX_PRIORITY = 10

Thread.NORM_PRIORITY = 5

Thread.MIN_PRIORITY = 1

从输出结果可以发现,主方法的优先级是普通的优先级。

2.4 同步与死锁(理解)

2.4.1 问题的引出

现在有如下的操作代码:

package org.syndemo;

class MyTicketThread implements Runnable { // 实现Runnable接口

    private int ticket = 5; // 一共才5张票

 

    public void run() { // 覆写run()方法

       for (int i = 0; i < 50; i++) {

           if (this.ticket > 0) {

              try {

                  Thread.sleep(300); // 延迟

              } catch (InterruptedException e) {

                  e.printStackTrace();

              }

              System.out.println("卖票:ticket = " +this.ticket--);

           }

       }

    }

}

 

public class SynDemo01 {

    public static void main(String args[]) {

       MyTicketThread mt = new MyTicketThread();// 一个线程

       new Thread(mt, "票贩子-A").start(); // 开始卖票

       new Thread(mt, "票贩子-B").start(); // 开始卖票

       new Thread(mt, "票贩子-C").start(); // 开始卖票

    }

}

卖票:ticket = 5

卖票:ticket = 5

卖票:ticket = 4

卖票:ticket = 3

卖票:ticket = 2

卖票:ticket = 1

卖票:ticket = 0

卖票:ticket = -1

造成此类问题的根本原因在于,判断剩余票数和修改票数之间加入了延迟操作。

2.2.2 同步解决

在java中可以通过同步代码的方式进行代码的加锁操作,同步的实现有两种方式:

● 同步代码块

● 同步方法

同步代码块:

● 使用synchronized关键字进行同步代码块的声明,但是在使用此操作时必须明确的指出到底要锁定的是哪个对象,一般都是以当前对象为主:

synchronized(对象){  //一般都是将this进行锁定

       需要同步的代码;

使用同步代码块修改之前的程序:

package org.syndemo;

class MyTicketThread implements Runnable { // 实现Runnable接口

    private int ticket = 5; // 一共才5张票

 

    public void run() { // 覆写run()方法

       for (int i = 0; i < 50; i++) {

           synchronized (this) {// 形成同步代码块

              if (this.ticket > 0) {

                  try {

                     Thread.sleep(300); //延迟

                  } catch (InterruptedException e) {

                     e.printStackTrace();

                  }

                  System.out.println("卖票:ticket = " +this.ticket--);

              }

           }

       }

    }

}

 

public class SynDemo01 {

    public static void main(String args[]) {

       MyTicketThread mt = new MyTicketThread();// 一个线程

       new Thread(mt, "票贩子-A").start(); // 开始卖票

       new Thread(mt, "票贩子-B").start(); // 开始卖票

       new Thread(mt, "票贩子-C").start(); // 开始卖票

    }

}

卖票:ticket = 5

卖票:ticket = 4

卖票:ticket = 3

卖票:ticket = 2

卖票:ticket = 1

此时,确实解决同步的问题,但是在java中也可以通过同步方法解决这样的问题。

同步方法定义格式:

synchronized 方法返回值方法名称(参数列表){}

范例:通过同步方法实现卖票程序

package org.syndemo;

class MyTicketThread implements Runnable { // 实现Runnable接口

    private int ticket = 5; // 一共才5张票

 

    public void run() { // 覆写run()方法

       for (int i = 0; i < 50; i++) {

           this.sale(); // 调用同步方法

       }

    }

 

    public synchronized void sale() { // 增加同步方法

       if (this.ticket > 0) {

           try {

              Thread.sleep(300); // 延迟

           } catch (InterruptedException e) {

              e.printStackTrace();

           }

           System.out.println("卖票:ticket = " +this.ticket--);

       }

    }

}

 

public class SynDemo01 {

    public static void main(String args[]) {

       MyTicketThread mt = new MyTicketThread();// 一个线程

       new Thread(mt, "票贩子-A").start(); // 开始卖票

       new Thread(mt, "票贩子-B").start(); // 开始卖票

       new Thread(mt, "票贩子-C").start(); // 开始卖票

    }

}

卖票:ticket = 5

卖票:ticket = 4

卖票:ticket = 3

卖票:ticket = 2

卖票:ticket = 1

此时,实际上就可以给出java中方法定义的完整格式了:

[访问权限(public | protected | default |private)]

[static] [final] [abstract] [synchronized] [native]

返回值类型 | void | ……

方法名称(参数列表)[throws 异常1,异常2,……]{}

发现同步可以保证资源的完整性,但是过多的同步也会出现问题。

2.4.3 死锁

在程序中过多的同步会产生死锁的问题,那么死锁

package org.syndemo;

class FanBo {

    public synchronized void say(ZhangYang zy) {

       System.out.println("把钱给我,放了你弟弟。");

       zy.give();

    }

 

    public synchronized void give() {

       System.out.println("得到了钱,同时被警察抓了。");

    }

}

 

class ZhangYang {

    public synchronized void say(FanBo fb) {

       System.out.println("把弟弟放了,我给你钱。");

       fb.give();

    }

 

    public synchronized void give() {

       System.out.println("弟弟救回来,同时报案了。");

    }

}

 

public class DeadLockDemo implements Runnable {

    private FanBo fb = new FanBo();

    private ZhangYang zy = new ZhangYang();

 

    public DeadLockDemo() {

       new Thread(this).start();

       fb.say(zy);

    }

 

    public void run() {

      

       zy.say(fb);

    }

 

    public static void main(String[] args) {

       new DeadLockDemo();

    }

}

把钱给我,放了你弟弟。

把弟弟放了,我给你钱。

在本块中只需要记住一个概念即可:

面试题:多个线程操作同一资源的时候需要注意什么?会带来哪些问题?

多个线程共享同一个资源的时候需要进行同步,但是过多的同步会造成死锁。

2.5 生产者-消费者(理解)

在多线程中有一个最经典的操作案例—— 生产者和消费者,生产者不断生产内容,但是消费者不断取出内容。

2.5.1 基本实现

现在假设生产的内容都保存在Info类之中,则在生产者中要有Info类的引用,而消费者中也要存在Info类的引用。

生产者应该不断的上产信息,消费者不断的取出,所以实现多线程的操作:

1、保存信息的类——Info.java:

package org.pcdemo01;

public class Info {

    private String title = "李兴华";

    // private String title = "MLDN";

    private String content = "Java讲师";

 

    // private String content = "www.mldnjava.cn";

    public String getTitle() {

       return title;

    }

 

    public void setTitle(String title) {

       this.title = title;

    }

 

    public String getContent() {

       return content;

    }

 

    public void setContent(String content) {

       this.content = content;

    }

}

2、生产者——Producer.java

package org.pcdemo01;

public class Producer implements Runnable {

    private Info info = null;

 

    public Producer(Info info) {

       this.info = info;

    }

 

    public void run() {

       for (int x = 0; x < 100; x++) {// 不断的生产

           if (x % 2 == 1) {//是奇数

              this.info.setTitle("MLDN");

              try {

                  Thread.sleep(300); // 休眠0.3

              } catch (InterruptedException e) {

                  e.printStackTrace();

              }

              this.info.setContent("www.mldnjava.cn");

           } else {

              this.info.setTitle("李兴华");

              try {

                  Thread.sleep(300); // 休眠0.3

              } catch (InterruptedException e) {

                  e.printStackTrace();

              }

              this.info.setContent("Java讲师");

           }

       }

    }

}

3、消费者——Consumer.java

package org.pcdemo01;

public class Consumer implements Runnable {

    private Info info = null;

 

    public Consumer(Info info) {

       this.info = info;

    }

 

    public void run() {

       for (int x = 0; x < 100; x++) {

           try {

              Thread.sleep(300); // 休眠0.3

           } catch (InterruptedException e) {

              e.printStackTrace();

           }

           System.out.println(this.info.getTitle() +" -> "

                  + this.info.getContent());// 取内容

       }

    }

}

4、测试类——TestInfoDemo01.java

package org.pcdemo01;

public class TestInfoDemo01 {

    public static void main(String[] args) {

       Info info = new Info();

       Producer pro = new Producer(info);// 实例化生产者对象

       Consumer con = new Consumer(info);// 实例化消费者对象

       new Thread(pro).start();// 启动线程

       new Thread(con).start();// 启动线程

    }

}

MLDN -> Java讲师

MLDN -> Java讲师

MLDN -> Java讲师

李兴华 -> www.mldnjava.cn

MLDN -> Java讲师

李兴华 -> www.mldnjava.cn

MLDN -> Java讲师

李兴华 -> www.mldnjava.cn

           .

           .

           .

           .

从代码中可以发现以下几点问题:

● 生产的内容有可能出现不一致的情况

● 出现了重复取值和重复设置的问题

2.5.2 加入同步

为了保证程序操作数据的完整性,此时,最好加入同步操作,可以直接在Info类中定义一个同步方法。专门完成设置和取得内容。

此时可以保证数据的一致性,但是依然存在重复取和重复设置的问题,那么该如何去掉这些问题呢?

1、保存信息的类——Info.java:

package org.pcdemo02;

public class Info {

    private String title = "李兴华";

    // private String title = "MLDN";

    private String content = "Java讲师";

 

    // private String content = "www.mldnjava.cn";

    public synchronized void set(String title, String content) {

       this.setTitle(title);

       try {

           Thread.sleep(300); // 休眠0.3

       } catch (InterruptedException e) {

           e.printStackTrace();

       }

       this.setContent(content);

    }

 

    public synchronized void get() {

       System.out.println(this.title +" --> " + this.content);

    }

 

    public String getTitle() {

       return title;

    }

 

    public void setTitle(String title) {

       this.title = title;

    }

 

    public String getContent() {

       return content;

    }

 

    public void setContent(String content) {

       this.content = content;

    }

}

2、生产者——Producer.java

package org.pcdemo02;

public class Producer implements Runnable {

    private Info info = null;

 

    public Producer(Info info) {

       this.info = info;

    }

 

    public void run() {

        for (int x = 0; x < 100; x++) {// 不断的生产

           if (x % 2 == 1) {//是奇数

              this.info.set("MLDN","www.mldnjava.cn");

           } else {

              this.info.set("李兴华","Java讲师");

           }

       }

    }

}

3、消费者——Consumer.java

package org.pcdemo02;

public class Consumer implements Runnable {

    private Info info = null;

 

    public Consumer(Info info) {

       this.info = info;

    }

 

    public void run() {

       for (int x = 0; x < 100; x++) {

           this.info.get();

       }

    }

}

4、测试类——TestInfoDemo01.java

package org.pcdemo02;

public class TestInfoDemo02 {

    public static void main(String[] args) {

       Info info = new Info();

       Producer pro = new Producer(info);// 实例化生产者对象

       Consumer con = new Consumer(info);// 实例化消费者对象

       new Thread(pro).start();// 启动线程

       new Thread(con).start();// 启动线程

    }

}

2.5.3 Object对线程的支持

现在就利用Object类中的以上方法来解决线程的等待和唤醒操作。修改Info类和Producer类即可。

Info.java:

package org.pcdemo03;

public class Info {

    private String title = "李兴华";

    // private String title = "MLDN";

    private String content = "Java讲师";

    // private String content = "www.mldnjava.cn";

    private boolean flag = false;// 默认是false

 

    /*

     * 1、flag = true,表示可以生产,但是不能取走

     *

     * 2、flag = false,表示可以取走,但是不能生产

     */

    public synchronized void set(String title, String content) {

       if (flag ==false) {//已经生产过了,需要等待

           try {

              super.wait();// 等待

           } catch (InterruptedException e) {

              e.printStackTrace();

           }

       }

       this.setTitle(title);

       try {

           Thread.sleep(300); // 休眠0.3//这个休眠是必须的吗?

       } catch (InterruptedException e) {

           e.printStackTrace();

       }

       this.setContent(content);

       this.flag =false;//表示不能生产了

       super.notify(); // 唤醒其他等待的线程

    }

 

    public synchronized void get() {

       if (flag ==true) {//表示不能取

           try {

              super.wait();// 等待

           } catch (InterruptedException e) {

              e.printStackTrace();

           }

       }

       System.out.println(this.title +" --> " + this.content);

       this.flag =true;false//表示不能取走了

       super.notify(); // 唤醒

    }

 

    public String getTitle() {

       return title;

    }

 

    public void setTitle(String title) {

       this.title = title;

    }

 

    public String getContent() {

       return content;

    }

 

    public void setContent(String content) {

       this.content = content;

    }

}

Producer.java:

package org.pcdemo03;

public class Producer implements Runnable {

    private Info info = null;

 

    public Producer(Info info) {

       this.info = info;

    }

 

    public void run() {

       for (int x = 0; x < 100; x++) {// 不断的生产

           if (x % 2 == 0) {//是奇数

              this.info.set("MLDN","www.mldnjava.cn");

           } else {

              this.info.set("李兴华","Java讲师");

           }

       }

    }

}

面试题:请解释sleep()和wait()方法的区别?

Ÿ   sleep()是Thread类中定义的方法,表示让线程休眠,时间一到自动唤醒;

Ÿ   wait()是Object类中定义的方法,表示让线程等待,需要使用notify()唤醒。

2.6 线程的生命周期

范例:停止线程运行

package MutilThread;

 

class MyThread implements Runnable {

    private boolean flag = true;// 定义标志位

 

    public void run() {

        int i = 0;

        while (this.flag) {

            System.out.println(Thread.currentThread().getName() +"运行,i = "

                    + (i++));

        }

    }

 

    public void stop() {

        this.flag =false; // 修改标志位

    }

}

 

public class StopDemo {

    public static void main(String args[]) {

        MyThread my = new MyThread();

        Thread t = new Thread(my,"线程"); // 建立线程对象

        t.start(); // 启动线程

        //让主线程睡眠30毫秒,以便让线程t可以运行30毫秒后才停止

        try {

            Thread.sleep(30);

        } catch (Exception e) {

 

        }

        my.stop(); // 修改标志位,停止运行

    }

}

 

3、总结

1、线程与进程的区别

2、掌握线程的两种实现手段及区别,重点

3、可以清楚的阐述出同步及死锁的概念即可

 

0 0
原创粉丝点击