多线程开发
来源:互联网 发布:明事理 知廉耻 编辑:程序博客网 时间:2024/05/23 19:19
参考资料:
参考1:java开发实战经典
参考2:Java 线程池学习
1.多线程概述
要实现多线程可以通过继承Thread和实现Runnable接口。不过这两者之间存在一些区别。其中最重要的区别就是,如果一个类继承Thread类,则不适合于多个线程共享资源,而实现了Runnable接口,就可以方便地实现资源的共享。
范例1:继承Thread类不能资源共享
public class MyThread1 extends Thread{private int ticket = 5;@Overridepublic void run() {// TODO Auto-generated method stubfor (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以上程序通过继承Thread类实现多线程,程序中启动了了三个线程,但是三个线程分别买了各自的5张票,并没有达到资源共享的目的。
范例2:实现Runable接口可以资源共享
public class MyRunableThread1 implements Runnable {private int ticket = 5;@Overridepublic void run() {// TODO Auto-generated method stubfor (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从程序的运行结果中可以清楚地发现,虽然启动了3个线程, 但是三个线程一共才卖出去5张票,即ticket属性是被所有线程所共享的。
可见,实现Runnable接口相对于继承Thrad类来说,有如下显著优势:
- 适合多个相同程序代码的线程去处理同一资源的情况。
- 可以避免由于java单继承特性带来的局限
- 增强了程序的健壮性,代码能够被多个线程共享,代码与数据时独立的。
2.多线程的同步
多次运行范例2我们发现得到的结果可能都不相同。下面列举两个可能的输出结果
范例2可能的输出结果1
卖票:剩余ticket=5卖票:剩余ticket=4卖票:剩余ticket=2卖票:剩余ticket=3卖票:剩余ticket=1范例2可能的输出结果2
卖票:剩余ticket=4卖票:剩余ticket=2卖票:剩余ticket=1卖票:剩余ticket=5卖票:剩余ticket=3
为了更形象地说明线程同步,我们在范例2中加入进程延时机制。
去掉范例2 MyRunableThread1类中的注释,代码如下所示:
public class MyRunableThread1 implements Runnable {private int ticket = 5;@Overridepublic void run() {// TODO Auto-generated method stubfor (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出现票数为负的情况是:线程1在执行 ticket--之前,线程2 进入了 if (ticket > 0) 这个判断,这样当线程1 ticket--之后ticket==0了,线程2再次执行ticket--那么ticket==-1。
3.两种线程同步方法
范例3:同步代码块
public class MyRunableThread2 implements Runnable {private int ticket=5;@Overridepublic void run() {// TODO Auto-generated method stubfor(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;@Overridepublic void run() {// TODO Auto-generated method stubfor(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;}@Overridepublic 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 blocke.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 blocke.printStackTrace();}this.info.setContent("content-"+i);flag=true;}}}class Consumer implements Runnable{private Info info=null;public Consumer(Info info){this.info=info;}@Overridepublic void run() {for(int i=0;i<10;i++){try {Thread.sleep(100);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.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-0name-2:-->content+1name+3:-->content-2name-4:-->content+3name+5:-->content-4name-6:-->content+5name+7:-->content-6name-8:-->content+7name+9:-->content+9name+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 blocke.printStackTrace();}this.setContent(content);}public synchronized void get(){try {Thread.sleep(300);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.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;}@Overridepublic 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;}@Overridepublic void run() {for(int i=0;i<10;i++){try {Thread.sleep(100);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.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-0name+1:-->content+1name-2:-->content-2name+3:-->content+3name-4:-->content-4name-6:-->content-6name+7:-->content+7name-8:-->content-8name+9:-->content+9name+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 blocke.printStackTrace();}}this.setName(name);try {Thread.sleep(30);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.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 blocke.printStackTrace();}}try {Thread.sleep(30);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.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;}@Overridepublic 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;}@Overridepublic void run() {for(int i=0;i<10;i++){try {Thread.sleep(10);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.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-0name+1:-->content+1name-2:-->content-2name+3:-->content+3name-4:-->content-4name+5:-->content+5name-6:-->content-6name+7:-->content+7name-8:-->content-8name+9:-->content+9
- 多线程开发
- 多线程开发
- 多线程开发
- 多线程开发
- 多线程开发
- 多线程开发
- 多线程开发
- 多线程开发
- 多线程开发
- ios 开发系列--多线程开发
- 【iOS开发系列】多线程开发
- 什么是多线程开发?
- vc多线程开发(1)
- 8.2 多线程开发
- 8.2 多线程开发
- Symbian多线程开发问题
- java多线程开发基础
- Windows Mobile 多线程开发
- 顶点和片段着色器
- 线程的实用功能
- 计算年龄
- 创建tabhost的两种方式
- 关于HTTP提交方式之PUT
- 多线程开发
- 经济学十大原理
- 活动选择(贪心算法) p226
- 单机搭建WinCE开发环境(二)
- using 三种用法
- NOIP 2011 Day1 Problem1
- windows下Virtualbox虚拟Ubuntu共享文件夹设置
- 黑马程序员—学习笔记之ADO.Net
- 单机搭建WinCE开发环境(三)