1.多线程

 1.1线程

线程是程序运行的基本执行单元。指的是一段相对独立的代码,执行指定的计算或操作。多操作系统执行一个程序时会在系统中建立一个进程,而在这个进程中,必须至少建立一个线程(这个线程被称为主线程)来作为这个程序运行的入口点。每个线程都有独立的堆栈、计数器、本地变量。但是能够共享内存,文件处理器,预存储状态等资源。JVM至少有主线程和垃圾清理进程。

1.2使用多线程的好处

如果能合理地使用线程,将会减少开发和维护成本,甚至可以改善复杂应用程序的性能。如在GUI应用程序中,还以通过线程的异步特性来更好地处理事件;在应用服务器程序中可以通过建立多个线程来处理客户端的请求。线程甚至还可以简化虚拟机的实现,如Java虚拟机(JVM)的垃圾回收器(garbage collector)通常运行在一个或多个线程中。

好处包括:

      ❶充分利用CPU资源。当执行单线程程序时,由于在程序发生阻塞时CPU可能会处于空闲状态。这将造成大量的计算资源的浪费。多线程可以在某一个线程处于休眠或阻塞时,而CPU又恰好处于空闲状态时来运行其他的线程。
      ❷简化编程模型。如果程序只完成一项任务,那只要写一个单线程的程序,并且按着执行这个任务的步骤编写代码即可。但要完成多项任务,如果还使用单线程的话,那就得在在程序中判断每项任务是否应该执行以及什么时候执行。如显示一个时钟的时、分、秒三个指针。使用单线程就得在循环中逐一判断这三个指针的转动时间和角度。如果使用三个线程分另来处理这三个指针的显示,那么对于每个线程来说就是指行一个单独的任务。这样有助于开发人员对程序的理解和维护。
      ❸简化异步事件的处理。当一个服务器应用程序在接收不同的客户端连接,监听线程负责监听来自客户端的请求。单线程来处理,当监听线程接收某个客户端请求后,读取客户端发来的数据,在读完数据后,read方法处于阻塞状态,也就是说,这个线程将无法再监听客户端请求了。而要想在单线程中处理多个客户端请求,就必须使用非阻塞的Socket连接和异步I/O。但使用异步I/O方式比使用同步I/O更难以控制,也更容易出错。因此,使用多线程和同步I/O可以更容易地处理类似于多请求的异步事件。
      ❹使GUI更有效率。使用单线程来处理GUI事件时,必须使用循环来对随时可能发生的GUI事件进行扫描,在循环内部除了扫描GUI事件外,还得来执行其他的程序代码。如果这些代码太长,那么GUI事件就会被“冻结”,直到这些代码被执行完为止。
      ❺节约成本。提高程序的执行效率一般有三种方法:
         ⑴增加计算机的CPU个数。昂贵。
         ⑵为一个程序启动多个进程。不容易共享数据,系统资源占用大。
         ⑶在程序中使用多进程。多线程可以模拟多块CPU的运行方式,廉价。

2.多线程的创建和实例化

❶扩展(extends)java.lang.Thread类

特点:需要重写public void run()方法。

实例化:直接new调用构造函数即可。

❷实现(implements)java.lang.Runnable接口。

特点:使用Thread的构造方法,如下:

实例化:name是自定的进程名,缺省时系统会默认制定。

Thread(Runnable target)
Thread(Runnable target, String name)
Thread(ThreadGroup group, Runnable target)
Thread(ThreadGroup group, Runnable target, String name)
Thread(ThreadGroup group, Runnable target, String name, long stackSize)

❸两者的优劣

实际上,Thread也是实现了Runnable接口:

class Thread implements Runnable {
//…
public void run() {
if (target != null)
target.run();
}
}
      相较于Thread,接口Runnable有利于实现资源共享。且有如下优势:
          ⑴适合多个相同的程序代码的线程去处理同一个资源
          ⑵可以避免java中的单继承的限制
          ⑶增加程序的健壮性,代码可以被多个线程共享,代码和数据独立。

❹实例。接口Runable实现资源共享的例子:

class MyThread implements Runnable{

    private int ticket = 5;  //5张票

    public void run() {
for (int i=0; i<=20; i++) {
if (this.ticket > 0) {
System.out.println(Thread.currentThread().getName()+ "正在卖票"+this.ticket--);
}
}
}
}
public class lzwCode { public static void main(String [] args) {
MyThread my = new MyThread();
new Thread(my, "1号窗口").start();
new Thread(my, "2号窗口").start();
new Thread(my, "3号窗口").start();
}
}

3.线程的状态

新状态:线程对象已经创建,还没有在其上调用start()方法。

可运行状态:当线程有资格运行,但调度程序还没有把它选定为运行线程时线程所处的状态。当start()方法调用时,线程首先进入可运行状态。在线程运行之后或者从阻塞、等待或睡眠状态回来后,也返回到可运行状态。

运行状态:线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式。

等待/阻塞/睡眠态:这是线程有资格运行时它所处的状态。实际上这个三状态组合为一种,其共同点是:线程仍旧是活的,但是当前没有条件运行。换句话说,它是可运行的,但是如果某件事件出现,他可能返回到可运行状态。

死亡态:当线程的run()方法完成时就认为它死去。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦死亡,就不能复生。如果在一个死去的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常.

4.线程的控制

4.1 优先 setPriority()

      Thread类中有三个常量,定义线程优先级范围:
             static int MAX_PRIORITY   线程可以具有的最高优先级。
             static int MIN_PRIORITY      线程可以具有的最低优先级。
             static int NORM_PRIORITY  分配给线程的默认优先级。

线程的优先级用1-10之间的整数表示,数值越大优先级越高,默认的优先级为5。主线程的优先级是5。与线程休眠类似,线程的优先级仍然无法保障线程的执行次序。只不过,优先级高的线程获取CPU资源的概率较大,优先级低的并非没机会执行。

JVM从不会改变一个线程的优先级。然而,1~10之间的值是没有保证的。一些JVM可能不能识别10个不同的值,而将这些优先级进行每两个或多个合并,变成少于10个的优先级,则两个或多个优先级的线程可能被映射为一个优先级。

窗口操作系统中,使用优先权加时间段的处理机制,优先级高的线程执行时间超时,也必须让步给低优先级的进程在一个线程中开启另外一个新线程,则新开线程称为该线程的子线程,子线程初始优先级与父线程相同。

class hello implements Runnable {
public void run() {
for(int i=0;i<5;++i){
System.out.println(Thread.currentThread().getName()+"运行"+i);
}
} public static void main(String[] args) {
Thread h1=new Thread(new hello(),"A");
Thread h2=new Thread(new hello(),"B");
Thread h3=new Thread(new hello(),"C");
h1.setPriority(8);
h2.setPriority(2);
h3.setPriority(6);
h1.start();
h2.start();
h3.start(); }
}

输出:

 A运行0 
A运行1
A运行2
A运行3
A运行4
B运行0
C运行0
C运行1
C运行2
C运行3
C运行4
B运行1
B运行2
B运行3
B运行4

并非优先级越高就先执行。谁先执行还是取决于谁先去的CPU的资源。

4.2让步 yield()

线程的让步含义就是使当前运行的线程让出CPU资源,即当前线程暂停,当前线程的状态回到可运行状态。这样允许具有相同优先级的其他线程获得运行机会。使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。因为让步的线程还有可能被线程调度程序再次选中。

如:

class hello implements Runnable {
public void run() {
for(int i=0;i<5;++i){
System.out.println(Thread.currentThread().getName()+"运行"+i);
if(i==3){
System.out.println("线程的礼让");
Thread.currentThread().yield();
}
}
} public static void main(String[] args) {
Thread h1=new Thread(new hello(),"A");
Thread h2=new Thread(new hello(),"B");
h1.start();
h2.start(); }
}

输出:

A运行0
A运行1
A运行2
A运行3
线程的礼让
A运行4
B运行0
B运行1
B运行2
B运行3
线程的礼让
B运行4

4.3加入 joint()

使调用他的进程插入当前进程,直到该进程执行完毕才恢复阻塞进程的运行。倘若该调用进程为无限循环进程,且发生了其它线程中断它,则或抛出InterruptedException异常,而停止运行。joint()非静态方法:

void join();//等待该线程终止。
void join(long millis);//等待该线程终止的时间最长为 millis毫秒。
void join(long millis,int nanos);//等待该线程终止的时间最长为 millis毫秒 + nanos 纳秒。

如下计算圆周率的例子:

       

public class Estimate {
public static void main(String[] args){
Thread demo=new estimatePI();
demo.start();
try{
demo.join();//阻塞主线程直到demo运行完毕
}
catch(InterruptedException e){}
System.out.println("PI= "+demo);
}
} class estimatePI extends Thread{
public static double PI=0.0;
private double flag=1.0; public void run(){
for(long i=1;i<999;i+=2){
PI+=4.0*flag/i;
flag=-flag;
}
}
public String toString(){
return ""+PI;
}
}

4.4中断 interrupt()

中断正在进行的线程。一旦中断,则抛出Interrupted异常,此时需要在run()中处理该异常。该进程的isInterrupted也将设置为真。

例如,使用中断监视用户从键盘的输入。

public class Stop {
public static void main(String[] args){
Thread demo=new Service();
Scanner sc=new Scanner(System.in);
demo.start();
String key="";
while(!key.equals("stop"))
key=sc.nextLine();
demo.interrupt();
}
} class Service extends Thread{ private int count=1;
public void run(){
while(!interrupted())
System.out.println(this.getName()+" "+count++);
System.out.println("终止");
}
}

4.5睡眠sleep()

       Thread.sleep(long millis)
    Thread.sleep(long millis, int nanos)

是帮助所有线程获得运行机会的最好方法。睡眠了,其他的线程就有机会执行了。参数设定线程不会运行的最短时间。sleep()是静态方法,强制当前正在执行的线程休眠(暂停执行)。当睡眠时间到期,则返回到可运行状态而不是运行状态。会抛出Interrupted必检异常。

如下使用睡眠,保证先输出中文再输出English:

public class Test{
public static void main(String[] args){
Thread t1,t2;
t1=new English();
t2=new Chinese();
t2.start();
t1.start();
}
} class English extends Thread{
public void run(){
System.out.println("English");
}
} class Chinese extends Thread{ public void run(){
try
{Thread.sleep(500);
System.out.println("中文");}
catch(InterruptedException e){}
}
}

5.小结

5.1线程离开运行状态的3种方法

❶调用Thread.sleep():使当前线程睡眠至少多少毫秒(尽管它可能在指定的时间之前被中断)。

❷调用Thread.yield():不能保障太多事情,尽管通常它会让当前运行线程回到可运行性状态,使得有相同优先级的线程有机会执行。

❸调用join()方法:保证当前线程停止执行,直到该线程所加入的线程完成为止。然而,如果它加入的线程没有存活,则当前线程不需要停止。

5.2其它

❶线程的调度是JVM的一部分,在一个CPU的机器上上,实际上一次只能运行一个线程。一次只有一个线程栈执行。JVM线程调度程序决定实际运行哪个处于可运行状态的线程

❷抛出InterruptedException异常的程序包括静态函数Thread.sleep和非静态函数joint(),interrupt().

参考:

1.Java线程详解

2.java多线程总结

Java探索之旅(17)——多线程(1)的更多相关文章

  1. Java探索之旅(18)——多线程(2)

    1 线程协调 目的对各线程进行控制,保证各自执行的任务有条不紊且有序并行计算.尤其是在共享资源或者数据情况下. 1.1 易变volatile cache技术虽然提高了访问数据的效率,但是有可能导致主存 ...

  2. Java探索之旅(1)——概述与控制台输入

    使用的课本: Java语言程序设计(基础篇)----西电 李娜(译) 原著: Introduction to Java Progrmming(Eighth Edition) -----Y.Daniel ...

  3. java Vamei快速教程17 多线程

    作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 多线程 多线程(multiple thread)是计算机实现多任务并行处理的一种方 ...

  4. Java探索之旅(16)——异常处理

    1.异常与异常处理 在<java编程思想>中这样定义 异常:阻止当前方法或作用域继续执行的问题.虽然java中有异常处理机制,但是要明确一点,决不应该用"正常"的态度来 ...

  5. Java探索之旅(15)——包装类和字符类

    1.包装类 ❶出于对性能的考虑,并不把基本数据类型作为对象使用,因为适用对象需要额外的系统花销.但是某些Java方法,需要对象作为参数,例如数组线性表ArrayList.add(Object).Jav ...

  6. Java探索之旅(14)——文本I/O与读写

    1文件类File    ❶封装文件或路径的属性.不包括创建和读写文件操作.File实例并不会实际创建文件.不论文件存在与否,可以创建任意文件名的实例.两种实例创建方式如下:               ...

  7. Java探索之旅(13)——字符串类String

    1.初始化 String类是Java预定义类,非基本类型而是引用类型. public class StudyString { public static void main(String[] args ...

  8. Java探索之旅(12)——equals方法及其覆盖

    1.Object中的equals方法 java中的的基本数据类型:byte,short,char,int,long,float,double,boolean.==比较的是值. ❶作用:对于复合类型来说 ...

  9. Java探索之旅(11)——抽象类与接口

    1.Java数据类型       ❶不可变类,是指当创建了这个类的实例后,就不允许修改它的属性值. 它包括:         Primitive变量:boolean,byte, char, doubl ...

随机推荐

  1. simple--factory--abstract

    <?php /* 示例2: */ //简单工厂模式 /* * 定义运算类 */ abstract class Operation { protected $_NumberA = 0; prote ...

  2. iOS 动态修改导航栏颜色 UINavigationBar

    示例 所谓动态修改  意思是 在当前页面滚动的过程中 亦或 是在 触发返回事件\进入一个新的页面  导航栏的动态变化 由于系统级别的navBar 高度集成  很多自己想实现的功能 很不好弄 如果是通过 ...

  3. python调用java jython

    环境:openjdk8,python2.7,jython2.7jython下载地址     http://www.jython.org/downloads.html 下载完成后,运行下面命令 java ...

  4. path.join()和path.resolve()区别

    一.区别 1.path.join() 方法使用平台特定的分隔符作为定界符将所有给定的 path 片段连接在一起,然后规范化生成的路径. 2.path.resolve() 方法将路径或路径片段的序列解析 ...

  5. <linux是怎么跑的?>傻瓜视角看linux引导启动过程

    每天开机关机,除了“等”之外,你得了解你的操作系统开机的时候真正做了什么? 一. 书上都是这么讲的 CPU自身初始化:硬件初始工作,以PC/IP寄存器跳转到BIOS首地址为结束标志. ->加电自 ...

  6. Qt之界面实现技巧-- 窗体显示,绘制背景,圆角,QSS样式

    转自 --> http://blog.sina.com.cn/s/blog_a6fb6cc90101dech.html 总结一下,在开发Qt的过程中的一些技巧!可遇而不可求... 一.主界面 1 ...

  7. php函数decbin

    decbin()将十进制转换为二进制.必须有一个十进制参数.

  8. adb命令(一)

    针对移动端 Android 的测试, adb 命令是很重要的一个点,必须将常用的 adb 命令熟记于心, 将会为 Android 测试带来很大的方便,其中很多命令将会用于自动化测试的脚本当中. And ...

  9. tensorflow中常量(constant)、变量(Variable)、占位符(placeholder)和张量类型转换reshape()

    常量 constant tf.constant()函数定义: def constant(value, dtype=None, shape=None, name="Const", v ...

  10. Convolutional Neural Networks for Visual Recognition 8

    Convolutional Neural Networks (CNNs / ConvNets) 前面做了如此漫长的铺垫,现在终于来到了课程的重点.Convolutional Neural Networ ...