Java基础读书笔记(十一)——多线程

多线程

多线程就是同时存在N个执行体,按几条不同的执行线索共同工作的情况。

1. 线程

线程是可由系统调度的一个最简单的代码单元,负责执行包含在进程的地址空间中的程序代码。

Java语言中提供了一个垃圾收集线程,自动回收动态分配的内存

多线程多进程本质区别在于每个进程有它自己的变量的完备集,线程则共享相同的数据。

- 用多线程技术的主要原因:

(1)可以编写一个交互程序。是指不至于总是等待用户的响应而无所事事。

(2)如果把程序分成若干个线程,这些程序可能更容易编写。例如,客户/服务器(C/S)模式的服务器程序。对于来自客户端的每个请求,如果服务器程序能够调度一个新的线程处理每一个请求,处理方式将会非常方便。

(3)某些程序特别适合于并行计算处理,按线程方式编写这样的程序将会更顺理成章。

- 线程与进程的关系

“进程”(process)是指一种“自包容”的运行程序,有自己的地址空间。

“多任务”操作系统能同时运行多个进程,但实际是由于CPU分时机制的作用,是每个进程都能循环获得自己的CPU时间片。但由于轮换速度超快,看起来就像是“同时”运行。“线程”是进程内部单一的一个顺序控制流。因此,一个进程可能容纳了多个同时执行的线程。


2. 线程的编写

Java的线程是通过Java.lang.Thread类来实现的。当生成一个Thread类的对象之后,一个新的线程就产生了。

Thread最重要的方法是run()方法,需要将该run()重载并将线程执行体部分加入该方法内,这样,run()里的代码就能够与程序里的其他线程“同时”执行了。

Thread包含了一个特殊的方法,叫做start()方法,它的作用是对线程进行特殊的初始化,然后调用run()。整个步骤包括:

(1)调用构造方法来初始化对象;(2)用start()配置线程; (3)调用run().

- 主线程

当Java程序启动时,立刻运行的那个线程,称为主线程(main thread)。

主线程的作用:

(1)主线程是产生其他子线程的线程。

(2)通常情况,主线程必须最后完成执行,因为它需要执行各种线程的关闭操作。

尽管主线程在程序启动时自动创建,但其可以由一个Thread对象控制,因此,程序员必须调用方法currentThread()获得它的一个引用,currentThread()是Thread类的公有静态成员。其通常形式如下:

 static Thread currentThread()
 该方法返回一个调用它的线程引用。一旦获得了主线程的引用,就可以想控制其他线程那样控制主线程。

实例

public class CurrentThreadEg {
    public static void main(String[] args) {
        Thread t = Thread.currentThread();
        System.out.println("Current thread: " +t);

        t.setName("My Thread");   //改变线程名
        System.out.println("After name change :" + t);
        try {
            for(int n =5;n > 0;n--){
                System.out.print(n);
                Thread.sleep(1000);    
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果:
这里写图片描述

注意:t作为语句print()中的参数运行时输出的产生,该显示顺序是:线程名城、优先级、组的名称

sleep()方法

第一种形式:按照毫秒级的时间只是是线程从被调用到挂起。

 static void sleep(long milliseconds) throws InterruptedException

第二种形式:该方法运行用户指定时间是以毫秒还是纳秒为周期。

  static void sleep(long milliseconds ,int nanoseconds) throws InterruptedException
  仅当运行以毫秒为时间周期时可用。

- 线程的实现方法

1)继承Thread

这表示调用Thread子类对象的run方法,new Thread(){}表示一个Thread的匿名子类的实例对象,子类加上run方法后的代码如下:

new Thread(){
   public void run(){

   }
  }.start();
2)实现Runnable接口

这表示调用Thread对象接受的Runnable对象的run方法,new Runnable(){}表示一个Runnable的匿名子类的实例对象,runnable的子类加上run方法后的代码如下:

  new Thread(new Runnable(){
        public void run(){

        }
    }).start();

建议:如果不重载Thread的其他方法,最好只实现Runnable接口。但与Thread子类相比较,实现Runnable接口的子类不便之处在于:因为在Runnable实例中不存在Thread对象,所以Runnable实例不知道也不能直接调用Thread中的方法。

- Thread类的相关方法

 run():线程的入口点
 start():通过调用运行方法来启动线程
 destroy():终止一个线程,不清除其他相关内容
 resume():重新开始执行该线程
 sleep(long millis):在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)。该线程不丢失任何监视器的所属权
 jion():等待一个线程终止

- 线程的生命周期

1)就绪。线程做好了运行准备并等待CPU,即线程已被创建但尚未执行(start()尚未被调用)。

2)运行。线程在CPU上执行。

3)等待。线程在等待发生某个事件。

4)休眠。线程已被告知要休眠一段时间。

5)阻塞。线程在等待I/O结束,线程不会被分配CPU时间,无法执行。

6)死亡。线程被终止,正常情况下run()返回使得线程死亡。调用stop()和destroy()亦有同样效果,但是不被推荐,前者会产生异常,后者是强制终止,不会释放锁。
这里写图片描述

- 线程的优先级

若有多个线程都处于可运行状态,具有较高优先级的线程将先执行。只有当高优先级的线程由于stop()、yield()和等待I/O而处于不可运行状态时,低优先级的线程才可运行。

Java线程的优先级范围是从MIN_PRIORITY到MAX_PRIORITY。其中的MIN_PRIORITY和MAX_PRIORITY都是线程类的整型常量。优先级的值越大,线程的级别高,被执行的机会也就越大。一般来说,MIN_PRIORITY的值为1,MAX_PRIORITY的值为10,缺省时,线程的优先级设置为Thread.NORM_PRIORITY(常数值5)


3. 线程同步

线程同步指多个线程同时访问某资源时,采用一系列的机制以保证同时最多只能有一个线程访问该资源。

- 使用synchronized同步线程

所有使用了synchronized方法的对象 都是一个监控器,监视器每次只允许对象的一个线程运行synchronized方法。

正在运行的synchronized方法的线程能够确定它已不能继续执行,所以线程主动调用wait方法,退出对处理器的竞争,同时也退出对监视器对象的竞争。

注意:每次只能有一个线程可以对对象上锁。wait、notify和notifyAll方法可被Object类的所有子类继承,因此,任何对象都有可能是监控器。此处应注意以下三点:

1)等待状态的监控器对象的线程必须由notify(或interrupt)方法唤醒,否则它就永远地等待下去,这便导致一个死锁。

2)调用wait方法和调用notify方法应该成对出现,这样最终所有等待状态的线程才能结束等待。调用notifyAll方法是一个更为保险的方法。

3)使用synchronized方法产生的锁可能会因为没有开锁信号而成为死锁,当这类例外发生时,Java的例外机制便与Java同步机制合作,释放相应地同步锁,避免死锁的发生。

- 为何不推荐使用stop()和suspend()方法

反对使用stop(),是因为它不安全。它会解除由线程获取的所有锁定,而且如果对象处于一种不连贯状态,那么其他线程能在那种状态下检查和修改它们。结果很难检查出真正的问题所在。

suspend()方法容易发生死锁。调用suspend()的时候,目标线程会停下来,但却仍然持有在这之前获得的锁定。此时,其他任何线程都不能访问锁定的资源,除非被”挂起”的线程恢复运行。对任何线程来说,如果它们想恢复目标线程,同时又试图使用任何一个锁定的资源,就会造成死锁。所以不应该使用suspend(),而应在自己的Thread类中置入一个标志,指出线程应该活动还是挂起。若标志指出线程应该挂起,便用wait()命其进入等待状态。若标志指出线程应当恢复,则用一个notify()重新启动线程。

- 线程同步和异步的异同

如果数据将在线程间共享。例如正在写的数据以后可能被另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就是共享数据,必须进行同步存取

当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往更有效率。

4. 线程组

线程组的作用是把若干相关的线程合并到一起执行,作为一个整体统一进行控制。

创建线程组:

  private ThreadGroup mygroup;
  mygroup = Thread.currentThread().getThreadGroup();

 ThreadGroup group = new ThreadGroup(groupName);
 Thread t1 = new Thread(ThreadGroup g,Runnable r1);
 Thread t1 = new Thread(ThreadGroup g,Runnable r2);

例: group.interrupt()可中断该组所有线程

猜你喜欢

转载自blog.csdn.net/YOLO97/article/details/81879594