java中用Runnable和Thread实现多线程,多线程间的同步和死锁。

来源:互联网 发布:知乎海贼王路飞的回答 编辑:程序博客网 时间:2024/06/09 16:39

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

通过观察Thread类的源码就会发现,Thread类其实也继承了Runnable接口。

继承Thread实现多线程:

//MyThread类class MyThread extends Thread{  private String name;  public MyThread(String name) {  super();  this.name = name;  }  public void run(){  for(int i=0;i<10;i++){  System.out.println("线程开始:"+this.name+",i="+i);  try {Thread.sleep(1000);} catch (InterruptedException e) {System.out.println("线程出错"+this.name);}}  }  } 
//ThreadDemo类public class ThreadDemo {public static void main(String[] args) {  MyThread mt1=new MyThread("线程a");  MyThread mt2=new MyThread("线程b");  mt1.run();  mt2.run();  }  }
此时实例化MyThread类,调用的是run()方法,此时执行的结果如下图:

此时结果很有规律,先第一个对象执行,然后第二个对象执行,并没有相互运行。在JDK的文档中可以发现,一旦调用start()方法,则会通过JVM找到run()方法。下面启动start()方法启动线程:

//ThreadDemo类public class ThreadDemo {public static void main(String[] args) {  MyThread mt1=new MyThread("线程a");  MyThread mt2=new MyThread("线程b");  mt1.start();  mt2.start();  }  }

这样程序可以正常完成交互式运行。那么为啥非要使用start();方法启动多线程呢?

在JDK的安装路径下,src.zip是全部的java源程序,通过此代码找到Thread中的start()方法的定义,可以发现此方法中使用了private native void start0();其中native关键字表示可以调用操作系统的底层函数,那么这样的技术成为JNI技术(java Native Interface)。


Runnable接口:

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

//MyThread类class MyThread implements Runnable{  private String name;  public MyThread(String name) {  super();  this.name = name;  }  public void run(){  for(int i=0;i<10;i++){  System.out.println("线程开始:"+this.name+",i="+i);  try {Thread.sleep(1000);} catch (InterruptedException e) {System.out.println("线程出错"+this.name);}}  } 
但是在使用Runnable定义的子类中没有start()方法,只有Thread类中才有。此时观察Thread类,有一个构造方法:public Thread(Runnable targer)此构造方法接受Runnable的子类实例,也就是说可以通过Thread类来启动Runnable实现的多线程。(start()可以协调系统的资源):

//Demo类public class ThreadDemo {  public static void main(String[] args) {     MyThread mt1=new MyThread("线程a");     MyThread mt2=new MyThread("线程b");     new Thread(mt1).start();     new Thread(mt2).start();   }  } 
两种实现方式的区别和联系:

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

1):适合多个相同的程序代码的线程去处理同一个资源

2):可以避免java中的单继承的限制

3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立。


卖票程序:

以卖票程序为例,通过Thread类完成:

class MyThread extends Thread{    private int ticket=10;    public void run(){       for(int i=0;i<20;i++){          if(this.ticket>0){              System.out.println("卖票:ticket"+this.ticket--);          }       }    }  }; 
下面通过三个线程对象,同时卖票:

public class ThreadTicket {  public static void main(String[] args) {     MyThread mt1=new MyThread();     MyThread mt2=new MyThread();     MyThread mt3=new MyThread();     mt1.start();//每个线程都各卖了10张,共卖了30张票     mt2.start();//但实际只有10张票,每个线程都卖自己的票     mt3.start();//没有达到资源共享  }  } 
如果用Runnable就可以实现资源共享,下面看例子:

class MyThread implements Runnable{  private int ticket=10;    public void run(){      for(int i=0;i<20;i++){        if(this.ticket>0){            System.out.println("卖票:ticket"+this.ticket--);           }        }     }  }  public class RunnableTicket {      public static void main(String[] args) {      MyThread mt=new MyThread();      new Thread(mt).start();//同一个mt,但是在Thread中就不可以,如果用同一      new Thread(mt).start();//个实例化对象mt,就会出现异常      new Thread(mt).start();    }  }; 

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

Runnable接口和Thread之间的联系:

public class Thread extends Object implements Runnable

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


class hello implements Runnable {    public void run() {        for (int i = 0; i < 3; i++) {            System.out.println(Thread.currentThread().getName());//Thread.currentTread().getName()获得当前线程的名字。        }    }     public static void main(String[] args) {        hello he = new hello();        new Thread(he,"A").start();        new Thread(he,"B").start();        new Thread(he).start();    }}

【运行结果】:

A

A

A

B

B

B

Thread-0

Thread-0

Thread-0

说明如果我们没有指定名字的话,系统自动提供名字。

判断线程是否启动

class hello implements Runnable {    public void run() {        for (int i = 0; i < 3; i++) {            System.out.println(Thread.currentThread().getName());        }    }     public static void main(String[] args) {        hello he = new hello();        Thread demo = new Thread(he);        System.out.println("线程启动之前---》" + demo.isAlive());        demo.start();        System.out.println("线程启动之后---》" + demo.isAlive());    }}

【运行结果】

线程启动之前---》false

线程启动之后---》true

Thread-0

Thread-0

Thread-0

主线程也有可能在子线程结束之前结束。并且子线程不受影响,不会因为主线程的结束而结束。

强制执行线程:

    /**     * @author Rollen-Holt 线程的强制执行     * */    class hello implements Runnable {        public void run() {            for (int i = 0; i < 3; i++) {                System.out.println(Thread.currentThread().getName());            }        }             public static void main(String[] args) {            hello he = new hello();            Thread demo = new Thread(he,"线程");            demo.start();            for(int i=0;i<50;++i){                if(i>10){                    try{                        demo.join();  //强制执行demo                    }catch (Exception e) {                        e.printStackTrace();                    }                }                System.out.println("main 线程执行-->"+i);            }        }    }

【运行的结果】:

main 线程执行-->0

main 线程执行-->1

main 线程执行-->2

main 线程执行-->3

main 线程执行-->4

main 线程执行-->5

main 线程执行-->6

main 线程执行-->7

main 线程执行-->8

main 线程执行-->9

main 线程执行-->10

线程

线程

线程

main 线程执行-->11

main 线程执行-->12

main 线程执行-->13

...

线程休眠

/** * @author Rollen-Holt 线程的休眠 * */class hello implements Runnable {    public void run() {        for (int i = 0; i < 3; i++) {            try {                Thread.sleep(2000);            } catch (Exception e) {                e.printStackTrace();            }            System.out.println(Thread.currentThread().getName() + i);        }    }     public static void main(String[] args) {        hello he = new hello();        Thread demo = new Thread(he, "线程");        demo.start();    }}

【运行结果】:(结果每隔2s输出一个)

线程0

线程1

线程2

线程中断

/** * @author Rollen-Holt 线程的中断 * */class hello implements Runnable {    public void run() {        System.out.println("执行run方法");        try {            Thread.sleep(10000);            System.out.println("线程完成休眠");        } catch (Exception e) {            System.out.println("休眠被打断");            return;  //返回到程序的调用处        }        System.out.println("线程正常终止");    }     public static void main(String[] args) {        hello he = new hello();        Thread demo = new Thread(he, "线程");        demo.start();        try{            Thread.sleep(2000);        }catch (Exception e) {            e.printStackTrace();        }        demo.interrupt(); //2s后中断线程    }}

【运行结果】:

执行run方法

休眠被打断

后台线程:

在java程序中,只要前台有一个线程在运行,整个java程序进程不会消失,所以此时可以设置一个后台线程,这样即使java进程消失了,此后台线程依然能够继续运行。


/** * @author Rollen-Holt 后台线程 * */class hello implements Runnable {    public void run() {        while (true) {            System.out.println(Thread.currentThread().getName() + "在运行");        }    }     public static void main(String[] args) {        hello he = new hello();        Thread demo = new Thread(he, "线程");        demo.setDaemon(true);        demo.start();    }}

虽然有一个死循环,但是程序还是可以执行完的。因为在死循环中的线程操作已经设置为后台运行了。

线程优先级:

/** * @author Rollen-Holt 线程的优先级 * */class hello implements Runnable {    public void run() {        for(int i=0;i<5;++i){            System.out.println(Thread.currentThread().getName()+"运行"+i);        }    }     public static void main(String[] args) {        Thread h1=new Thread(new hello(),"A");        Thread h2=new Thread(new hello(),"B");        Thread h3=new Thread(new hello(),"C");        h1.setPriority(8);        h2.setPriority(2);        h3.setPriority(6);        h1.start();        h2.start();        h3.start();             }}

【运行结果】:

A运行0

A运行1

A运行2

A运行3

A运行4

B运行0

C运行0

C运行1

C运行2

C运行3

C运行4

B运行1

B运行2

B运行3

B运行4

但是不要误以为优先级越高就先执行。谁先执行还是取决于谁先去的CPU的资源。所以B运行了1次,C开始运行。运行完轮到B,另外,主线程的优先级是5

线程的礼让:

在线程操作中,也可以使用yield()方法,将一个线程的操作暂时交给其他线程执行。

/** * @author Rollen-Holt 线程的优先级 * */class hello implements Runnable {    public void run() {        for(int i=0;i<5;++i){            System.out.println(Thread.currentThread().getName()+"运行"+i);            if(i==3){                System.out.println("线程的礼让");                Thread.currentThread().yield();            }        }    }     public static void main(String[] args) {        Thread h1=new Thread(new hello(),"A");        Thread h2=new Thread(new hello(),"B");        h1.start();        h2.start();             }}

结果:

A运行0

A运行1

A运行2

A运行3

线程的礼让

A运行4

B运行0

B运行1

B运行2

B运行3

线程的礼让

B运行4

同步和死锁:

【问题引出】:比如说对于买票系统,有下面的代码:

class hello implements Runnable {    public void run() {        for(int i=0;i<10;++i){            if(count>0){                try{                    Thread.sleep(1000);                }catch(InterruptedException e){                    e.printStackTrace();                }                System.out.println(count--);            }        }    }     public static void main(String[] args) {        hello he=new hello();        Thread h1=new Thread(he);        Thread h2=new Thread(he);        Thread h3=new Thread(he);        h1.start();        h2.start();        h3.start();    }    private int count=5;}

【运行结果】:

5

4

3

2

1

0

-1

这里出现了-1,显然这个是错的。,应该票数不能为负值。

如果想解决这种问题,就需要使用同步。所谓同步就是在统一时间段中只有有一个线程运行,

其他的线程必须等到这个线程结束之后才能继续执行。

【使用线程同步解决问题】

采用同步的话,可以使用同步代码块和同步方法两种来完成。

【同步代码块】:

语法格式:

synchronized(同步对象){

 //需要同步的代码

}

但是一般都把当前对象this作为同步对象。

比如对于上面的买票的问题,如下:

class hello implements Runnable {    public void run() {        for(int i=0;i<10;++i){            synchronized (this) {                if(count>0){                    try{                        Thread.sleep(1000);                    }catch(InterruptedException e){                        e.printStackTrace();                    }                    System.out.println(count--);                }            }        }    }     public static void main(String[] args) {        hello he=new hello();        Thread h1=new Thread(he);        Thread h2=new Thread(he);        Thread h3=new Thread(he);        h1.start();        h2.start();        h3.start();    }    private int count=5;}

【运行结果】:(每一秒输出一个结果)

5

4

3

2

1

【同步方法】

也可以采用同步方法。

语法格式为synchronized 方法返回类型方法名(参数列表){

    // 其他代码

}

现在,我们采用同步方法解决上面的问题。

class hello implements Runnable {    public void run() {        for (int i = 0; i < 10; ++i) {            sale();        }    }     public synchronized void sale() {        if (count > 0) {            try {                Thread.sleep(1000);            } catch (InterruptedException e) {                e.printStackTrace();            }            System.out.println(count--);        }    }     public static void main(String[] args) {        hello he = new hello();        Thread h1 = new Thread(he);        Thread h2 = new Thread(he);        Thread h3 = new Thread(he);        h1.start();        h2.start();        h3.start();    }     private int count = 5;}

【运行结果】(每秒输出一个)

5

4

3

2

1

提醒一下,当多个线程共享一个资源的时候需要进行同步,但是过多的同步可能导致死锁。

此处列举经典的生产者和消费者问题。

【生产者和消费者问题】

先看一段有问题的代码。

class Info {     public String getName() {        return name;    }     public void setName(String name) {        this.name = name;    }     public int getAge() {        return age;    }     public void setAge(int age) {        this.age = age;    }     private String name = "Rollen";    private int age = 20;} /** * 生产者 * */class Producer implements Runnable{    private Info info=null;    Producer(Info info){        this.info=info;    }         public void run(){        boolean flag=false;        for(int i=0;i<25;++i){            if(flag){                this.info.setName("Rollen");                try{                    Thread.sleep(100);                }catch (Exception e) {                    e.printStackTrace();                }                this.info.setAge(20);                flag=false;            }else{                this.info.setName("chunGe");                try{                    Thread.sleep(100);                }catch (Exception e) {                    e.printStackTrace();                }                this.info.setAge(100);                flag=true;            }        }    }}/** * 消费者类 * */class Consumer implements Runnable{    private Info info=null;    public Consumer(Info info){        this.info=info;    }         public void run(){        for(int i=0;i<25;++i){            try{                Thread.sleep(100);            }catch (Exception e) {                e.printStackTrace();            }            System.out.println(this.info.getName()+"<---->"+this.info.getAge());        }    }} /** * 测试类 * */class hello{    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();    }}
【运行结果】:

Rollen<---->100

chunGe<---->20

chunGe<---->100

Rollen<---->100

chunGe<---->20

Rollen<---->100

Rollen<---->100

Rollen<---->100

chunGe<---->20

chunGe<---->20

chunGe<---->20

Rollen<---->100

chunGe<---->20

Rollen<---->100

chunGe<---->20

Rollen<---->100

chunGe<---->20

Rollen<---->100

chunGe<---->20

Rollen<---->100

chunGe<---->20

Rollen<---->100

chunGe<---->20

Rollen<---->100

chunGe<---->20

大家可以从结果中看到,名字和年龄并没有对于。

 

那么如何解决呢?

1)加入同步

2)加入等待和唤醒

先来看看加入同步会是如何。

class Info {         public String getName() {        return name;    }     public void setName(String name) {        this.name = name;    }     public int getAge() {        return age;    }     public void setAge(int age) {        this.age = age;    }     public synchronized void set(String name, int age){        this.name=name;        try{            Thread.sleep(100);        }catch (Exception e) {            e.printStackTrace();        }        this.age=age;    }         public synchronized void get(){        try{            Thread.sleep(100);        }catch (Exception e) {            e.printStackTrace();        }        System.out.println(this.getName()+"<===>"+this.getAge());    }    private String name = "Rollen";    private int age = 20;} /** * 生产者 * */class Producer implements Runnable {    private Info info = null;     Producer(Info info) {        this.info = info;    }     public void run() {        boolean flag = false;        for (int i = 0; i < 25; ++i) {            if (flag) {                                 this.info.set("Rollen", 20);                flag = false;            } else {                this.info.set("ChunGe", 100);                flag = true;            }        }    }} /** * 消费者类 * */class Consumer implements Runnable {    private Info info = null;     public Consumer(Info info) {        this.info = info;    }     public void run() {        for (int i = 0; i < 25; ++i) {            try {                Thread.sleep(100);            } catch (Exception e) {                e.printStackTrace();            }            this.info.get();        }    }} /** * 测试类 * */class hello {    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();    }}

【运行结果】:

Rollen<===>20

ChunGe<===>100

ChunGe<===>100

ChunGe<===>100

ChunGe<===>100

ChunGe<===>100

Rollen<===>20

ChunGe<===>100

ChunGe<===>100

ChunGe<===>100

ChunGe<===>100

ChunGe<===>100

ChunGe<===>100

ChunGe<===>100

ChunGe<===>100

ChunGe<===>100

ChunGe<===>100

ChunGe<===>100

ChunGe<===>100

ChunGe<===>100

ChunGe<===>100

ChunGe<===>100

ChunGe<===>100

ChunGe<===>100

ChunGe<===>100

从运行结果来看,错乱的问题解决了,现在是Rollen 对应20,ChunGe对于100

,但是还是出现了重复读取的问题,也肯定有重复覆盖的问题。如果想解决这个问题,就需要使用Object类帮忙了、

,我们可以使用其中的等待和唤醒操作。

要完成上面的功能,我们只需要修改Info类饥渴,在其中加上标志位,并且通过判断标志位完成等待和唤醒的操作,代码如下:

class Info {         public String getName() {        return name;    }     public void setName(String name) {        this.name = name;    }     public int getAge() {        return age;    }     public void setAge(int age) {        this.age = age;    }     public synchronized void set(String name, int age){        if(!flag){            try{                super.wait();            }catch (Exception e) {                e.printStackTrace();            }        }        this.name=name;        try{            Thread.sleep(100);        }catch (Exception e) {            e.printStackTrace();        }        this.age=age;        flag=false;        super.notify();    }         public synchronized void get(){        if(flag){            try{                super.wait();            }catch (Exception e) {                e.printStackTrace();            }        }                 try{            Thread.sleep(100);        }catch (Exception e) {            e.printStackTrace();        }        System.out.println(this.getName()+"<===>"+this.getAge());        flag=true;        super.notify();    }    private String name = "Rollen";    private int age = 20;    private boolean flag=false;} /** * 生产者 * */class Producer implements Runnable {    private Info info = null;     Producer(Info info) {        this.info = info;    }     public void run() {        boolean flag = false;        for (int i = 0; i < 25; ++i) {            if (flag) {                                 this.info.set("Rollen", 20);                flag = false;            } else {                this.info.set("ChunGe", 100);                flag = true;            }        }    }} /** * 消费者类 * */class Consumer implements Runnable {    private Info info = null;     public Consumer(Info info) {        this.info = info;    }     public void run() {        for (int i = 0; i < 25; ++i) {            try {                Thread.sleep(100);            } catch (Exception e) {                e.printStackTrace();            }            this.info.get();        }    }} /** * 测试类 * */class hello {    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();    }}
【程序运行结果】:
Rollen<===>20
ChunGe<===>100
Rollen<===>20
ChunGe<===>100
Rollen<===>20
ChunGe<===>100
Rollen<===>20
ChunGe<===>100
Rollen<===>20
ChunGe<===>100
Rollen<===>20
ChunGe<===>100
Rollen<===>20
ChunGe<===>100
Rollen<===>20
ChunGe<===>100
Rollen<===>20
ChunGe<===>100
Rollen<===>20
ChunGe<===>100
Rollen<===>20
ChunGe<===>100
Rollen<===>20
ChunGe<===>100
Rollen<===>20
现在看结果就可以知道,之前的问题完全解决。