码迷,mamicode.com
首页 > 编程语言 > 详细

线程(Thread)和异常

时间:2018-01-13 23:39:45      阅读:263      评论:0      收藏:0      [点我收藏+]

标签:存在   效率   顺序   fbo   进程   特殊   放弃   逻辑错误   线程并发   

线程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

 

 

异常

技术分享图片

 

 

  异常的继承结构:基类为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语句直接返回。

 

 

线程(Thread)和异常

标签:存在   效率   顺序   fbo   进程   特殊   放弃   逻辑错误   线程并发   

原文地址:https://www.cnblogs.com/ghq120/p/8280693.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!