多线程

一、多线程概述
1. 进程
  进程是应用程序的执行实例,每个进程是由私有的虚拟地址空间、代码、数据和其它系统资源组成。
2. 线程
  通常在一个进程中可以包含若干个线程,它们可以利用进程所拥有的资源,在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位,由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统内多个程序间并发执行的程度。
  线程是程序中一个单一的顺序控制流程。在单个程序中同时运行多个线程完成不同的工作,称为多线程。
  多线程是为了同步完成多项任务,不是为了提高运行效率,而是为了提高资源使用效率来提高系统的效率。

二、创建线程的方式

  在Java中要想实现多线程,有两种手段,一种是继承Thread类,另外一种是实现Runable接口。在实现Runnable接口时需要建立一个Thread实例。因此,无论是通过Thread类还是Runnable接口建立线程,都必须建立Thread类或它的子类的实例。


1. 继承Thread类,覆盖run()方法
  启动线程的唯一方法就是通过Thread类的start()实例方法。start()方法是一个native方法,它将启动一个新线程,并执行run()方法。

public class MyThread extends Thread{
	public void run(){
		while (true){
			System.out.println(Thread.currentThread().getName());
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}


	public static void main(String[] args) {
		MyThread mt1 = new MyThread();
		MyThread mt2 = new MyThread();
		mt1.start();
		mt2.start();
	}
}

  ※几个Thread常用的方法
  Static native Thread currentThread():返回线程对象的一个引用,它控制当前执行的线程。在任何地方利用这个方法都可获当前是哪一个线程在运行。
  final String getName():以字符串形式返回线程的名称。
  final int getPriority():返回线程的优先级。
  void start() :启动线程对象
  void sleep (long millis):使线程暂时休眠millis毫秒,让低优先级的线程暂时获取处理器资源。

2. 实现Runnable接口,实现run()方法
  由于Java只允许单继承,所以一个类已经继承了别的类,那就无法继承Thread类了。此时,为了实现多线程,Java提供了Runnable接口。实现Runnable接口中的run()方法之后,通过创建一个Thread的实例就可以使用多线程,之后同样使用start()方法在合适的地方启动多线程。

public class MyThread2 implements Runnable {
	
	public void run() {
		while (true){
			System.out.println(Thread.currentThread().getName());
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}


	public static void main(String[] args) {
		Thread mt1 = new Thread(new MyThread2(),"线程A");
		Thread mt2 = new Thread(new MyThread2(),"线程B");
		mt1.start();
		mt2.start();
	}
}

三、线程的生命周期及控制
  线程有开始(等待)、运行、挂起(阻塞)和停止四种不同的状态。
1. 开始(等待)
  线程在建立后并不马上执行run()方法中的代码,而是处于等待状态。线程处于等待状态时,可以通过Thread类的方法来设置线程不各种属性,如线程的优先级(setPriority)、线程名(setName)和线程的类型(setDaemon)等。

2. 运行
  当调用start()方法后,系统为这个线程分配它所需的系统资源,安排其运行并调用线程运行方法,线程执行run()方法中的代码,线程进入运行状态。
  需要注意的是这一状态并不是运行中状态(Running ),因为线程也许实际上并未真正运行。由于很多计算机都是单处理器的,所以要在同一时刻运行所有的处于可运行状态的线程是不可能的,Java的运行系统必须实现调度来保证这些线程共享处理器。

3. 挂起(阻塞)
  一但线程开始执行run()方法,就会一直到这个run()方法执行完成这个线程才退出。但在线程执行的过程中,线程可能因为某种原因(输入/输出、等待消息或其它阻塞情况),进入系统不能继续执行线程的状态。这时即使处理器空闲,也不能执行该线程,这个时候就称为线程挂起(阻塞)
  使用suspend挂起线程,然后可以通过resume方法唤醒线程;
  sleep可以使线程休眠,在设定的时间后,线程将处于等待状态;
  在使用sleep方法时必须使用throws或try{…}catch{…}。因为run方法无法使用throws,所以只能使用try{…}catch{…}。当在线程休眠的过程中,使用interrupt方法中断线程时sleep会抛出一个InterruptedException异常。
  线程阻塞还可能因为等候一个条件变量,线程调用wait()方法,或在输入输出流中发生线程阻塞。
  被挂起的线程,可以在满足一定的条件后进入到等待状态,然后再次被启用。

4. 停止
  有三种方法可以使线程终止。
  a. 线程执行完之后自然撤消,使线程正常退出,也就是当run方法完成后线程终止;
  b. 使用stop()方法强行终止线程(这个方法不推荐使用,因为stopsuspendresume一样,也可能发生不可预料的结果);
  c. 使用interrupt()方法中断线程。


四、多线程的互斥与同步
  通常,一些同时运行的线程需要共享数据。如果线程只是读取这个共享的数据,则不必阻止多个线程同时访问该数据。但是,在多个线程访问同一个数据时,若A线程读取了一个数据,同时B线程对这个数据进行了修改,那么程序会变得具有不确定性,不能保证运行结果的正确。
  所以,解决的办法就是在一个线程运行时,对共享数据的语句进行锁定,只能在一个线程执行结束之后,才允许另一个线程对其访问。
  Java中采用“对象互斥锁”阻止多个线程同时访问同一共享资源。
  实现“对象互斥锁”有两个办法:
  1. 用关键字volatile来声明一个共享数据(变量);
  2. 用关键字synchronized来声明一个操作共享数据的方法或一段代码。
  一般情况下,都使用synchronized关键字在方法的层次上实现对共享资源操作的同步,很少使用volatile关键字声明共享变量。 

public synchronized void call(String callerName, String msg){
	int time;
	System.out.println("我在接"+callerName+"打来的电话,他说:"+msg+",我在和他交谈...");
	time = (int)(Math.random()*1000+1000);
	try {
		Thread.sleep(time);
	} catch (InterruptedException e) {
		e.printStackTrace();
	}
	System.out.println("我接完"+callerName+"的电话!");
}

  为了处理线程间同步的问题,出于代码的安全性和健壮性的考虑,java放弃了使用原来的suspendresume方法来处理线程的挂起等待和唤醒继续,取而代之则是从Object类继承而来的wait(等待)、nnotify(唤醒)、notifyAll(唤醒全部)。这三个方法必须在synchronized代码块中调用,否则会出现IllegalMonitorStateException异常。
  wait()//这个方法可以使线程一直等待,直到其他线程调用notify(或notifyAll)将其唤醒。
  notify()//唤醒一个等待同一个管程锁的线程,如果同一管程锁下有多个线程处于睡眠等待状态,则随机地挑出一个等待线程将其唤醒。
  notifyAll()//唤醒所有等待同一管程锁的线程,这些唤醒后的线程同样也要拥有管程才能继续执行。


  死锁是所有线程都因为申请不到它们所需要的资源而全部进入“阻塞”状态时,该Java程序将被挂起,程序再不能继续前进。比如说,系统有A、B两个资源,现在程序有甲、乙两个线程,两个线程均需要A、B两个资源,然而发生的情况是,甲申请到了A资源,乙申请到了B资源,于是甲就开始等待B资源被释放,乙开始等待A资源被释放,而两个线程在没有等到自己所需的资源时,不会释放自己所拥有的资源。最终程序停止在这里。
  若不配套使用使用wait()/notifyAll(),就很容易造成死锁。


五、守护线程
  守护线程,是专门为其它线程提供服务的线程,故又称为服务线程。非守护线程包括常规的用户线程或诸如用于处理GUI事件的事件调度线程。守护线程与其它线程的区别是,守护线程不会阻止程序的终止,如果守护线程是唯一运行着的线程,程序会自动退出。
  通过调用线程的setDaemon方法可以实现用户线程与守护线程之间的转换。它的方法头如下:
  public void setDaemon(boolean on)
  当ontrue时,将线程转成守护线程,onfalse时转成用户线程
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值