Java基础知识整理四(多线程编程以…

16Java多线程

实现线程的方式有两种:继承Thread类和实现Runnable接口。继承Thread类时,在run方法中若想获得当前线程的相关信息,直接调用线程的实例方法或者静态方法即可,想获得当前线程,用this即可,且这种方式创建的线程之间不能共享线程类的实例变量;实现Runnable接口时,在具体创建一个线程对象时,需要将该Runnable实现类的对象传给Thread的构造方法当做其target来启动线程,本身不能直接启动线程,在run方法中如果想获得当前线程,需要调用Thread类的静态方法currentThread(),该方法返回当前正在执行的线程对象,而且这种方式创建的多条线程可以共享Runnable实现类中的实例变量。

线程的生命周期:新建——就绪(阻塞)——运行——死亡。当创建了一个线程类的实例后,该线程就处于新建状态,此时他和其他Java对象一样,仅仅由Java虚拟机为其分配了内存,并初始化了其成员变量的值。当线程调用了start()方法之后,线程就进入就绪状态Java虚拟机就会为其创建方法调用栈和程序计数器,处于这个状态中的线程还没有执行,它只是代表了这个线程可以运行了,至于如何运行,取决于JVM里线程调度器的调度。(注意启动一个线程不要调用其run方法,也不要对已经处于启动状态或者死亡状态的线程再次调用start()方法)。如果处于就绪状态的线程获得了CPU,开始执行run方法中的线程执行体,则该线程处于运行状态。当一个线程处于运行状态之后,它一般不可能一直占用CPU,在其运行过程中需要被中断来是其他线程获得执行的机会,或者在手动让其让出CPU出来,这时线程会进入阻塞状态。一般发生如下情况,线程将会进图阻塞状态:

Ø 线程调用了sleep方法主动放弃了所占用的资源

Ø 线程调用了一个阻塞式IO方法,在该方法返回之前,该线程被阻塞

Ø 线程试图获得一个同步监视器,但该同步监视器正被其他线程所持有。

Ø 线程在等待某个通知(notify

Ø 程序调用了线程的suspend方法将该线程挂起。不过这个方法容易导致死锁,尽量不用

当正在执行的线程被阻塞之后,其他线程就可以获得执行的机会了。针对上面的几种情况,当发生如下的情况可解除上面的阻塞重新进入就绪状态:

Ø 调用sleep方法的线程经过了指定的时间。

Ø 线程调用的阻塞式IO方法已经返回

Ø 线程成功的获得了同步监视器

Ø 线程正在等待某个通知时,其他线程发出一个通知。

Ø 处于挂起状态的线程被调用了resume恢复方法。

调用sleep方法的线程会让线程进入阻塞状态,线程从阻塞状态只能进入就绪状态而无法进入运行状态调用yield方法可以让运行状态的线程转入就绪状态。线程以以下几种方式之一结束后,将处于死亡状态

Ø Run方法执行完成,线程正常结束

Ø 线程执行过程中抛出一个未捕获的异常或错误

Ø 直接调用该线程的stop方法来结束该线程,这种方法容易导致线程死锁,不推荐使用

控制线程Thread提供了一个让线程等待另一个线程完成的方法:join方法。当在某个程序执行流中调用其他线程的join方法时,调用线程将被阻塞,直到被join的线程执行完成为止。

后台线程:在后台运行的,为其他线程提供服务的线程。如JVM的垃圾回收线程。当前台线程死亡后,后台线程也随即死亡。可以调用Thread类的setDaemontrue)来将某一个线程设为后台线程。

线程睡眠:调用Thread类的sleep方法,可以让当前线程阻塞。当线程调用sleep进入阻塞状态后,在其sleep时间里,该线程不会获得执行的机会,及时CPU空闲也不会执行。等时间经过之后,线程转入就绪状态。

线程让步:调用Threadyield()方法,可以让当前线程暂停,但他不会阻塞该线程,只是将该线程转入就绪状态,暂停一下,让调度器重新调度线程。完全可能的情况是,当某个线程调用了yield方法之后,线程调度器又将其调度出来重新执行。实际上,当调用里该方法暂停以后,只有优先级与当前线程相同或者更高的就绪状态的线程才会获得执行的机会。

sleepyield的区别:

² sleep方法暂停当前线程后,会给其他线程执行机会,不会理会其他线程的优先级。但yield方法只会给优先级相同或者更高的线程执行的机会。

² sleep方法将会将线程转入阻塞状态,知道经过阻塞时间才会转图就绪状态。而yield不会将线程转入阻塞状态,只是将线程强制转入就绪状态。

² sleep方法声明抛出了InterruptedException异常,所以调用sleep方法要么捕捉该异常,要么抛出,而yield方法没有声明任何异常。

² sleep方法比yield方法有更好的可移植性,通常不要依靠yield方法来控制并发执行的线程。

改变线程的优先级:调用线程的setPriority()方法。一般不推荐直接写数值,而是设置成Thread提供的MAX/NORM/MIN_PRIORITY三个静态常量。这样具有更好的平台移植性。

线程的同步:线程同步一般有三种方法:synchronized块、synchronized方法和加lock锁。Synchronized方法只需在方法签名中加入该关键字即可,lock锁只要啊相应方法中调用lock对象的lock方法即可。二者的区别:synchronized 也有好处的,比如一些性能监控工具,可以监测到 synchronized 的锁。无法监测到 Lock 的锁定,毕竟 Lock 的锁定是通过代码实现的,而 synchronized 是在 JVM 层面上实现的。要监控工具能监测到 Lock 的锁定那还需要有一段时日。另外,synchronized 在锁定时如果方法块抛出异常,JVM 会自动将锁释放掉,不会因为出了异常没有释放锁造成线程死锁。但是 Lock 的话就享受不到 JVM 带来自动的功能,出现异常时必须在 finally 将锁释放掉,否则将会引起死锁。

线程通信Object类的wait()、notify()、notifyAll()三个方法可以实现线程间的协调通信。但这三个方法必须由同步监视器来调用。对于synchronized修饰的方法,因为该类的默认实例(this)就是同步监视器,所以可以在该方法中直接调用者三个方法;对于synchronized块,同步监视器是后面括号里的对象,所以必须使用该对象来调用这三个方法。

Ø wait():导致当前线程等待,知道其它线程调用该同步监视器的notify()方法或notifyAll()方法唤醒该线程。

Ø notify():唤醒再次同步监视器上等待的单个线程,如果所有线程都在此同步监视器上等待,则随机唤醒一个。

Ø Notifyall():唤醒在此同步监视器上等待的所有线程,只有当前线程放弃对该同步监视器的锁定后,才可以执行被唤醒的线程。

CallableFutureJava中实现线程除了以上提到的两种方式以外,还有一种比较特殊的方式:实现Callable接口并实现call()方法,call()方法作为线程执行体且执行结束后会有返回值。用这种方式创建线程的方式如下:

Ø 1、创建Callable接口实现类,并实现call方法,该call方法作为线程的执行体且有返回值。

Ø 2、创建Callable实现类的实例,使用Future接口的实现类FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象call方法的返回值。

Ø 3、将FutureTask对象作为Thread对象的target来创建线程,并启动线程。

Ø 4、调用FutureTaskget方法获得子线程执行结束后的返回值。

注意:调用FutureTask对象的get方法来返回call方法的返回值时,将导致主线程被阻塞,知道call方法结束并返回为止。

线程池:使用线程池来执行线程任务的步骤如下:

Ø 调用Executors类的静态工厂方法创建一个ExecutorService对象,给对象代表了一个线程池。

Ø 创建Runnable实现类或者Callable实现类的实例,作为线程执行任务

Ø 调用ExecutorService对象的submit方法来提交Runnable实例或者Callable实例。

Ø 当不想提交任何任务时调用ExecutorService对象的shutdown方法来关闭线程池。

 

 

17Java异常处理机制

Java异常一般分为ExceptionErrorError指不能挽回的错误,如系统崩溃、虚拟机出错、动态链接失败等,通常会导致程序崩溃,Exception是可以处理的一些“例外”状况。他们都继承自Throwable父类。Exception分为RuntimeException和非RuntimeException,分别代表Runtime异常和Checked异常。异常可以用try-catch-finally捕捉处理。一般一个try块执行一次后最多只有一个catch块执行,就是匹配异常实例成功的那个在使用try-catch-finally时要注意一下几点:

Ø 1try块中的变量是块内局部变量,这一点需要注意。如果需要一个变量在try-catch以及其他地方都能使用,则应在类内定义成员变量。

Ø 2try块执行到某行语句A处,如果出现异常,则寻找相应的catch块进行处理。如果找到catch块并处理,则程序往catch块后面执行,try块中紧跟着A处之后的语句不会再获得执行的机会。如果没有处理,则可以抛给方法调用者或者虚拟机。

Ø 3try-catch-finally之后的语句一般都会执行。即如果没有异常出现或者出现的异常被处理掉了,则finally之后的语句正常执行。如果在try中出现的异常没有被处理掉,而导致程序退出或者抛给其调用者,则异常处之后的代码都不会被执行,所以finally之后的句子也不会被执行。

Ø 4、使用finally回收物理资源。一般情况下,不论异常有没有被捕捉到,在结束try-catch-finally之前,都会执行finally块中的代码,即使在try中写了return来结束方法,也是等finally执行完成后再return。但是,如果在try或者catch块中调用System.exit1)之类的退出虚拟机的方法,则程序会立即结束,不会再理会之后的代码。所以一般不会在finally块中return或者throw,这将导致程序不会再返回到try块中来执行return等语句。

Ø 5、捕获异常信息时,先捕获小的异常,再捕获大的异常。重写方法时,子类方法抛出的异常不能比父类声明抛出更多的异常,子类声明抛出的异常应该是父类方法声明抛出的异常的子类或相等。

Ø 不要过度使用异常,要注意代码的可读性和程序的逻辑性。必要时可以自定义异常。在实际应用中,应将原始异常记录保存下来,留给系统人员,而将一些可以公开的提示信息提供给用户。

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值