1.多线程概述
要实现多线程可以通过继承Thread和实现Runnable接口。不过这两者之间存在一些区别。其中最重要的区别就是,如果一个类继承Thread类,则不适合于多个线程共享资源,而实现了Runnable接口,就可以方便地实现资源的共享。
范例1:继承Thread类不能资源共享
- public class MyThread1 extends Thread{
- private int ticket = 5;
- @Override
- public void run() {
- // TODO Auto-generated method stub
- for (int i = 0; i < 100; i++)
- if (ticket > 0) {
- System.out.println("买票:剩余ticket=" + ticket--);
- }
- }
- }
- public class MyThreadDemo1 {
- public static void main(String args[])
- {
- MyThread1 mt1=new MyThread1();
- MyThread1 mt2=new MyThread1();
- MyThread1 mt3=new MyThread1();
- mt1.start();
- mt2.start();
- mt3.start();
- }
- }
- 卖票:剩余ticket=5
- 卖票:剩余ticket=4
- 卖票:剩余ticket=3
- 卖票:剩余ticket=2
- 卖票:剩余ticket=1
- 卖票:剩余ticket=5
- 卖票:剩余ticket=4
- 卖票:剩余ticket=3
- 卖票:剩余ticket=5
- 卖票:剩余ticket=4
- 卖票:剩余ticket=3
- 卖票:剩余ticket=2
- 卖票:剩余ticket=1
- 卖票:剩余ticket=2
- 卖票:剩余ticket=1
范例2:实现Runable接口可以资源共享
- public class MyRunableThread1 implements Runnable {
- private int ticket = 5;
- @Override
- public void run() {
- // TODO Auto-generated method stub
- for (int i = 0; i < 100; i++)
- if (ticket > 0) {
- // try {
- // Thread.sleep(300);
- // } catch (InterruptedException e) {
- // e.printStackTrace();
- // }
- System.out.println("卖票:剩余ticket=" + ticket--);
- }
- }
- }
- public class SyncDemo1 {
- public static void main(String args[])
- {
- MyRunableThread1 mrt=new MyRunableThread1();
- Thread t1=new Thread(mrt);
- Thread t2=new Thread(mrt);
- Thread t3=new Thread(mrt);
- t1.start();
- t2.start();
- t3.start();
- }
- }
- 卖票:剩余ticket=5
- 卖票:剩余ticket=4
- 卖票:剩余ticket=3
- 卖票:剩余ticket=2
- 卖票:剩余ticket=1
可见,实现Runnable接口相对于继承Thrad类来说,有如下显著优势:
- 适合多个相同程序代码的线程去处理同一资源的情况。
- 可以避免由于java单继承特性带来的局限
- 增强了程序的健壮性,代码能够被多个线程共享,代码与数据时独立的。
2.多线程的同步
多次运行范例2我们发现得到的结果可能都不相同。下面列举两个可能的输出结果
范例2可能的输出结果1
- 卖票:剩余ticket=5
- 卖票:剩余ticket=4
- 卖票:剩余ticket=2
- 卖票:剩余ticket=3
- 卖票:剩余ticket=1
- 卖票:剩余ticket=4
- 卖票:剩余ticket=2
- 卖票:剩余ticket=1
- 卖票:剩余ticket=5
- 卖票:剩余ticket=3
为了更形象地说明线程同步,我们在范例2中加入进程延时机制。
去掉范例2 MyRunableThread1类中的注释,代码如下所示:
- public class MyRunableThread1 implements Runnable {
- private int ticket = 5;
- @Override
- public void run() {
- // TODO Auto-generated method stub
- for (int i = 0; i < 100; i++)
- if (ticket > 0) {
- try {
- Thread.sleep(300);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("卖票:剩余ticket=" + ticket--);
- }
- }
- }
再次运行范例2,得到的结果如下:
- 卖票:剩余ticket=4
- 卖票:剩余ticket=5
- 卖票:剩余ticket=3
- 卖票:剩余ticket=2
- 卖票:剩余ticket=1
- 卖票:剩余ticket=0
- 卖票:剩余ticket=-1
3.两种线程同步方法
范例3:同步代码块
- public class MyRunableThread2 implements Runnable {
- private int ticket=5;
- @Override
- public void run() {
- // TODO Auto-generated method stub
- for(int i=0;i<100;i++)
- synchronized(this)
- {
- if(ticket>0)
- {
- try{
- Thread.sleep(300);
- }catch(InterruptedException e)
- {
- e.printStackTrace();
- }
- System.out.println("卖票:剩余ticket="+ ticket--);
- }
- }
- }
- }
- public class SyncDemo2 {
- public static void main(String args[])
- {
- MyRunableThread2 mrt=new MyRunableThread2();
- Thread t1=new Thread(mrt);
- Thread t2=new Thread(mrt);
- Thread t3=new Thread(mrt);
- t1.start();
- t2.start();
- t3.start();
- }
- }
范例4:同步方法
- public class MyRunableThread3 implements Runnable {
- private int ticket=5;
- @Override
- public void run() {
- // TODO Auto-generated method stub
- for(int i=0;i<100;i++)
- this.sale();
- }
- public synchronized void sale()
- {
- if(ticket>0)
- {
- try{
- Thread.sleep(300);
- }catch(InterruptedException e)
- {
- e.printStackTrace();
- }
- System.out.println("卖票:剩余ticket="+ ticket--);
- }
- }
- }
- public class SyncDemo3 {
- public static void main(String args[])
- {
- MyRunableThread3 mrt=new MyRunableThread3();
- Thread t1=new Thread(mrt);
- Thread t2=new Thread(mrt);
- Thread t3=new Thread(mrt);
- t1.start();
- t2.start();
- t3.start();
- }
- }
4生产者消费者案例
范例5:
- package edu.sjtu.erplab.thread;
- class Info{
- private String name="name";
- private String content="content";
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public String getContent() {
- return content;
- }
- public void setContent(String content) {
- this.content = content;
- }
- }
- class Producer implements Runnable{
- private Info info=null;
- public Producer(Info info)
- {
- this.info=info;
- }
- @Override
- public void run() {
- boolean flag=false;
- for(int i=0;i<10;i++)
- if(flag)
- {
- this.info.setName("name+"+i);
- try {
- Thread.sleep(90);
- } catch (InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- this.info.setContent("content+"+i);
- flag=false;
- }
- else
- {
- this.info.setName("name-"+i);
- try {
- Thread.sleep(90);
- } catch (InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- this.info.setContent("content-"+i);
- flag=true;
- }
- }
- }
- class Consumer implements Runnable{
- private Info info=null;
- public Consumer(Info info)
- {
- this.info=info;
- }
- @Override
- public void run() {
- for(int i=0;i<10;i++)
- {
- try {
- Thread.sleep(100);
- } catch (InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- System.out.println(this.info.getName()+":-->"+this.info.getContent());
- }
- }
- }
- public class ThreadDeadLock {
- 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();
- }
- }
程序输出:
- name+1:-->content-0
- name-2:-->content+1
- name+3:-->content-2
- name-4:-->content+3
- name+5:-->content-4
- name-6:-->content+5
- name+7:-->content-6
- name-8:-->content+7
- name+9:-->content+9
- name+9:-->content+9
范例5存在两个问题:
- 假设生产者线程刚向数据存储空间添加了信息的名称,还没有加入该信息的内容,程序就切换到了消费者线程,消费者线程将把信息的名称和上一个信息的内容联系到一起(比如:name+1:-->content-0)。
- 生产者放了若干次数据,消费者才开始去数据,或者是,消费者取完一个数据后,还没有等到生产者放入新的数据,又重复取出已去过的数据。
问题1 解决:加入同步
如果要为操作加入同步,可以通过定义同步方法的方式完成,即将设置名称和内容定义在一个方法里面,代码如范例6所示。
范例6
- package edu.sjtu.erplab.thread;
- class Info{
- private String name="name";
- private String content="content";
- public synchronized void set(String name,String content)
- {
- this.setName(name);
- try {
- Thread.sleep(300);
- } catch (InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- this.setContent(content);
- }
- public synchronized void get()
- {
- try {
- Thread.sleep(300);
- } catch (InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- System.out.println(this.getName()+":-->"+this.getContent());
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public String getContent() {
- return content;
- }
- public void setContent(String content) {
- this.content = content;
- }
- }
- class Producer implements Runnable{
- private Info info=null;
- public Producer(Info info)
- {
- this.info=info;
- }
- @Override
- public void run() {
- boolean flag=false;
- for(int i=0;i<10;i++)
- if(flag)
- {
- this.info.set("name+"+i, "content+"+i);
- flag=false;
- }
- else
- {
- this.info.set("name-"+i, "content-"+i);
- flag=true;
- }
- }
- }
- class Consumer implements Runnable{
- private Info info=null;
- public Consumer(Info info)
- {
- this.info=info;
- }
- @Override
- public void run() {
- for(int i=0;i<10;i++)
- {
- try {
- Thread.sleep(100);
- } catch (InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- this.info.get();
- }
- }
- }
- public class ThreadDeadLock {
- 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();
- }
- }
- name-0:-->content-0
- name+1:-->content+1
- name-2:-->content-2
- name+3:-->content+3
- name-4:-->content-4
- name-6:-->content-6
- name+7:-->content+7
- name-8:-->content-8
- name+9:-->content+9
- name+9:-->content+9
从程序的运行结果中可以发现,信息错乱的问题已经解决,但是依然存在重复读取的问题,以及漏读信息的问题。既然有重复读取,则肯定会有重复设置的问题,那么对于这样的问题,该如何解决呢?此时,就需要使用Object类。
问题解决2——加入等待与唤醒
- package edu.sjtu.erplab.thread;
- class Info{
- private String name="name";
- private String content="content";
- private boolean flag=true;
- public synchronized void set(String name,String content)
- {
- if(!flag)//标志位为false,不可以生产
- {
- try {
- super.wait();
- } catch (InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- this.setName(name);
- try {
- Thread.sleep(30);
- } catch (InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- this.setContent(content);
- flag=false;//修改标志位为false,表示生产者已经完成资源,消费者可以消费。
- super.notify();//唤醒消费者进程
- }
- public synchronized void get()
- {
- if(flag)
- {
- try {
- super.wait();
- } catch (InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- try {
- Thread.sleep(30);
- } catch (InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- System.out.println(this.getName()+":-->"+this.getContent());
- flag=true;//修改标志位为true,表示消费者拿走资源,生产者可以生产。
- super.notify();//唤醒生产者进程。
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public String getContent() {
- return content;
- }
- public void setContent(String content) {
- this.content = content;
- }
- }
- class Producer implements Runnable{
- private Info info=null;
- public Producer(Info info)
- {
- this.info=info;
- }
- @Override
- public void run() {
- boolean flag=false;
- for(int i=0;i<10;i++)
- if(flag)
- {
- this.info.set("name+"+i, "content+"+i);
- flag=false;
- }
- else
- {
- this.info.set("name-"+i, "content-"+i);
- flag=true;
- }
- }
- }
- class Consumer implements Runnable{
- private Info info=null;
- public Consumer(Info info)
- {
- this.info=info;
- }
- @Override
- public void run() {
- for(int i=0;i<10;i++)
- {
- try {
- Thread.sleep(10);
- } catch (InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- this.info.get();
- }
- }
- }
- public class ThreadDeadLock {
- 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();
- }
- }
- name-0:-->content-0
- name+1:-->content+1
- name-2:-->content-2
- name+3:-->content+3
- name-4:-->content-4
- name+5:-->content+5
- name-6:-->content-6
- name+7:-->content+7
- name-8:-->content-8
- name+9:-->content+9