Java学习10——书本自学

第11章     多线程

个人计算机上的操作系统纷纷改用多任务Multtasking和分时Timesharing设计,将早期只有大型计算机才具有的系统特性,带给了个人计算机。

11.1 线程的概念

以往开发的程序大都是单线程的。即一个程序只有从头到尾的一条执行路径。然而现实生活中的很多过程都具有多条途径同时运作的特征。多线程Multthread是指在同一个进程中同时存在几个执行体,按照几条不同的执行路径同时工作的情况。

 多线程的含义就是将一个程序任务分成几个可以同时并发执行的子任务。特别是在网络编程中,会发现许多功能是可以并发执行的。

11.1.1  程序、进程、多任务与线程

1.程序

程序是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码。

2.进程

进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个进程即是一个进程从创建、运行到消亡的过程。每个进程之间是独立的,除非利用其他通讯管道来通信,或是通过操作系统产生交互作用,否则基本上各个进程不知道彼此的存在。

3.多任务

多任务是指在一个系统中可以同时运行多个进程,即有多个独立运行的任务,每一个任务对应一个进程。每个进程都有一个专属的内存区域,即使是多次启动同一段程序产生不同的进程也是如此。所谓同时运行的进程,其实是指由操作系统将系统资源分配给各个进程,每个进程在CPU上交替运行。

4.线程

对于完全不相关的程序而言,在同时执行时,彼此的进程也不会做数据交换的工作,而可以完全独立运行。但是对于同一程序所产生的多个进程,通常是因为程序设计者希望能加快整体工作的运行效率,运行多个进程协同工作。但在进程这个概念中,每一个进程的内部数据和状态是完全独立的,所以即使他们是同一个程序产生,也必须重复许多的数据复制工作,而且在交换彼此数据的时候,也要使用一些进程间通信的机制。为了减少不必要的负担,线程的概念就应用而生。

运行一个进程时,程序内部代码都是按照顺序执行的。如果能够将一个进程划分成更小的运行单位,则程序中一些相对独立的代码块就可以同时运行,从而获得更高的执行效率。线程就提供了这种同时执行的办法。

所谓线程,其实与进程相似,也是一个执行中的程序,但线程是一个比进程更小的执行单位。一个进程在其执行过程中可以产生过多个线程,形成多条执行路径。但是与进程不同的是,同类的多个线程共享一块内存空间和一组系统资源,所以系统产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小很多,也正因为如此,线程被称为轻量型进程。

要去完成一项工作,在一般系统程序设计中主要是使用单一进程来完成一项工作,也就是单一线程。然而若利用时间分享的概念来完成工作使其更有效率,也就是说让CPU在同一时间内执行一个程序的好几个程序段来完成工作,这就是多线程的概念。利用时间分享的概念来工作。

综上可知,所谓多线程就是同时执行一个以上的线程,一个线程的执行不必等待另一个线程执行完成后才执行,所有线程都可以发生在同一时刻。但操作系统并没有将多个线程看作多个独立的应用去实现线程的调度和管理以及资源分配。

11.1.2  线程的状态和生命周期

每个Java程序都有一个默认的主线程,对于应用程序来说其主线程是main()方法执行的线程;对小程序来说,其主线程坠毁u浏览器加载并执行Java小程序。要想实现多线程,必须在主线程中创建新的线程对象。Java语言利用Thread类及其子类的对象来表示线程,新建线程在它的一个完整生命周期内通常要经五种状态。通过线程的控制和调度可使线程在几种状态之间转化。

1.新建状态(newborn)

当一个Thread类或其子类的对象被声明并创建,但还未被执行的这段时间内,处于一种特殊的新建状态中。此时,线程对象已经被分配了内存空间和其它资源,并已被初始化。但是该线程尚未被调度。此时线程可以被调度,变成就绪状态。

2.就绪状态(runnable)

就绪状态也称为可运行状态。处于新建状态的线程被启动后,将进入线程队列排队等待CPU时间片,此时它已经具备了运行的条件,也就是处于就绪状态。

3.运行状态(running)

当就绪状态的线程被调度并获得CPU资源时,就绪状态就变成可运行状态。该状态表示线程正在运行,该线程已经拥有了CPU的控制权。

线程运行完毕;

有比当前线程优先级更高的线程处于就绪状态;

线程主动睡眠一段时间;

线程在等待某一资源;

4.阻塞状态(blocked)

一个正在执行的线程如果是某些特殊情况下,将让出CPU并暂时中止自己的执行,线程处于这种不可运行状态称为阻塞状态。

阻塞状态是因为某种原因系统不能执行线程的状态,在这种状态下即使CPU空闲也不能执行线程。在下面几种情况下可以使线程获得阻塞状态。

一是调用sleep()或者yeild()方法;二是为等待一个条件变量,线程调用wait()方法;三是该线程与另一个线程join()在一起。一个线程被阻塞时它不能进入排队队列,只有当引起阻塞的原因被消除时,线程才可以转入就绪状态,重新进到线程队列中排队等待CPU资源,以便从原来的暂停处继续运行。

5.消亡状态(dead)

处于消亡状态的线程不具有继续运行的能力。导致线程消亡的原因有两个:一是正常运行的线程完成了它全部的工作,即执行了run()方法后的最后一条语句并退出;二是当进程因故停止运行时,该进程中的所有线程都将被强制终止。当线程处于消亡状态,并且没有该线程对象的引用时,垃圾回收器会从内存中删除该线程对象。

11.1.3 线程的优先级与调度

1.优先级

在多线程中,每一个线程都被赋予一个执行优先级。优先级决定了线程被CPU执行的优先顺序。

优先级高的程序可以在调度时使用更多的执行时间。

Java语言中线程的优先级从低到高以整数1~10表示,共分为十级。Thread类中有三个关于线程优先级的静态变量,MIN_PRIORITY表示最小优先级,通常为1;MAX_PRIORITY表示最大优先级,通常为10;NORM_PRIORITY,默认值为5。

对应一个新建的线程,系统会遵循以下原则来指定优先级。

1)新建线程将继承创建它的父线程的优先级。父线程是指执行创建新线程对象语句所在的线程,它可能是程序的主线程,也可能是某一个用户自定义的线程;

2)一般情况下,主线程具有普通优先级。

2.调度

调度就是在各个线程之间分配CPU资源。多个线程并发实际上是通过一个调度来进行的。线程调度有两种模型:分时模型和抢占模型。在分时模型中,CPU资源是按照时间片分配的,获得CPU资源的线程只能在指定的时间段内执行;在分时模型中,程序本身不会让出CPU。在抢占模型中,当前活动的线程一旦获得执行权,将一直执行下去,直到执行它完成或由于某种原因主动放弃执行权。如在一个低优先级线程的执行过程中,又有一个高优先级的线程准备就绪,那么低优先级的线程就把CPU的资源让给高优先级的进程。

11.2 Java的Thread线程类与Runnable接口

Java语言中实现多线程的方法有两种:一种是继承java.lang包中的Thread类;另一种是用户在定义自己的类中实现Runnable接口。但不管采用那种方法,都要用到Java语言类库中的Thread类及其方法。

由于Java语言支持多线程功能,所以只要发现程序的工作都可以分头同时执行,就应该产生一个新的线程分头去工作。

11.2.1  利用Thread类的子类来创建线程

Java语言的基本类库中已定义了Thread的基本类,内置了一组方法,使得程序利用该类提供的方法产生一个新的线程、执行一个线程、终止一个线程的工作,或是查看线程的执行状态。

public Thread()        创建一个线程对象,此线程对象的名称是Thread-n的形式,其中n是一个整数,使用这个构造方法,必须创建thread类的一个子类并覆盖其run()方法;

public  Thread(String name)  创建一个线程对象,参数name制定了线程的名称

public Thread(Runnable target)  创建一个线程对象,此线程对象的名称是“thread-n的形式,其中n是一个整数。参数target的run()方法将被线程对象调用,来作为其执行代码。

public  Thread(Runnable target, String name)        功能同上,参数target的run()方法将被线程对象调用,来作为其执行代码。参数name指定了新创建线程的名称。

Thread类的常用方法

public static  Thread currentThread()        返回当前正在运行的线程对象

public final  String  getName()        返回线程的名称

public void start()        使用该线程由新建状态变为就绪状态,如果该线程已经是就绪状态,,则产生IllegalStateException异常

public void run()        线程应执行的任务

public final Boolean isAlive()  判断当前线程是否正在运行,若是返回true,否则返回false

public void interrupt()        中断当前线程

public boolean isInterrupt()        判断该线程是否被中断,若是返回true,若不是返回false

public final void join()        暂停当前线程的执行,等待调用该方法的线程结束后再继续执行本线程

public int getPriority()        返回线程的优先级

public final void getPriority(int newPriority)        设置线程优先级。如果当前线程不能修改这个线程,则产生SecurityException异常。如果参数不在所要求的优先级范围内,则产生IllegalArgumentException异常。

public static void  sleep(long millis)        为当前执行的线程指定睡眠时间。参数millis是线程睡眠的毫秒数。如果这个线程已经被别的线程中断,则产生Interrupt Exception异常。

public static void yield()        暂停当前线程的执行,但该线程仍处于就绪状态,不转为阻塞状态,该方法只给同优先级线程以运行的机会。

要在一个Thread子类里激活线程,必须先准备好下列两件事情。

1)此类必须是继承自Thread类;

2)线程所要执行的代码必须写在run()方法内。

线程执行时,从它的run()方法开始执行。run()方法是线程执行的起点,就像main()方法是应用程序的执行起点。

语法如下;

class 类名  extends Thread

{

        类中的成员变量;

        类中的成员方法;

        修饰符    run()

        {

                线程的代码

        }

}

注:run()方法规定了线程要执行的任务,但一般不是直接调用run()方法,而是通过线程的start()方法来启动线程。

//filename: App11_1.java
class MyThread extends Thread
{
	private String who;
	public MyThread(String str)
	{
		who = str;
	}
	public void run()
	{
		for(int i=0;i<5;i++)
		{
			try
			{
				sleep((int)(1000*Math.random()));
				
			}
			catch(InterruptedException e) { }
			System.out.println(who+" is running!");
		}
	}
}
public class App11_1
{
	public static void main(String[] args)
	{
		MyThread you=new MyThread("you");
		MyThread she=new MyThread("she");
		MyThread he=new MyThread("he");
		you.start();
		she.start();
		he.start();
		System.out.println("Main running is over!");
	}
}

11.2.2 用Runnable接口来创建线程

Runnable接口定义在java.lang包中,其中只提供了一个抽象方法run()的声明。

Runnable是Java语言中实现线程的接口,从本质上说,任何实现线程的类都必须实现该接口。其实Thread类就是直接继承了Object类,并实现了Runnable接口,所以其子类才具有线程的功能。

Thread类实际上定义了run()方法,但是对于Thread类本身来说,这种方法是空的。如果从Thread类生成子类,就要用新的方法覆盖Thread类中的该方法。

Runnable接口只有一个run()方法,将线程写入代码中,用户可以声明一个类并实现Runnable接口,并定义run方法,将线程代码写入其中,就完成了这一部分的任务。

其实,使用Runnable接口的好处不仅在于间接的解决了多重继承问题,与Thread类相比,Runnable接口更适合用多个线程处理同一资源。事实上,几乎所有的线程应用都可以用实现Runnable的方式来实现。

//filename:App11_2.java
class MyThread implements Runnable
{
	private String who;
	public MyThread(String str)
	{
		who = str;
	}
	public void run()
	{
		for(int i=0;i<5;i++)
		{
			try
			{
				Thread.sleep((int)(100*Math.random()));
			}
			catch(InterruptedException e)
			{
				System.out.println(e.toString());
			}
			System.out.println(who+" is running!");
		}
	}
}
public class App11_2
{
	public static void main(String[] args)
	{
		MyThread tom=new MyThread("Tom");
		MyThread jim=new MyThread("Jim");
		Thread t1=new Thread(tom);
		Thread t2=new Thread(jim);
		t1.start();
		t2.start();
	}
}

11.2.3  线程间的数据共享

当多个线程的执行代码都是同一个类的run()方法时,则称这多个线程共享相同的代码,同样当他们共享访问相同的对象时,则称他们共享相同的数据。

//filename: App11_3.java
class ThreadSale extends Thread
{
	private int tickets=10;
	public void run()
	{
		while(true)
		{
			if (tickets>0)
				System.out.println(getName()+"    Ticket machine Number  "+tickets--);
			else
				System.exit(0);
		}
	}
}
public class App11_3
{
	public static void main(String[] args)
	{
		ThreadSale t1=new ThreadSale();
		ThreadSale t2=new ThreadSale();
		ThreadSale t3=new ThreadSale();
		t1.start();
		t2.start();
		t3.start();
	}
}
//filename: App11_5.java
class ThreadSale implements Runnable
{
	private int tickets=10;
	public void run()
	{
		while(true)
		{
			if(tickets>0)
				System.out.println(Thread.currentThread().getName()+" Ticket machine number"+tickets--);
			else
				System.exit(0);
		}
	}
}
public class App11_5
{
	public static void main(String[] args)
	{
		ThreadSale t=new ThreadSale();
		Thread t1=new Thread(t,"Number 1");
		Thread t2=new Thread(t,"Number 2");
		Thread t3=new Thread(t,"Number 3");
		t1.start();
		t2.start();
		t3.start();
		
	}
}

11.3 多线程的同步控制

当线程运行时,不用关心外部的数据和方法,这样的线程称为独立的、不同步的或异步执行的。当应用问题的功能增强、关系复杂,存在多个线程共享数据时,若线程仍旧以异步的方式访问数据,有时是不安全的或不符合逻辑的。

同步指的是处理数据的线程不能处理其他线程当前还没有处理完的数据,但是可以处理其他数据。

//filename: App11_6.java
class Mbank
{
	private static int sum=2000;
	public static void take(int k)
	{
		int temp=sum;
		temp -= k;
		try {Thread.sleep((int)(1000*Math.random()));}
		catch(InterruptedException e) { }
		sum = temp;
		System.out.println("sum= "+sum);
	}
}
class Customer extends Thread
{
	public void run()
	{
		for(int i=1;i<=4;i++)
			Mbank.take(100);
	}
	
}
public class App11_6
{
	public static void main(String[] args)
	{
		Customer c1=new Customer();
		Customer c2=new Customer();
		c1.start();
		c2.start();
	}
}

在并发程序设计中,对多个线程共享的资源或数据称为临界资源或同步资源,而把每个线程中访问临界资源的那一段代码称为临界代码或临界区。简单地说,一个时期只能被一个线程访问的资源称为临界资源,而使用临界资源的代码称为临界区。为了使临界代码的线程间互斥地操作,在Java语言中每个对象都有一个互斥锁与之相连。当一个线程得到互斥锁之后,则需要该互斥锁的其他线程只能处于等待状态。

为了保证互斥,Java语言使用synchronized关键字来标识同步资源,这里的资源可以是一种类型的数据,也就是对象,也可以是一个方法,还可以是一段代码。语法如下:

格式一:同步语句

Synchronized(对象)

{

        临界代码段

}

格式二:同步方法

public synchronized 返回类型  方法名()

{

        方法体

}

synchronized的功能是:首先判断对象或方法的互斥锁是否存在,若在就获得互斥锁,然后就可以执行紧随其后的临界代码或方法体;如果对象或方法的互斥锁不存在(已被其他线程拿走),就进入等待状态,直到获得互斥锁。

//filename: App11_7.java
class Mbank
{
	private static int sum=2000;
	public synchronized static  void take(int k)
	{
		int temp=sum;
		temp -= k;
		try {Thread.sleep((int)(1000*Math.random()));}
		catch(InterruptedException e) { }
		sum = temp;
		System.out.println("sum= "+sum);
	}
}
class Customer extends Thread
{
	public void run()
	{
		for(int i=1;i<=4;i++)
			Mbank.take(100);
	}
	
}
public class App11_7
{
	public static void main(String[] args)
	{
		Customer c1=new Customer();
		Customer c2=new Customer();
		c1.start();
		c2.start();
	}
}

11.4 线程之间的通信

多线程的执行往往需要相互之间配合。为了有效地协调不同线程的工作,需要在线程之间建立沟通渠道,通过线程间的”对话“来解决线程之间的同步问题,而不仅仅是依靠互斥的机制。

java.lang.Object类中的wait()、notify()等方法为线程间的通信提供了有效手段。

public final void wait()        如果一个正在执行同步代码的线程A执行了wait()调用,该线程暂停执行而进入对象的等待队列,并释放自己已获得的对象x的互斥锁。线程A要一直等到其他线程在对象x上调用notify()或notify All()方法,才能重新弄获得对象x的互斥锁之后继续执行从wait()语句后的继续执行。

public void notigy()        唤醒正在等待该对象互斥锁的第一个线程

public void notifyAll()        唤醒正在等待该对象互斥锁的所有线程,具有最高优先级的线程首次按被唤醒并执行。

 

//filename: App11_8.java
public class App11_8
{
	public static void main(String[] args)
	{
		Tickets t=new Tickets(10);
		new Producer(t).start();
		new Consumer(t).start();
	}
}
class Tickets
{
	protected int size;
	int number=0;
	boolean available=false;
	public Tickets(int size)
	{
		this.size=size;
	}
	public synchronized void put()
	{
		if(available)
			try {wait();}
			catch(Exception e) { }
		System.out.println("deposit tickets ["+(number)+" ]");
		available=true;
		notify();
	}
	public synchronized void sell()
	{
		if(!available)
			try {wait();}
			catch(Exception e) { }
		System.out.println("sold out tickets ["+(number)+" ]");
		available=false;
		notify();
		if (number==size)  number = size +1;
			}
	}
	class Producer extends Thread
	{
		Tickets t=null;
		public Producer(Tickets t)
		{
			this.t = t;
		}
		public void run()
		{
			while(t.number<t.size)
				t.put();
		}
	}
	class Consumer extends Thread
	{
		Tickets t=null;
		public Consumer(Tickets t)
		{
			this.t=t;
		}
		public void run()
		{
			while(t.number<t.size)
				t.sell();
			
		}
	}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

王辞夜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值