首页 > 代码库 > 黑马程序与----java线程学习
黑马程序与----java线程学习
线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有就绪、阻塞和运行三种基本状态。每一个程序都至少有一个线程,若程序只有一个线程,那就是程序本身。
线程是程序中一个单一的顺序控制流程。在单个程序中同时运行多个线程完成不同的工作,称为多线程。
用范围
1.服务器中的文件管理或通信控制
2.前后台处理
3.异步处理
特点
线程的使用
在多线程OS中,通常是在一个进程中包括多个线程,每个线程都是作为利用CPU的基本单位,是花费最小开销的实体。线程具有以下属性。
1)轻型实体
线程中的实体基本上不拥有系统资源,只是有一点必不可少的、能保证独立运行的资源,比如,在每个线程中都应具有一个用于控制线程运行的线程控制块TCB,用于指示被执行指令序列的程序计数器、保留局部变量、少数状态参数和返回地址等的一组寄存器和堆栈。
2)独立调度和分派的基本单位。
在多线程OS中,线程是能独立运行的基本单位,因而也是独立调度和分派的基本单位。由于线程很“轻”,故线程的切换非常迅速且开销小(在同一进程中的)。
3)可并发执行。
在一个进程中的多个线程之间,可以并发执行,甚至允许在一个进程中所有线程都能并发执行;同样,不同进程中的线程也能并发执行,充分利用和发挥了处理机与外围设备并行工作的能力。
4)共享进程资源。
线程
在同一进程中的各个线程,都可以共享该进程所拥有的资源,这首先表现在:所有线程都具有相同的地址空间(进程的地址空间),这意味着,线程可以访问该地址空间的每一个虚地址;此外,还可以访问进程所拥有的已打开文件、定时器、信号量机构等。由于同一个进程内的线程共享内存和文件,所以线程之间互相通信不必调用内核。
线程与进程的比较
线程
进程是资源分配的基本单位。所有与该进程有关的资源,都被记录在进程控制块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); } } }
其输出结果为:
我们可以看到,两个方法都是在一个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); } } }
其执行结果如下:
这个时候,我们可以看到,线程正在交替的进行,也就是说现在有两个线程正在异步执行,两个线程采用你执行一会,我执行一会的交替(这个一会取决于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); } } }
结果为:
上述代码之所以不直接在实现的类中覆写指定的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(); } }
这个时候,我们发现运行结果和我们预期的一致.结果为:
只要我们记住一句话就可以了,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(); } }
但是结果不如人意:
相同的座位的票被卖出了多次,那么电影院的确是赚钱了,但是随机会破产,因为所有的顾客都会投诉.如果这件时间是发生在火车票系统或者航空系统那么就可怕了.当然有人会使用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的方法来编写多线程操作.
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。