多线程开发

参考资料:

参考1:java开发实战经典

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
以上程序通过继承Thread类实现多线程,程序中启动了了三个线程,但是三个线程分别买了各自的5张票,并没有达到资源共享的目的。

范例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
从程序的运行结果中可以清楚地发现,虽然启动了3个线程, 但是三个线程一共才卖出去5张票,即ticket属性是被所有线程所共享的。

可见,实现Runnable接口相对于继承Thrad类来说,有如下显著优势:

  1. 适合多个相同程序代码的线程去处理同一资源的情况。
  2. 可以避免由于java单继承特性带来的局限
  3. 增强了程序的健壮性,代码能够被多个线程共享,代码与数据时独立的。

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;

	@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
出现票数为负的情况是:线程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;
	
	@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存在两个问题:

  1. 假设生产者线程刚向数据存储空间添加了信息的名称,还没有加入该信息的内容,程序就切换到了消费者线程,消费者线程将把信息的名称和上一个信息的内容联系到一起(比如:name+1:-->content-0)
  2. 生产者放了若干次数据,消费者才开始去数据,或者是,消费者取完一个数据后,还没有等到生产者放入新的数据,又重复取出已去过的数据。

问题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



  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值