zoukankan      html  css  js  c++  java
  • 线程(Thread)和异常

    线程Thread

    实现多线程有两种方式:

      1、继承Thread类(本质也是实现Runnable接口的一个实例)

      Thread类源码

    public class Thread implements Runnable {}

     

    定义一个线程

    public class MyThreadextends Thread{
        public void run(){     //重写run方法
        }
    }
    MyThread thread1 = new MyThread();
    thread1.start();

     

      启动线程唯一的方法就是通过Thread类的start()实例方法。start()方法是一个native方法,它将启动一个新线程,并执行run()方法。

      2、实现Runnable接口

      如果一个类已经extends另一个类,就无法直接extends Thread,此时,必须实现一个Runnable接口

    public class MyThread extends otherclass implements Runnable{
        public void run(){     //重写run方法
        }
    }

     

    启动线程,需要首先实例化一个Thread,并传入自己的Thread实例

    MyThread mythread = new MyThread();

    Thread thread  = new Thread(mythread);

    thread.start();

    区别

      继承Thread类,来实现多线程,相当于拿出三件事,分别交给三个线程来做。因为MyThread继承Thread,所以在new MyThread的时候在创建三个对象的同时创建了三个线程。

      实现Runnable的,相当于拿出一件事让三个线程去做, new MyThread 相当于创建一个任务,然后实例化三个Thread。多线程的实例变量也是共享的,故而实现Runnable接口适合于资源共享。

     

    解析

    (1)start()方法

      用start方法来启动线程,是真正实现了多线程,通过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法(run()方法是开始执行的点)。但要注意的是,此时无需等待run()方法执行完毕,即可继续执行下面的代码。所以run()方法并没有实现多线程。

    (2)run()方法

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

    (3)线程可以设置优先级大小:thread1.setPriority();

    (4)Thread类是在java.lang包中定义的。一个类只要继承了Thread类同时重写了本类中的run方法就可以实现多线程操作,但是一个类只能继承一个父类。

      Runnable接口避免继承的局限,一个类可以继承多个接口。适用于资源的共享。

    (5)除了Thread、Runnable还有Callable等方式来实现线程。

    (6)wait()方法必须进行try catch异常捕捉,线程抛出的错误一般与InterruptedException有关。

    (7)将一个线程设置成daemon(后台线程),意味着主线程结束,后台线程自动结束。

    (8)线程停止有三种方法:①调用stop()方法②线程执行完成③异常抛出。

     

    守护线程(Daemon Thread

      守护线程也称“服务进程”、“后台线程”,是指在程序运行时在后台提供一种通用服务的线程。如果用户线程已经全部退出运行,只剩下守护线程存在了,JVM也就退出了。

      守护线程一般具有较低的优先级,用户也可以自己设置守护线程,在调用start()方法启动线程之前调用对象的setDaemon(true)方法,设置为false,则表示的是用户进程模式。当一个守护线程中产生了其他线程,这些新的线程还是守护线程。

      典型的例子就是垃圾回收器

     

    线程同步

    (1)同步方法

             用synchronized关键字修饰方法。由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。

    (2)同步代码块

             用synchronized关键字修饰的语句块,语句块会自动被加上内置锁,从而实现同步。

             同步是一种高开销的操作,因此应该尽量减少同步的内容。

    (3)wait和notify

             wait():使线程处理等待状态,并且释放所持有的对象的lock。

    (4)使用特殊变量(volatile)实现线程同步

    (5)使用重入锁实现线程同步

    (6)使用局部变量实现线程同步

    (7)使用阻塞队列实现线程同步

     

    volatile 与 synchronized

      volatile主要用在多个线程感知实例变量被更改了场合,从而使得各个线程获得最新的值。它强制线程每次从主内存中讲到变量,而不是从线程的私有内存中读取变量,从而保证了数据的可见性。

    比较

      ①volatile轻量级,只能修饰变量。synchronized重量级,还可修饰方法。

      ②volatile只能保证数据的可见性,不能用来同步,因为多个线程并发访问volatile修饰的变量不会阻塞。

      synchronized不仅保证可见性,而且还保证原子性,因为,只有获得了锁的线程才能进入临界区,从而保证临界区中的所有语句都全部执行。多个线程争抢synchronized锁对象时,会出现阻塞。

    线程安全性

      线程安全性包括两个方面:①可见性;②原子性。

      从上面自增的例子中可以看出:仅仅使用volatile并不能保证线程安全性。而synchronized则可实现线程的安全性。

     

    线程之间状态转换

      线程从新建到结束一般有5个状态,为新建、可运行、运行、阻塞、结束,其中阻塞不是必经状态!

     

    1. 新建(new):新创建了一个线程对象。

    2. 可运行(runnable):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权。

    3. 运行(running):可运行状态(runnable)的线程获得了cpu 时间片(timeslice),执行程序代码。

    4. 阻塞(block):阻塞状态是指线程因为某种原因放弃了cpu 使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得cpu timeslice 转到运行(running)状态。阻塞的情况分三种:

      (一). 等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放入等待队列(waitting queue)中。

      (二). 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。

      (三). 其他阻塞:运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。

    5. 死亡(dead):线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。

     

    sleep()和wait()

      sleep()是线程类(Thread)的方法,导致此线程暂停执行指定时间,给执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用sleep不会释放对象锁。

      wait()是Object类的方法,对此对象调用wait方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入就绪状态

    区别:

      sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常。

      sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。

     

      Condition是在java 1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition1的await()、signal()这种方式实现线程间协作更加安全和高效。因此通常来说比较推荐使用Condition,阻塞队列实际上是使用了Condition来模拟线程间协作。

      Condition是个接口,基本的方法就是await()和signal()方法;

     

    死锁

    死锁产生的原因及必要条件

        产生死锁的原因主要是:

      (1)因为系统资源不足。

      (2)进程运行推进的顺序不合适。

      (3)资源分配不当等。

      如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。其次,进程运行推进顺序与速度不同,也可能产生死锁。

        产生死锁的四个必要条件:

      (1)互斥条件:一个资源每次只能被一个进程使用。

      (2)请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。

      (3)不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。

      (4)循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

      这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。

     

    进程、线程、纤程

             进程通常被定义为一个正在运行的程序的实例,是具有一定独立功能的程序关于某个数据集合上的依次运动活动,是系统进行资源分配和调度的一个独立单位。

             线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属于一个进程的其他线程共享进程所拥有的全部资源。

             纤程是以用户方式代码来实现的,内核并不知道纤程,并且他们是根据用户定义的算法来调度的。由于定义了纤程的调度算法,因此,就内核而言,纤程采用非抢占式调度的方式。

    (1)关系

             一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行。相对进程而言,线程是一个更加接近于执行体的概念,它可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。

    (2)区别

      进程和线程的主要差别在于它们是不同的操作系统资源管理方式。

      进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。

      线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

        a. 一个程序至少有一个进程,一个进程至少有一个线程。

        b. 线程的划分尺度小于进程,使得多线程程序的并发性高。

        c. 进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。

        d. 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

        e. 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。

    线程安全和线程不安全

      线程安全就是多线程访问的时候,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可以使用,不会出现数据不一致或者数据污染。Vector、HashTable、StringBuffer

      线程不安全就是不提供数据的访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。

       线程安全的问题就是由全局变量以及静态变量引起的,若每个线程中对全局变量,静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时进行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

      举例:开启1000个线程分别向ArrayList中添加元素,每个对象添加100个元素,最终size应该为100000,结果会发现size会小于100000

     

     

    异常

     

     

      Java是通过面向对象的方法进行异常处理,把各种不同的异常进行分类,并提供了良好的接口。在Java中,每一个异常都是一个对象,它是Throwable类或者其他子类的实例。

    当一个方法出现异常后便抛出一个异常对象,该对象中包含异常信息,调用这个对象的方法可以捕获到这个异常并进行处理。

     

     

      异常的继承结构:基类为Throwable,Error和Exception继承Throwable,RuntimeException和IOException等继承Exception。

        非RuntimeException一般是外部错误(非Error),其必须被 try{}catch语句块所捕获

        手动创建一个异常需要写new exception

      运行异常,可以通过java虚拟机来自行处理。非运行异常,我们应该捕获或者抛出。

      throw关键字用于抛出异常

      throws关键字可以在方法上声明该方法要抛出的异常,然后在方法内部通过throw抛出异常对象。

      运行时异常:都是RuntimeException类及其子类异常,如NullPointerException、IndexOutOfBoundsException等,这些异常是不检查异常,程序可以选择捕获处理,也可以不处理,一般都是由逻辑错误引起的。

      非运行时异常:是RuntimeException以外的异常,类型上都属于Exception类及其子类。必须进行异常处理,不处理,程序不能编译通过。

     

    finally

        两种情况下finally语句是不会被执行的:

    (1)try语句没有被执行到,如在try语句之前就返回了,这样finally语句就不会执行,这也说明了finally语句被执行的必要而非充分条件是:相应的try语句一定被执行到。

    (2)在try块中有System.exit(0);这样的语句,System.exit(0);是终止Java虚拟机JVM的,连JVM都停止了,所有都结束了,当然finally语句也不会被执行到。

      1、finally语句是在try的return语句执行之后,return返回之前执行。

      2、finally块中的return语句会覆盖try块中的return返回。

      3、如果finally语句中没有return语句覆盖返回值,那么原来的返回值可能因为finally里的修改而改变也可能不变。

      4、finally语句先于 return 和 throw语句执行。

    总结

      finally块的语句在try或catch中的return语句执行之后返回之前执行且finally里的修改语句可能影响也可能不影响try或catch中 return已经确定的返回值,若finally里也有return语句则覆盖try或catch中的return语句直接返回。

     

     

  • 相关阅读:
    627. Swap Salary
    176. Second Highest Salary
    596. Classes More Than 5 Students
    183. Customers Who Never Order
    181. Employees Earning More Than Their Managers
    182. Duplicate Emails
    175. Combine Two Tables
    620. Not Boring Movies
    595. Big Countries
    HDU 6034 Balala Power! (贪心+坑题)
  • 原文地址:https://www.cnblogs.com/ghq120/p/8280693.html
Copyright © 2011-2022 走看看