多线程

来源:互联网 发布:软件系统开发合同 编辑:程序博客网 时间:2024/06/02 06:23

进程与线程

*单进程处理:最传统的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 blocke.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 blocke.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 blocke.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 blocke.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;@Overridepublic 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 blocke.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 blocke.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 blocke.printStackTrace();}System.out.println("卖出票的编号是:" + (ticket--) + ";剩余票的数量是:"+ ticket);}}}





原创粉丝点击