多线程.docx
《多线程.docx》由会员分享,可在线阅读,更多相关《多线程.docx(61页珍藏版)》请在冰豆网上搜索。
多线程
线程
线程状态
1.新建状态(New):
新创建了一个线程对象。
2.就绪状态(Runnable):
线程对象创建后,其他线程调用了该对象的start()方法。
该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
3.运行状态(Running):
就绪状态的线程获取了CPU,执行程序代码。
4.阻塞状态(Blocked):
阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。
直到线程进入就绪状态,才有机会转到运行状态。
阻塞的情况分三种:
(一)、等待阻塞(WAITING):
运行的线程执行wait()方法,JVM会把该线程放入等待池中。
join()不带超时时间也会进入WAITING状态。
(二)、同步阻塞(BLOCKED):
运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
线程被notify或notifyAll唤醒以后也会进入BLOCKED状态等待对象锁。
(三)、其他阻塞(TIMED-WAITING):
运行的线程执行sleep()或带有超时时间的join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。
当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5.死亡状态(Dead):
线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
如:
sleep阻塞时,用jstack看到状态如下:
被synchronized阻塞的线程状态如下:
创建线程用Thread的子类或者实现Runnable接口的类。
停止线程
stop(已废弃)
停止线程有stop方法,不过不要使用,太暴力,容易出现数据不一致等问题。
interrupt中断
中断线程通过调用线程的interrupt方法。
如果线程是RUNNABLE状态,则会对线程设置一个中断标志,然后实际的中断还是要程序中通过标志来判断自行处理。
如:
publicstaticvoidmain(String[]args)throwsInterruptedException{
Threadt1=newThread(){
@Override
publicvoidrun(){
while(true){
Thread.yield();
}
}
};
t1.start();
Thread.sleep(2000);
t1.interrupt();
}
这里执行了t1.interrupt();并不会让t1线程中断。
改成如下即可:
publicstaticvoidmain(String[]args)throwsInterruptedException{
Threadt1=newThread(){
@Override
publicvoidrun(){
while(true){
System.out.println("kkk");
Thread.yield();
if(this.isInterrupted()){
break;
}
}
}
};
t1.start();
Thread.sleep(2000);
t1.interrupt();
}
如果线程正在WAITING、TIME-WAITING状态,收到interrupt则会抛出InterruptedException异常,不会设置标志位。
而在BLOCKED状态则要区分,普通BLOCKED状态不会抛异常,如果是等待ReentrantLock的lockInterruptibly锁,则会抛出异常
状态切换
wait、notify、notifyAll
wait、notify、notifyAll是对象上的方法,一个线程调用了某个对象wait方法,当前线程会进入阻塞状态(WAITING),加入到对象的阻塞队列中,一个对象的阻塞队列可能有多个线程,调用对象的notify方法会从阻塞队列中随机唤醒一个线程,唤醒的线程会进入blocked状态,等待对象的锁,notifyAll会唤醒该对象阻塞队列的所有阻塞线程。
调用wait和notify方法前必须获得对象的锁,直接调用是不行的,如:
publicclassTest3{
publicstaticvoidmain(String[]args)throwsInterruptedException{
Objecto=newObject();
o.wait();
}
}
会报错
所以一般都在synchronize中调用,改为如下即可:
publicclassTest3{
publicstaticvoidmain(String[]args)throwsInterruptedException{
Objecto=newObject();
synchronized(o){
o.wait();
}
}
}
唤醒的线程不会直接向下执行,而是进入blocked状态,等待拿到对象的锁以后才进入往下执行。
如:
publicclassTest2{
staticObjecto=newObject();
publicstaticvoidmain(String[]args)throwsInterruptedException{
Threadt1=newThread(){
@Override
publicvoidrun(){
try{
synchronized(o){
o.wait();
}
System.out.println("t1end");
}catch(InterruptedExceptione){
e.printStackTrace();
}
}
};
t1.start();
Thread.sleep(1000);
synchronized(o){
o.notify();
System.out.println("t1notified");
Thread.sleep(2000);
System.out.println("mainend");
}
}
}
输出:
t1notified
mainend
t1end
其中t1唤醒之后并没有马上执行,而是等待主线程执行完synchronize方法释放锁以后才执行。
调用wait方法会释放当前的锁,而sleep方法不会释放锁。
suspend、resume(已废弃)
suspend会挂起线程,resume恢复线程,挂起的线程状态还是RUNNABLE,如下:
由于挂起线程代码一般是在本线程中执行,而线程挂起之后就能通过其他线程来执行恢复本线程的代码,而线程间的先后顺序往往不能保证,很容易导致resume先于suspend执行,这样挂起的线程就无法恢复了,所以此方式已被废弃使用。
join、yield
join等待一个线程执行完,本线程再执行,用于一个线程依赖另一个线程的执行结果的情况,如:
publicclassTest2{
staticvolatileinta=0;
publicstaticvoidmain(String[]args)throwsInterruptedException{
Threadt1=newThread(){
publicvoidrun(){
for(inti=0;i<1000000;i++){
a++;
}
};
};
t1.start();
t1.join();//等待t1线程累加完再打印a,否则打印的a可能会小于1000000
System.out.println(a);//结果打印出1000000
}
}
yield会让线程从运行状态转为就绪状态,重新争夺CPU资源,用于优先级低的线程让出CPU资源。
volatile
见”jvm.docx”
线程组
把多个线程归成一个组,起个组名,统一管理。
如:
publicclassTest3{
publicstaticvoidmain(String[]args)throwsInterruptedException{
ThreadGroupthreadGroup=newThreadGroup("threadGroup");
Threadt1=newThread(threadGroup,"t1"){
@Override
publicvoidrun(){
while(true){
try{
Thread.sleep(3000);
}catch(InterruptedExceptione){
e.printStackTrace();
}
System.out.println(this.getThreadGroup().getName()+"-"+this.getName());
}
}
};
Threadt2=newThread(threadGroup,"t2"){
@Override
publicvoidrun(){
while(true){
try{
Thread.sleep(3000);
}catch(InterruptedExceptione){
e.printStackTrace();
}
System.out.println(this.getThreadGroup().getName()+"-"+this.getName());
}
}
};
t1.start();
t2.start();
threadGroup.list();
}
}
打印:
java.lang.ThreadGroup[name=threadGroup,maxpri=10]
Thread[t1,5,threadGroup]
Thread[t2,5,threadGroup]
threadGroup-t1
threadGroup-t2
threadGroup-t1
threadGroup-t2
……
守护线程
当一个Java应用内,所有非守护进程都结束时,Java虚拟机就会自然退出,守护线程自动结束
如:
/**
*守护线程测试
*@authorAdministrator
*
*当一个Java应用内,所有非守护进程都结束时,Java虚拟机就会自然退出,守护线程自动结束
*
*/
publicclassDaemonThreadTest{
publicstaticclassDaemonThreadextendsThread{
@Override
publicvoidrun(){
System.out.println("start");
try{
Thread.sleep(2000);
}catch(InterruptedExceptione){
e.printStackTrace();
}
System.out.println("end");//这句代码没有执行,因为主线程已经提前执行完了
}
}
publicstaticvoidmain(String[]args)throwsInterruptedException{
DaemonThreaddt=newDaemonThread();
dt.setDaemon(true);
dt.start();
Thread.sleep(1000);
}
}
输出:
start
其中dt.setDaemon(true);要先于start()方法,执行,否则抛出如下异常:
而线程会被当做非守护线程继续执行,此时打印了end。
线程优先级
优先级越高,争夺到CPU的概率越高。
/**
*测试线程优先级,优先级高的线程先执行完的概率大
*@authorAdministrator
*
*/
publicclassPriorityTest{
publicstaticclassHighextendsThread{
staticintcount=0;
@Override
publicvoidrun(){
while(true){
synchronized(PriorityTest.class){
count++;
if(count>10000000){
System.out.println("High");
break;
}
}
}
}
}
publicstaticclassLowextendsThread{
staticintcount=0;
@Override
publicvoidrun(){
while(true){
synchronized(PriorityTest.class){
count++;
if(count>10000000){
System.out.println("Low");
break;
}
}
}
}
}
publicstaticvoidmain(String[]args)throwsInterruptedException{
Highhigh=newHigh();
Lowlow=newLow();
//设置线程优先级
high.setPriority(Thread.MAX_PRIORITY);
low.setPriority(Thread.MIN_PRIORITY);
low.start();
high.start();
}
}
多次试验之后High先执行完的概率高于Low
synchronized
见”jvm.docx”
注意:
不要锁Integer等包装对象,因为它的值一旦变化,就会自动创建一个新对象。
ArrayList、HashMap的并发问题
见”jvm.docx”
Vector、ConcurrentHashMap是线程安全的
队列
概念
Queue是jdk1.5以后提供的队列接口,与List、Set平级,都是Collection的子接口
Queue队列先进先出,从队尾进,队首出。
队列的方法:
add、offer都是入列,队列满时,add抛出异常,offer返回false。
remove、poll都是出列,返回出列的值,队列空时,remove抛出异常,poll返回null
element、peek都是返回队首值,不出列,队列空时,element抛出异常,peek返回null
ps:
由于poll和peek方法出错就返回null。
因此,尽量不要向队列插入null值。
使用,如:
publicclassQueueTest{
publicstaticvoidmain(String[]args){
//add()和remove()方法在失败的时候会抛出异常(不推荐)
Queuequeue=newLinkedList();
//添加元素(入列)
queue.offer("a");//如果队列已满,返回false
queue.add("b");//如果队列已满,会抛出异常
for(Stringq:
queue){
System.out.println(q);
}
//获取第一个元素,不删除
System.out.println("peek="+queue.peek());//返回第一个元素,队列如果为空,返回null
//遍历队列
for(Stringq:
queue){
System.out.println(q);
}
//删除元素(出列)
System.out.println("poll1="+queue.poll());//返回第一个元素,并在队列中删除,如果队列为空,返回null
System.out.println("poll2="+queue.remove());//返回第一个元素,并在队列中删除,果队列为空,返回抛出异常
System.out.println("poll3="+queue.poll());//队列为空,返回null
//获取第一个元素,不删除
System.out.println("peek="+queue.peek());//返回第一个元素,队列如果为空,返回null
System.out.println("element="+queue.element());//返回第一个元素,队列如果为空,抛出异常
}
}
Queue的子类
Queue有许多实现,如:
双向队列Deque
双向队列,提供addFirst、addLast、offerFirst、offerLast等可以从队首队尾操作的方法。
线程安全队列ConcurrentLinkedQueue
采用无锁的volatile做的,效率很高
阻塞队列BlockingQueue(线程安全)
实现阻塞接口BlockingQueue的队列,如:
ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue、SynchronousQueue、DelayQueue
阻塞队列是线程安全的,内部用ReentrantLock保证,如:
阻塞接口的Queue有put入列,take出列,put时如果队列已满,会阻塞等待,take时队列如果为空,也会阻塞等待。
通常用于生产者、消费者模式。
ArrayBlockingQueue:
创建时必须指定大小。
LinkedBlockingQueue:
创建时可不指定大小,如果没有指定大小,则put时,只有容量达到Integer.MAX_VALUE才会阻塞。
/**
*阻塞队列测试
*@authorAdministrator
*
*/
publicclassQueueTest{
staticBlockingQueueblockingQueue=newArrayBlockingQueue
(2);
publicstaticvoidmain(String[]args)throwsInterruptedException{
newThread(newRunnable(){
@Override
publicvoidrun(){
try{
Thread.sleep(2000);//新线程启动后先sleep2秒。
blockingQueue.put(123);
System.out.println("branchthreadend");
}catch(Exceptione){
e.printStackTrace();
}
}
}).start();
//take空队列会阻塞当前线程,等待队列put完后才继续执行
blockingQueue.take();
System.out.println("mainthreadend");
}
}
输出:
branchthreadend
mainthreadend
SynchronousQueue
一种特殊的BlockingQueue,put和take必须交替执行,其中每个put必须等待一个take,反之亦然。
*iterator()永远返回空,因为里面没东西。
*peek()永远返回null。
*put()往queue放进去一个element以后就一直wait直到有其他thread进来把这个element取走。
*offer()往queue里放一个element后立即返回,如果碰巧这个element被另一个thread取走了,offer方法返回true,认为offer成功;否则返回false。
*offer(2000,TimeUnit.SECONDS)往queue里放一个element但是等待指定的时间后才返回,返回的逻辑和offer()方法一样。
*take()取出并且remove掉queue里的element(认为是在queue里的。
。
。
),取不到东西他会一直等。
*poll()取出并且remove掉queue里的element(认为是在queue里的。
。
。
),只有到碰巧另外一个线程正在往queue里offer数据或者put数据的时候,该方法才会取到东西。
否则立即返回null。
*poll(2000,TimeUnit.SECONDS)等待指定的时间然后取出并且remove掉queue里的element,其实就是再等其他的thread来往里塞。
*isEmpty()永远是true。
*remainingCapacity()永远是0。
*remove()和removeAll()永远是false。
优先级队列PriorityQueue
每次入列,整个队列都会重新排序,出列时按排序后的顺序出列,不能有null元素(会抛空指针异常),使用如:
/**
*优先级列队测试
*@authorAdministrator
*
*/
publicclassQueueTest{
publicstaticvoidmain(String[]args)throwsInterruptedExcepti