资料-Java基础:多线程--线程基础

前言

  在复习java基础知识的时候,对线程等相关的知识点进行了总结和整理,便于以后记忆和学习。

一、基础概念

  学习线程之前,需要先了解一些程序、线程、进程的基础知识和联系。

  在操作系统中,线程运行在进程中,进程是系统进行资源分配和调度的基本单位,是操作系统结构的基础。

  进程:进程是一个具有独立功能的程序关于某个数据集合的一次运行活动。它可以申请和拥有系统资源,是一个动态的概念,是一个活动的实体。

     程序是指令、数据及其组织形式的描述,进程是程序的实体。

  二者区别和联系

  在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。

  进程是资源分配的最小单位,线程是程序执行的最小单位。

  进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵。而线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。

  线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。不过如何处理好同步与互斥是编写多线程程序的难点。

  多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。

二、线程应用

  建立:

  当使用一个main方法执行程序时,会自动建立一个名称为“main”的线程。main函数是java运行启动的入口,它是由一个name叫main线程调用的;如果一个线程没有专门设置名称,程序会默认的将名称设置为Thread-num,num是从0开始累加的数字。

  源代码注释中,有2种方式可以创建一个可执行线程:

    1.定义一个继承Thread类的子类.子类可覆写父类的run()方法.子类实例分配内存后可运行(非立即,取决于CPU调用)

    比如:计算大于指定值的素数的线程可以写成如下

 1 class PrimeThread extends Thread {
 2     long minPrime;
 3     PrimeThread(long minPrime) {
 4       this.minPrime = minPrime;
 5     }
 6 
 7     public void run() {
 8   // compute primes larger than minPrime
 9    . . .
10     }
11 }
View Code

 

    下面的代码将创建一个线程并启动它.

    PrimeThread p = new PrimeThread(143);

    p.start();

    2.另一个实现线程的方式就是使类实现Runnable接口.

    此类自己会实现run()方法.然后此线程会被分配内存,当线程被创建时,会传入一个参数,然后开始执行.

    此种方式的样例代码如下:

 1 class PrimeRun implements Runnable {
 2   long minPrime;
 3   PrimeRun(long minPrime) {
 4     this.minPrime = minPrime;
 5   }
 6 
 7   public void run() {
 8     // compute primes larger than minPrime
 9     . . .
10   }
11 }

 

   下面的代码能够创建一个线程并开始执行:

   PrimeRun p = new PrimeRun(143);

   new Thread(p).start();

 

  线程的状态转换:

  •  当一个线程执行了start方法后,不代表这个线程就会立即被执行,只代表这个线程处于可运行的状态,最终由OS的线程调度来决定哪个可运行状态下的线程被执行。
  • 一个线程一次被选中执行是有时间限制的,这个时间段叫做CPU的时间片,当时间片用完但线程还没有结束时,这个线程又会变为可运行状态,等待OS的再次调度;在运行的线程里执行Thread.yeild()方法同样可以使当前线程变为可运行状态。
  • 在一个运行中的线程等待用户输入、调用Thread.sleep()、调用了其他线程的join()方法,则当前线程变为阻塞状态。
  • 阻塞状态的线程用户输入完毕、sleep时间到、join的线程结束,则当前线程由阻塞状态变为可运行状态。
  • 运行中的线程调用wait方法,此线程进入等待队列。
  • 运行中的线程遇到synchronized同时没有拿到对象的锁标记、等待队列的线程wait时间到、等待队列的线程被notify方法唤醒、有其他线程调用notifyAll方法,则线程变成锁池状态。
  • 锁池状态的线程获得对象锁标记,则线程变成可运行状态。
  • 运行中的线程run方法执行完毕或main线程结束,则线程运行结束。

  说明:

  sleep()会让线程交出CPU的执行权,但是不会释放锁。

   yield()和sleep方法相似,也会交出CPU的执行权,也不会释放锁,两者之间的区别有:

    (1) sleep()会使线程进入阻塞状态,yield()不会时线程进入阻塞态而是进入可运行态,当线程重新获得CPU执行权后又可以执行。
    (2) sleep()释放CPU后其他都可以竞争CPU的执行权,而yield()只会让线程优先级大于等于自己的线程竞争CPU执行权的机会。

  join()可以保证让一个线程在另一个线程之前执行结束。如何保证一个工作在另一个工作结束之前完成,就可以使用join()方法。

  wait()可以让线程从运行态转换为阻塞态,同时还会释放线程的同步锁。

  线程的生命周期:

  当一个线程被创建之后,进入新建状态,JVM分配内存空间,并进行初始化操作。

  当线程对象调用了start()方法,该线程就处于就绪状态(可执行状态),JVM会为其创建方法调用栈、和程序计数器,处于可执行状态下的线程随时可以被CPU调度执行。CPU执行该线程的时候,该线程进入执行状态。

  执行过程中,该线程遇倒像wait()等待阻塞、以及synchronized锁同步阻塞或者调用线程的sleep()方法等进入一个阻塞状态。阻塞之后的线程通过notify()或者notifyAll()方法唤醒重新获取对象锁之后再次进入就绪状态,等待CPU执行。当线程执行完或者return则线程正常结束,如果发生处理的运行时异常,则线程因为异常而结束。这是一个线程的整个运行的生命周期的基本描述。

 

三、多线程 

   基本概念

  多线程(英语:multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。

  最开始,线程只是用于分配单个处理器的处理时间的一种工具。多线程是多个线程程序同时运行,抢占同一个cpu时间片,以执行程序的过程。

  多线程是为了同步完成多项任务,不是为了提高运行效率,而是为了提高资源使用效率来提高系统的效率。线程是在同一时间需要完成多项任务的时候实现的。

  最简单的比喻多线程就像火车的每一节车厢,而进程则是火车。车厢离开火车是无法跑动的,同理火车也不可能只有一节车厢。多线程的出现就是为了提高效率。同时它的出现也带来了一些问题。

  名词解释

  线程同步:协同、协助、互相配合,多个线程之间协调运行。在Java里面,通过synchronized进行同步的保证。

  线程阻塞:当前线程退出CPU时间片,等待其他线程运行完,或者就绪之后,等待CPU重新分给时间片才可继续运行。

  线程安全:线程安全是多线程编程时的计算机程序代码中的一个概念。在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。

  死锁:死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

 

posted @ 2019-12-12 23:33  流浪的蛋炒饭  阅读(139)  评论(0)    收藏  举报