码迷,mamicode.com
首页 > 编程语言 > 详细

黑马程序与----java线程学习

时间:2014-06-02 23:11:22      阅读:399      评论:0      收藏:0      [点我收藏+]

标签:c   style   class   blog   code   java   

线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有就绪阻塞运行三种基本状态。每一个程序都至少有一个线程,若程序只有一个线程,那就是程序本身。
线程是程序中一个单一的顺序控制流程。在单个程序中同时运行多个线程完成不同的工作,称为多线程

用范围

1.服务器中的文件管理或通信控制
2.前后台处理
3.异步处理

特点

bubuko.com,布布扣

线程的使用

多线程OS中,通常是在一个进程中包括多个线程,每个线程都是作为利用CPU的基本单位,是花费最小开销的实体。线程具有以下属性。
1)轻型实体
线程中的实体基本上不拥有系统资源,只是有一点必不可少的、能保证独立运行的资源,比如,在每个线程中都应具有一个用于控制线程运行的线程控制块TCB,用于指示被执行指令序列的程序计数器、保留局部变量、少数状态参数和返回地址等的一组寄存器堆栈
2)独立调度和分派的基本单位。
在多线程OS中,线程是能独立运行的基本单位,因而也是独立调度和分派的基本单位。由于线程很“轻”,故线程的切换非常迅速且开销小(在同一进程中的)。
3)可并发执行。
在一个进程中的多个线程之间,可以并发执行,甚至允许在一个进程中所有线程都能并发执行;同样,不同进程中的线程也能并发执行,充分利用和发挥了处理机与外围设备并行工作的能力。
4)共享进程资源。
bubuko.com,布布扣

线程

在同一进程中的各个线程,都可以共享该进程所拥有的资源,这首先表现在:所有线程都具有相同的地址空间(进程的地址空间),这意味着,线程可以访问该地址空间的每一个虚地址;此外,还可以访问进程所拥有的已打开文件、定时器、信号量机构等。由于同一个进程内的线程共享内存文件,所以线程之间互相通信不必调用内核

线程与进程的比较

bubuko.com,布布扣

线程

进程是资源分配的基本单位。所有与该进程有关的资源,都被记录在进程控制块PCB中。以表示该进程拥有这些资源或正在使用它们。
另外,进程也是抢占处理机的调度单位,它拥有一个完整的虚拟地址空间。当进程发生调度时,不同的进程拥有不同的虚拟地址空间,而同一进程内的不同线程共享同一地址空间。
与进程相对应,线程与资源分配无关,它属于某一个进程,并与进程内的其他线程一起共享进程的资源。
线程只由相关堆栈系统栈或用户栈寄存器和线程控制表TCB组成。寄存器可被用来存储线程内的局部变量,但不能存储其他线程的相关变量。
通常在一个进程中可以包含若干个线程,它们可以利用进程所拥有的资源。在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位。由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统内多个程序间并发执行的程度,从而显著提高系统资源的利用率和吞吐量。因而近年来推出的通用操作系统都引入了线程,以便进一步提高系统的并发性,并把它视为现代操作系统的一个重要指标。
线程与进程的区别可以归纳为以下4点:
1)地址空间和其它资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
2)通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
3)调度和切换:线程上下文切换比进程上下文切换要快得多。
4)在多线程OS中,进程不是一个可执行的实体。
而java是少数的几种支持多线程的语言之一.
原始情况下,我们在不使用多线程的时候,代码总是在执行在main方法所在的线程中运行,这种情况下,如果碰到多个超长循环的时候,后面的代码总是迟迟无法执行,这种情况下,尤其是在用swing编写的游戏中,这样做会有很多动作延迟
下面举一个例子来说:
package cn.felay.thread;
class ThreadDemo1{
	public void run(){
		for (int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread()+""+i);
		}
	}
}
public class SingleThreadDemo {
	public static void main(String[] args) {
		new ThreadDemo1().run();
		for (int i = 0; i <10; i++) {
			System.out.println(Thread.currentThread()+""+i);
		}
	}
}
其输出结果为:
bubuko.com,布布扣
我们可以看到,两个方法都是在一个main方法的线程中执行,无法进行多线程操作,如果循环次数比较少而CPU处理的比较快的话,对程序的影响不大,但是当循环次数基量比较大或者逻辑比较复杂的时候,这样做,后面的代码可能要等上很长时间才回去执行,而且当正在执行的循环代码还不是那么重要的时候,我们就不能接受了.这个时候我们可以去采用多线程来进行操作了,如下:
package cn.felay.thread;
class ThreadDemo1 extends Thread{
	public void run(){
		for (int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread()+""+i);
		}
	}
}
public class SingleThreadDemo {
	public static void main(String[] args) {
		new ThreadDemo1().start();
		for (int i = 0; i <10; i++) {
			System.out.println(Thread.currentThread()+""+i);
		}
	}
}

其执行结果如下:
bubuko.com,布布扣

这个时候,我们可以看到,线程正在交替的进行,也就是说现在有两个线程正在异步执行,两个线程采用你执行一会,我执行一会的交替(这个一会取决于CPU的时间周期),而在java中采用多线程的方法主要有三种:
1.实现Runnable接口
2.继承Thread类(不推荐)
3.直接使用Thread类(尽管可以这么做)
实际上,看到了JavaAPI中,Thread类也是实现了Runnble接口.
因此上面的代码我们可以改写为:
package cn.felay.thread;
class ThreadDemo1 implements Runnable{
	public void run() {
	}
}
public class SingleThreadDemo {
	public static void main(String[] args) {
		new Thread(new ThreadDemo1(){
			public void run(){
				for (int i = 0; i < 10; i++) {
					System.out.println(Thread.currentThread()+""+i);
				}
			}
		}).start();
		for (int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread()+""+i);
		}
	}
}
结果为:
bubuko.com,布布扣
上述代码之所以不直接在实现的类中覆写指定的run()方法,是因为,在大多数的时候,我们在执行多线程的时候,并不总是采用一个固定的方法体的,因此我们可以只是去实现空的,等到真正去使用的时候采取覆写.
既然有三种方法来实现多线程的话,那么我们应该采用哪种呢?
1.直接使用Thread类,如下面实例:
package cn.felay.thread;
public class ThreadDemo {
	public static void main(String[] args) {
		new Thread().run();
		new Thread().run();
		new Thread().run();
		new Thread().run();
		new Thread().run();
		new Thread().run();
	}
}

那么这样的代码有什么意义?我们的确开启了多个线程,但是这些线程什么事情都没有做,返回让JVM增加额外的开销去创建线程,因此,这是一种无意义的多线程使用方法.
2.继承Thread类
也许有人这么尝试过这么使用线程继承
package cn.felay.thread;
public class ThreadDemo extends Thread{
	public void run() {
		int i = 0;
		while(i<100){
			System.out.println(Thread.currentThread().getName()+"==="+i);
			i++;
		}
	}

	public static void main(String[] args) {
		new ThreadDemo().run();
		new ThreadDemo().run();
		new ThreadDemo().run();
		new ThreadDemo().run();
		new ThreadDemo().run();
		new ThreadDemo().run();
	}
}

然后运行后发现,开启了多个线程后,为什么还总是在一个线程中执行.
package cn.felay.thread;
public class ThreadDemo extends Thread{
	public void run() {
		int i = 0;
		while(i<100){
			System.out.println(Thread.currentThread().getName()+"==="+i);
			i++;
		}
	}

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

这个时候,我们发现运行结果和我们预期的一致.结果为:
bubuko.com,布布扣
这里我们就要区别了run()和start()方法的区别了:

) start:

用start方法来启动线程,真正实现了多线程运行,这时无需等待run方法体代码执行完毕而直接继续执行下面的代码。通过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到spu时间片,就开始执行run()方法,这里方法run()称为线程体,它包含了要执行的这个线程的内容,Run方法运行结束,此线程随即终止。

2) run:

run()方法只是类的一个普通方法而已,如果直接调用Run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行下面的代码,这样就没有达到写线程的目的。

总结:调用start方法方可启动线程,而run方法只是thread的一个普通方法调用,还是在主线程里执行。

只要我们记住一句话就可以了,start()方法才会启动线程,而run()只不过是调用了一个方法(至于为什么,sorry,API文档上是这么规定的,只有在调用了start()方法的时候JVM才会启动一个线程)
那么我们回到问题上,这么做有什么好处,好处就是我们的确开启了多个线程,但是很遗憾,我们的每个线程都拼命的抢占CPU的资源然后去执行自己的run()方法内的循环代码,那么这么做给我们的程序带来了什么好处呢?这么做的话,我们就减少了系统开销了么?很遗憾,没有,那么资源共享呢?也很遗憾,也没有,我们用下面的例子证明:假如我们正在为一家电影院卖票,我们需要在多个窗口共享所有的票数,也就是说,每个窗口只能出售一张指定座位的电影票,如果我们使用继承Thread类的方法来实现这个效果的话,代码如下:
package cn.felay.thread;

public class ThreadDemo2 extends Thread {
	private int ticktes = 10;
	public void run() {
		while(ticktes>0){
				System.out.println(Thread.currentThread().getName()+"出售了第"+(ticktes--)+"张票");
		}
	}
	public static void main(String[] args) {
		
		new ThreadDemo2().start();
		new ThreadDemo2().start();
		new ThreadDemo2().start();
		new ThreadDemo2().start();
	}

}

但是结果不如人意:
bubuko.com,布布扣
相同的座位的票被卖出了多次,那么电影院的确是赚钱了,但是随机会破产,因为所有的顾客都会投诉.如果这件时间是发生在火车票系统或者航空系统那么就可怕了.当然有人会使用static来表示票数,如下:
package cn.felay.thread;

public class ThreadDemo2 extends Thread {
	private static int ticktes = 10;
	public void run() {
		while(ticktes>0){
				System.out.println(Thread.currentThread().getName()+"出售了座位号为"+(ticktes--)+"的票");
		}
	}
	public static void main(String[] args) {
		
		new ThreadDemo2().start();
		new ThreadDemo2().start();
		new ThreadDemo2().start();
		new ThreadDemo2().start();
	}

}

这样的确是可以解决问题,但是问题也随即而来,这个static修饰变量是常驻内存的,假如我们共享的资源非常大呢?常驻内存的话需要很大的开销,那么怎么办呢?这个时候我们就需要采用下面的方式了
3.使用实现Runnable接口的方式
package cn.felay.thread;

public class ThreadDemo2 implements Runnable {
	private  int ticktes = 10;
	public void run() {
		while(ticktes>0){
				System.out.println(Thread.currentThread().getName()+"出售了座位号为"+(ticktes--)+"的票");
		}
	}
	public static void main(String[] args) {
		ThreadDemo2 t =new ThreadDemo2();
		new Thread(t).start();
		new Thread(t).start();
		new Thread(t).start();
		new Thread(t).start();
	}

}
这样以来,我们即使不使用static修饰,我们依然能够进行同步操作,即所有的线程都共享的是同一个资源,不管这个资源多大,一旦使用线程销毁,这个资源也会随着销毁,因此它的开销只是在创建的时候才会有的.
这样总结而来,因为我们在java中使用实现Runnable的方法来编写多线程操作.

黑马程序与----java线程学习,布布扣,bubuko.com

黑马程序与----java线程学习

标签:c   style   class   blog   code   java   

原文地址:http://blog.csdn.net/u012332735/article/details/27720897

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!