多线程

进程与线程

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

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

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

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

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

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

进程: 一个完成独立运行的程序称为一个进程(有独立的内存空间)

线程:是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行,一个进程最少有一个线程(单线程程序)




线程实现的两种方式:

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

继承Thread类

Thread类是在java.lang包中定义的。所以此类可以自动导入并使用。

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

格式如下:

 class 线程类 extends Thread{ //继承Thread类

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

//此方法编写的是线程的主体

}

}


例子:

package 多线程;

public class ThreadDemo extends Thread{
	private String name;
	public ThreadDemo(String name) {
		this.name = name;
	}
	public void run() {
		for(int i=0;i<10;i++){
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(name+":i="+i);
		}
	}
}
以上的操作,已经实现了一个线程类,那么下面在主方法之中,就要求启动此线程。多个进行是同时运行的,那么多个线程也是同时运行的。那么此时就需要启动线程


package 多线程;

public class ThreadDemo extends Thread{
	private String name;
	public ThreadDemo(String name) {
		this.name = name;
	}
	public void run() {
		for(int i=0;i<10;i++){
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(name+":i="+i);
		}
	}
}
//运行结果:
//线程的主类
//线程2:i=0
//线程1:i=0
//线程1:i=1
//线程2:i=1
//线程2:i=2
//线程1:i=2
//线程1:i=3
//线程2:i=3
//线程2:i=4
//线程1:i=4
//线程1:i=5
//线程2:i=5
//线程1:i=6
//线程2:i=6
//线程1:i=7
//线程2:i=7
//线程1:i=8
//线程2:i=8
//线程1:i=9
//线程2:i=9

此时发现代码,可以完成交替的运行操作,谁抢占到了CPU资源,就运行那个线程。


实现Runnable接口

观察一下Runnable接口的定义,因为此接口也可以实现多线程的。此接口也是在java.lang包中定义的。

public interface Runnable{

public void run();

}


那么,此时子类只需要实现以上的接口就可以完成多线程的支持了。

范例:定义Runnable接口子类

package 多线程;

public class RunnableDemo implements Runnable{
	private String name;
	public RunnableDemo(String name){
		this.name = name;
	}
	public void run() {
		for(int i=0;i<5;i++){
			try {
				Thread.sleep(500);
				System.out.println("name:"+name+"  ;i="+i);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
		}
	}
}
之前继承Thread类实现多线程的时候靠的是Thread类中的Start()方法,但是现在实现的是Runnable接口,此接口中并没有Start()方法的定义,那么该如何启动呢?因为线程启动的时候必须依靠操作系统的支持,所以肯定要使用Start()方法,此时观察Thread类中的构造方法:

*接收Runnable子类实例的构造:public Thread(Runnable target)

可以通过Runnable子类的对象构建Thread类的对象,并调用Start()方法。

package 多线程;

public class RunnableDemo implements Runnable{
	private String name;
	public RunnableDemo(String name){
		this.name = name;
	}
	public void run() {
		for(int i=0;i<5;i++){
			try {
				Thread.sleep(500);
				System.out.println("name:"+name+"  ;i="+i);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
		}
	}
}
//
//结果:
//name:线程2  ;i=0
//name:线程1  ;i=0
//name:线程1  ;i=1
//name:线程2  ;i=1
//name:线程1  ;i=2
//name:线程2  ;i=2
//name:线程2  ;i=3
//name:线程1  ;i=3
//name:线程2  ;i=4
//name:线程1  ;i=4

两种实现方式的区别

既然多线程的操作有两种实现方式,那么两者的区别是什么,该使用那个呢?

*首先,从基本概念上看,使用Runnable接口更好,因为避免单继承局限

*其次,使用Runnable接口还可以方便的实现数据的共享操作。

一个卖票程序为例,观察两者的实现区别


范例:使用Thread类观察运行结果

package 多线程;

public class ThreadTicket extends Thread{
	private int ticket = 3;
	@Override
	public void run() {
		while(ticket>0){
			System.out.println("卖出票的编号是:"+(ticket--)+";剩余票的数量是:"+ticket);
		}
	}
}

package 多线程;

public class ThreadTicketMain {
	public static void main(String args[]){
		new ThreadTicket().start();
		new ThreadTicket().start();
		new ThreadTicket().start();
	}
}

//结果:
//卖出票的编号是:3;剩余票的数量是:2
//卖出票的编号是:3;剩余票的数量是:2
//卖出票的编号是:3;剩余票的数量是:2
//卖出票的编号是:2;剩余票的数量是:1
//卖出票的编号是:2;剩余票的数量是:1
//卖出票的编号是:1;剩余票的数量是:0
//卖出票的编号是:1;剩余票的数量是:0
//卖出票的编号是:2;剩余票的数量是:1
//卖出票的编号是:1;剩余票的数量是:0
以上的程序一共启动了三个线程,结果卖出了9张票,个人卖个人的票。现在并没有实现资源的共享。

范例:现在使用Runnable来实现

package 多线程;

public class RunnableTicket implements Runnable{
	private int ticket = 3;
	public void run() {
		while(ticket>0){
			System.out.println("卖出票的编号是:"+(ticket--)+";剩余票的数量是:"+ticket);
		}
	}

}

package 多线程;

public class RunnableTicketMain {
	public static void main(String args[]){
		RunnableTicket runnableTicket = new RunnableTicket();
		new Thread(runnableTicket).start();
		new Thread(runnableTicket).start();
		new Thread(runnableTicket).start();
	}
}

//结果:
//卖出票的编号是:3;剩余票的数量是:2
//卖出票的编号是:2;剩余票的数量是:1
//卖出票的编号是:1;剩余票的数量是:0

通过以上的操作代码,可以发现使用Runnable接口实际上可以完成资源共享的操作。

除了以上明显标志之外,对于两种实现本身还有联系的,观察Thread类定义格式:

public class Thread extends Object implements Runnable

发现,实际上Thread类本身也属于Runnable接口的子类。

那么,之前使用Runnable接口实现多线程操作的时候实际上操作的效果就如下图所示


所以,从以上 的图形中可以发现,Thread类实际上完成的只是一个代理的操作功能,而具体的功能有MyThread(用户自定义的类)来完成,一个典型的代理设计模式应用。



线程的操作状态

之前的代码中可以发现线程有如下的操作状态: 创建——>启动——>运行。


第一步:创建一个线程的实例化对象。

第二步:启动多线程,调用Start()方法,但是调用Start()方法之后并不是立刻执行多线程操作。而是跑到就绪状态。

第三步:等待CPU进行调度,调度之后进入运行状态。

第四部:如果现在假设运行一段时间之后,被其他线程抢先了,出现了阻塞状态

第五步:从阻塞状态要重新回到就绪状态,等待CPU下一次调度

第六步:当全部的操作执行完成之后,线程将终止执行

虽然在代码上操作是有先有后,但是所有的线程肯定都是同时启动的,所以在线程的开发中没有顺序而言。

哪个线程抢占到了CPU资源,哪个线程就先执行。


线程的同步与死锁(理解)

线程同步的引出

在多线程的操作中,多个线程有可能同时处理同一个资源,例如:实现了Runnable接口之后就属于资源共享的操作。

范例:观察一下同步的问题:

package 多线程;

public class RunnableTicket implements Runnable{
	private int ticket = 3;
	public void run() {
		while(ticket>0){
			try {
				//加入延迟
				Thread.sleep(300);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println("卖出票的编号是:"+(ticket--)+";剩余票的数量是:"+ticket);
		}
	}

}

package 多线程;

public class RunnableTicketMain {
	public static void main(String args[]){
		RunnableTicket runnableTicket = new RunnableTicket();
		new Thread(runnableTicket).start();
		new Thread(runnableTicket).start();
		new Thread(runnableTicket).start();
	}
}

//结果:
//卖出票的编号是:3;剩余票的数量是:2
//卖出票的编号是:2;剩余票的数量是:1
//卖出票的编号是:1;剩余票的数量是:0
//卖出票的编号是:0;剩余票的数量是:-1
//卖出票的编号是:-1;剩余票的数量是:-2

此时,发现程序运行的时候出现了负数,为什么会出现?

现在来分析一下程序的执行步骤:

1、判断是否还有票

2、修改票数

此时,所有的线程会同时进入到操作的方法之中。



应该完成一个加锁的操作。那么这样的操作在程序中成为同步操作,使用synchronized(同步)关键字完成功能。


线程同步

在java中想要对线程进行同步,有以下两种方法:

一、使用同步代码块

二、使用同步方法

但是如果要想使用同步代码块的话,则必须指定要对那个对象进行同步,所以同步代码块的执行格式如下:

synchronized(要同步的对象){

要同步的操作;

}

package 多线程;

public class RunnableTicket implements Runnable {
	private int ticket = 3;

	public void run() {
		synchronized (this) {//同步代码块
			while (ticket > 0) {
				try {
					// 加入延迟
					Thread.sleep(300);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println("卖出票的编号是:" + (ticket--) + ";剩余票的数量是:"
						+ ticket);
			}
		}
	}

}

package 多线程;

public class RunnableTicketMain {
	public static void main(String args[]){
		RunnableTicket runnableTicket = new RunnableTicket();
		new Thread(runnableTicket).start();
		new Thread(runnableTicket).start();
		new Thread(runnableTicket).start();
	}
}

//结果:
//卖出票的编号是:3;剩余票的数量是:2
//卖出票的编号是:2;剩余票的数量是:1
//卖出票的编号是:1;剩余票的数量是:0

加入同步代码块之后,可以发现程序的执行速度有所减缓,但是最终的结果很正确的。

当然,也可以使用同步方法完成操作。

package 多线程;

public class RunnableTicket implements Runnable {
	private int ticket = 3;

	public void run() {
		sale();
	}
	
	public synchronized void sale(){//售票是同步方法
		while (ticket > 0) {
			try {
				// 加入延迟
				Thread.sleep(300);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println("卖出票的编号是:" + (ticket--) + ";剩余票的数量是:"
					+ ticket);
	}
	}

}





  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值