线程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语句直接返回。