线程
1.什么是线程
在java核心技术卷一第10版中给出:一个程序执行多个任务,通常,每个任务称为一个线程(thread)。可以同时运行一个以上线程的程序称为多线程程序。那么多线程和多进程有什么区别:本质区别是进程都有自己的一套变量。而线程则是数据共享的。
Thread:构造一个线程,用于调用给定目标的run()方法。
void start():启动这个线程,将引发调用run()方法。这个方法将立即返回,并创建一个新的线程将并发运行。
void run():创建线程过后必须重写这个方法,并在这个方法中提供所要执行的任务指令。
2.创建线程
我们创建线程一般有两个方法
-
-
-
-
继承Thread类,实现run方法。
-
实现Runnable接口。Thread r = new Thread(接口的实现类对象);
-
-
-
3.中断线程
-
-
-
-
当线程中的run方法执行完方法体中的最后一条语句,并返回。将正常结束线程。
-
当run方法中发生了异常并且没有捕获是,线程也将终止。
-
-
-
在java的早期版本中有一个stop方法会终止线程。但是这个方法已经被抛弃了。该方法结束所有的为结束的方法,包括stop方法。当线程被终止,立即释放被他锁住的所有对象的锁。这会导致对象处于不一致的状态。例如一个账户对象向另一个账户对象转账,钱已经转出了,但是目标对象不见了。当一个线程终止另一个线程时无法知道什么时候调用stop是安全的。
想要终止一个方法,我们可以用interrupt方法来请求终止线程。
interrupt():向线程发送中断的请求,线程的中断状态将会被设置为true。如果目前该线程被一个sleep调用了阻塞,那么就会抛出InterrtptException异常。
interruptde()测试当前线程是否被终止。不像静态的中断方法,这一调用不改变线程的中断状态。
currentThread()返回当前执行线程的Treahd对象。
4.线程的状态
看别人的博客发现被人分为很多种,java核心技术中提到,线程的状态有6种,分别是New(新建)、Runnable(可运行)、Blocked(被阻塞)、Waiting(等待)、Timed waiting(计时等待)、Terminated(被终止);
-
-
New(新建): 当使用new操作符创建一个线程时,那么这个线程就处于新建状态。但是还没有开始运行。
-
Runnable(可运行):当使用了start()方法线程处于runnable状态。一个可运行状态的线程可能运行可可能还没有运行,这取决于线程调度器是否给该线程分配资源。
-
Blocked(被阻塞):当一个线程试图获取一个对象锁,而该锁被其他线程锁持有,则该线程进入阻塞状态,当其他线程释放锁,并且线程调度器运行它持有的时候,该线程将会变成一个非阻塞状态。
-
Waiting(等待):当线程等待另一个线程通知调度器一个条件是,它自己进入等待状态,调用Object.wait()或者Thread.join()方法时。
-
Timed waiting(计时等待):有一个方法有一个超时参数。调用他们导致线程进入计时等待状态。比如sleep方法。
-
Terminated(被终止):两个原因一个是执行完run。第二个原因是在run中没有捕获一个异常导致线程意外死亡。
-
join():等待终止指定的线程
getState():获取线程的状态
5.线程的优先级
在java中每一个线程都有自己的优先级。优先级别为int型的 1到10.默认优先级为5。每当线程调度器有机会没线程分配时间片的时候。会优先给级别高的线程分配。注意,如果几个高优先级的线程一直处于可运行状态,那么低优先级的线程很可能可一直无法运行。
setPriority(int n)设置优先级
yield():让当前线程处于让步状态。如果有其他可运行线程具有至少与线程同样高的优先级,那么这些线程接下来会被调度。
6.线程分类
线程一般可以分为用户线程或者守护线程。用户线程就是普通创建的线程。而守护线程通过setDaemon(true)来设置。
当只剩下守护线程的时候jvm就会推出。
7.未捕获异常处理器
线程run方法不能抛出任何受查异常,但是非受查异常会导致线程终止。在这种情况下,线程就死亡了。
但是不需要任何catch子句来处理可以被传播的异常。相反,就在线程死亡之前,异常被传递到一个用于未捕获异常的处理器。
我们可以为任何线程安装一个处理器使用(setUncaughtExceptionHandler()方法为线程设置。)。如果不安装,处理器就为null。此时处理器就是改该线程的ThreadGroup(线程组)对象。
线程组是一个可以统一管理的线程集合。创建的所有线程属于相同的线程组,但是,也可能会建立其他的组。现在引入了更好特性用于线程集合的操作,所以建议不要为自己的程序创建线程组
8.同步
当多个线程对统一数据进行操作的时候,数据就可能出现问题。线程一从内存中取出一个数据i,i=0,对其进行i++操作。此时的i=1.但是还没有将它写入到内存中。线程二进来了,取出一个数据i,i=0; 对其进行i--操作。然后把他放入内存i=-1。此时线程一又可以执行了,把i=1放入了内存中,覆盖了i=-1.我们的预期是i加了一次,又减了一次。最后为0。而两个线程同时对他操作的时候,结果可能就为1了。所以这样的方式是很不安全的。所有就提出了同步。
9.锁对象
我们不希望多个线程同时执行共享代码块。可以使用锁对象。在共享代码块执行之前使用一个(Lock lock = new ReentrantLock())对象的lock()方法,用于添加锁。在finally中使用nulock()释放锁。使用这种方式可以让共享代码块一段时间只让一个线程使用。当一个线程unlock()后,另一个线程在可以lock。这些的前提是多个线程必须使用同一个锁对象。
线程中每一次调用lock都需要nulock来释放锁。当然他也可以嵌套,在被一个所对象保护的代码中调用另一个使用相同锁的方法。
10.条件对象(条件变量)
线程进入代码共享区,却发现需要满足一个条件在可以继续执行。要使用一个条件对象来管理哪些已经获得了一个锁对象但是却不能做有用工作的线程。当我们需要向一个账户取钱的时候发现余额不够。这时候就需要其他线程想账户中汇钱。但是当前所对象还没有释放,其他线程还不能执行。这就是我们问什么需要条件对象的原因。一个锁对象课可以有一个或者多个相关的条件对象。你可以使用newCondition方法来获取一个条件对象。习惯上给每一个条件对象表达“余额充足”的条件。
使用await()。当前线程被阻塞了。并且放弃了锁,我们希望这样可以使得另一个线程可以进行增加余额的操作。
等待获得锁的线程和调用await()方法的线程存在本质上的不同。一旦一个线程调用await方法他进入该条件调用同一条件的signalAll方法时为止。当另一个线程转账时,他应该调用signalAll()这一调用重新激活了因为这一条件而等待的所有线程。
-
-
-
锁是用来保护共享代码块,任何时刻只能有一个线程执行被保护的代码块。
-
锁可以管理试图进入保护代码块的线程
-
锁可以拥有一个或者多个条件对象。
-
每一个条件对象管理那些已经进入被保护的代码块但是还不能运行的线程。
-
-
11.synchronized关键字
java中每一个对象都有一个内部锁。如果一个方法用synchronizeed关键字声明,那么对象的锁将保护整个方法。也就是说要调用该方法,线程必须获得内部的对象锁。
下面两个方法等价。
public synchronized void method(){
...
}
public void method(){
this.intrinasicLock.lock();
try{
...
}finally{
this.intrinsicLock.unlock();
}
}
而对于条件对象。await(),signalAll()和Object对象的wait(),notifAll()等价
我们发现使用synchronized关键字更加简洁。当然,你必须了解每一个对象有一个内部锁,并且该锁有一个内部条件。由锁来管理那些试图进入synchronized方法的线程,由条件来管理那些调用wait的线程
notifyAll()解除那些在该对象上调用wait方法的线程的阻塞状态。
notify()随机选择一个在该对象上调用wait()方法的线程,解除其阻塞状态。该方法只能在一个同步方法和同步方法快中调用。
wait()线程处于等待状态。
12.同步阻塞
法获得锁。还有另一种机制可以获得锁,通过进入一个同步阻塞。
synchronized(obje){....}。于是他获得了obje锁,objec可以是任意类对象
13.监视器
监视器可以让程序员不需要考虑如何加锁。他有以下特性
-
-
-
监视器类的对象有一个相关的锁。
-
使用该锁对所有的方法进行加锁,如果调用obj.method(),那么obj对象的锁是在方法调用开始自动获取的,并且当前方法返回时自动释放。因为所有的域都是私有的,这样的安排可以确保一个线程在对对象操作时,没有其他线程能访问该域。
-
该锁可以有任意个相关条件。
-
-
14.Volatile域
volatile关键字为实例域的同步访问提供了一种免锁的机制。如果声明一个volatile,那么编译器和虚拟机就知道该域是可能被另一个线程并发更新的。
例如假定一个对象有一个布尔标记done,它的值被一个线程设置却被另一个线程查询:
private boolean done;
public synchronized boolean idDone(){return done;}
public synchronized boolean setDone(){done = false;}
如果另一个线程对该对象加了锁,那么这两个方法将会阻塞。这种情况下我们就使用volatile来修饰变量,它将会使用一个独立的锁。