线程基础
2016-03-13 16:07
267 查看
1、线程状态:
新生状态
用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新生状态,处于新生状态的线程有自己的内存空间,通过调用start方法进入就绪状态(rumnable)
就绪状态
处于就绪状态的线程已经具备了运行的条件,但还没有分配到cpu,处于线程就绪队列,等待系统为其分配CPU,等待状态并不是执行状态,当系统选定一个等待执行的Thread对象后,他/她就会从等待执行状态进入执行状态,系统挑选的动作称之为“CPU调度”。一旦获得CPU,线程就进入运行状态并自动调用自己的run方法。
运行状态
在运行状态的线程执行自己的run方法中代码,知道调度其他方法而终止、或等待某资源而阻塞或完成任务而死亡。如果在给定的时间片内没有执行结束,就会被系统给换下来回到等待执行状态
阻塞状态
处于运行状态的线程在某些情况下,如执行了sheep(睡眠)方法,或等待I/O设备等资源,将让出CPU并暂时停止自己的运行,进入阻塞状态,在阻塞状态的线程不能进入就绪队列,只有当引起阻塞的原因消除时,如睡眠时间已到,或等待的I/O设备空闲下来,线程便转入就绪状态,重新到就绪队列中排队等待,被系统选中后从原来停止的位置开始继续运行。
死亡状态
死亡状态是线程生命周期中的最后一个阶段,线程死亡的原因有两个。一个是正常运行的线程完成了它全部工作,另一个是线程被强制性的终止,如通过执行stop或destroy方法来终止一个线程(不推荐使用这两个方法,前者会产生异常,后者是强制终止,不会释放锁)
2、停止线程
1)自然终止:线程体正常执行完毕
2)外部干涉
1、在线程类中定义线程体使用的标识
2、线程体使用该标识
3、提供对外的方法改变该标识
4、外部根据条件调用该方法即可
3、阻塞
1) join:合并线程
2)yield:暂停自己的线程 静态方法
3) sleep:休眠,不释放锁
*与时间相关:倒计时
*模拟网络延时
4、线程的同步(并发)
1)由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题。java语言提供了专门机制以解决这种冲突,有效避免了同一个数据对象被多个线程同时访问。
2)由于我们可以通过private关键字来保证数据对象只能被方法访问,所有我们只需针对方法提出一套机制,这套机制就是synchronized关键字,它包括两种用法:synchronized方法和synchronized块。
同步:多个线程访问同一份资源 确保资源安全--->线程安全
同步块
synchronized(引用类型|this|类.class){
}
同步方法
public synchronized void test1(){
}
线程安全:保证资源安全,相对效率低
锁定范围过大:效率低下
锁定范围过小:没有保证资源安全
例1:
5、死锁
建立在同步之上,过多的同步容易造成死锁
例2:
解决死锁方法:生产者消费者模式(不是设计模式)
生产者消费者问题,也称有限缓冲问题,是一个多线程同步问题的经典案例。该问题描述了两个共享固定大小缓冲区的线程----即所谓的“生产者”和“消费者” ---在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。在此同时,消费者也在缓冲区消费这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。
要解决该问题,就必须谈生产者在缓冲区满时休眠(要么干脆就放弃数据),等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区添加数据。同样,也可以让消费者在缓冲区空时进入休眠,等到生产者往缓冲区添加数据之后,再唤醒消费者。通常常用的方法由信号灯法、管程等。如果解决方法不够完善,则容易出现死锁的情况。出现死锁时,两个线程都会陷入休眠,等待对方唤醒自己。
例3:
sleep()与wait()区别
对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。
sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。
在调用sleep()方法的过程中,线程不会释放对象锁。
而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备
获取对象锁进入运行状态。
6、任务调度
Timer 定时器类
TimerTask 任务类
通过java timer timertask :(spring的任务调度就是通过他们来实现的)
在这种实现方式中,Timer类实现的是类似闹钟的功能,也就是定时或者每个一定时间触发一次线程。其实,Timer类本身实现的就是一个线程,只是这个线程是用来实现调用其他线程的。而TimerTask类似一个抽象类,该类实现了Runnable接口,所以按照前面的介绍,该类具备多线程的能力。
在这种实现方式中,通过继承TimerTask是该类获得多线程的能力,将需要多线程执行的代码书写在run方法内部,然后通过Timer类启动线程的执行。
在实际使用时,一个Timer可以启动任意多个TimerTask实现线程,但是多个线程之间会存在阻塞。所以如果多个线程之间如果需要完全独立运行的话,最好还是一个Timer启动一个TimerTask实现。
例4:
第三方框架:quartz
juc
7、总结
1、创建线程:
继承Thread
实现Runnable
实现Callable
2、线程状态
1)新生-->start -->就绪-->运行-->阻塞-->终止
2)终止线程
3)阻塞:join yield
sleep
3、线程的信息
1)Thread.currentThread
2)
获取名称设置名称设置优先级判断状态
4、同步 ----对同一份资源
新生状态
用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新生状态,处于新生状态的线程有自己的内存空间,通过调用start方法进入就绪状态(rumnable)
就绪状态
处于就绪状态的线程已经具备了运行的条件,但还没有分配到cpu,处于线程就绪队列,等待系统为其分配CPU,等待状态并不是执行状态,当系统选定一个等待执行的Thread对象后,他/她就会从等待执行状态进入执行状态,系统挑选的动作称之为“CPU调度”。一旦获得CPU,线程就进入运行状态并自动调用自己的run方法。
运行状态
在运行状态的线程执行自己的run方法中代码,知道调度其他方法而终止、或等待某资源而阻塞或完成任务而死亡。如果在给定的时间片内没有执行结束,就会被系统给换下来回到等待执行状态
阻塞状态
处于运行状态的线程在某些情况下,如执行了sheep(睡眠)方法,或等待I/O设备等资源,将让出CPU并暂时停止自己的运行,进入阻塞状态,在阻塞状态的线程不能进入就绪队列,只有当引起阻塞的原因消除时,如睡眠时间已到,或等待的I/O设备空闲下来,线程便转入就绪状态,重新到就绪队列中排队等待,被系统选中后从原来停止的位置开始继续运行。
死亡状态
死亡状态是线程生命周期中的最后一个阶段,线程死亡的原因有两个。一个是正常运行的线程完成了它全部工作,另一个是线程被强制性的终止,如通过执行stop或destroy方法来终止一个线程(不推荐使用这两个方法,前者会产生异常,后者是强制终止,不会释放锁)
2、停止线程
1)自然终止:线程体正常执行完毕
2)外部干涉
1、在线程类中定义线程体使用的标识
2、线程体使用该标识
3、提供对外的方法改变该标识
4、外部根据条件调用该方法即可
3、阻塞
1) join:合并线程
2)yield:暂停自己的线程 静态方法
3) sleep:休眠,不释放锁
*与时间相关:倒计时
*模拟网络延时
4、线程的同步(并发)
1)由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题。java语言提供了专门机制以解决这种冲突,有效避免了同一个数据对象被多个线程同时访问。
2)由于我们可以通过private关键字来保证数据对象只能被方法访问,所有我们只需针对方法提出一套机制,这套机制就是synchronized关键字,它包括两种用法:synchronized方法和synchronized块。
同步:多个线程访问同一份资源 确保资源安全--->线程安全
同步块
synchronized(引用类型|this|类.class){
}
同步方法
public synchronized void test1(){
}
线程安全:保证资源安全,相对效率低
锁定范围过大:效率低下
锁定范围过小:没有保证资源安全
例1:
public class SynDemo1 { public static void main(String[] args) { JvmThread thread1 = new JvmThread(100); JvmThread thread2 = new JvmThread(500); thread1.start(); thread2.start(); } } class JvmThread extends Thread{ private long time; public JvmThread() { } public JvmThread(long time){ this.time = time; } @Override public void run() { System.out.println(Thread.currentThread().getName()+ "-->创建:"+Jvm.getInstance1(time)); } } /** * 单例设计模式 * 确保一个类只有一个对象 * 懒汉式(使用的创建对象): * 1、构造器私有化,避免外部直接创建对象 * 2、声明一个私有的静态变量 * 3、对外提供一个公共的静态方法访问该变量, * 如果变量没有对象,创建对象,否则直接返回 * @author qjc * * 2016-3-13 */ class Jvm{ private static Jvm instance = null; private Jvm() { } //线程不安全 public static Jvm getInstance1(long time){ if(null==instance){ try { Thread.sleep(time);//延时 放大错误 } catch (InterruptedException e) { e.printStackTrace(); } instance = new Jvm(); } return instance; } public static synchronized Jvm getInstance2(long time){ if(null==instance){ //安全 try { Thread.sleep(time);//延时 放大错误 } catch (InterruptedException e) { e.printStackTrace(); } instance = new Jvm(); } return instance; } public static Jvm getInstance3(long time){ //a b c d e都在此等待 -->效率不高 存在对象也需要等待 synchronized (Jvm.class) { //静态代码块里没有this if(null==instance){ //安全 try { Thread.sleep(time);//延时 放大错误 } catch (InterruptedException e) { e.printStackTrace(); } instance = new Jvm(); } return instance; } } //double checking 双重检查 public static Jvm getInstance4(long time){ //b c d e 直接返回对象-->效率 提供已经存在对象的访问效率 if(null==instance){ //a 创建对象 synchronized (Jvm.class) { //静态代码块里没有this if(null==instance){ //安全 try { Thread.sleep(time);//延时 放大错误 } catch (InterruptedException e) { e.printStackTrace(); } instance = new Jvm(); } } } return instance; } }
5、死锁
建立在同步之上,过多的同步容易造成死锁
例2:
/** * 过多同步容易造成死锁 * @author qjc * * 2016-3-13 */ public class SynDemo2 { public static void main(String[] args) { Object goods = new Object(); Object money = new Object(); Test t1 = new Test(goods,money); Test t2 = new Test(goods,money); Thread proxy = new Thread(t1); Thread proxy2 = new Thread(t2); proxy.start(); proxy2.start(); } } class Test implements Runnable{ Object goods; Object money; public Test(Object goods, Object money) { super(); this.goods = goods; this.money = money; } @Override public void run() { while (true) { test(); } } public void test() { synchronized (goods) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (money) { } } System.err.println("一手给钱"); } } class Test2 implements Runnable{ Object goods ; Object money ; public Test2(Object goods, Object money) { super(); this.goods = goods; this.money = money; } @Override public void run() { while (true) { test(); } } public void test() { synchronized (money) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (goods) { } } System.err.println("一手给货"); } }
解决死锁方法:生产者消费者模式(不是设计模式)
生产者消费者问题,也称有限缓冲问题,是一个多线程同步问题的经典案例。该问题描述了两个共享固定大小缓冲区的线程----即所谓的“生产者”和“消费者” ---在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。在此同时,消费者也在缓冲区消费这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。
要解决该问题,就必须谈生产者在缓冲区满时休眠(要么干脆就放弃数据),等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区添加数据。同样,也可以让消费者在缓冲区空时进入休眠,等到生产者往缓冲区添加数据之后,再唤醒消费者。通常常用的方法由信号灯法、管程等。如果解决方法不够完善,则容易出现死锁的情况。出现死锁时,两个线程都会陷入休眠,等待对方唤醒自己。
例3:
/** * 一个场景,共同的资源 * 生产者消费者模式:信号灯法 * wait():等待,释放锁 * sleep():不释放锁 * notify()/notifyAll():唤醒 * @author qjc * * 2016-3-13 */ public class Movie { private String pic; /** * 信号灯 * flag = T 生产者生产,消费者等待,生产完成后通知消费 * flag = F 消费者消费,生产者等待,消费完后通知生产 */ private boolean flag = true; public synchronized void play(String pic){ if(!flag){ //生产者等待 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //开始生产 try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("生产了"+pic); //生产完毕 this.pic = pic; //通知消费 this.notify(); //生产者停下 this.flag = false; } public synchronized void watch(){ if(flag){ //消费者等待 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //开始消费 try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(pic); //消费完毕 System.out.println("消费了"+pic); //通知生产 this.notifyAll(); //消费停止 this.flag = true; } } /** * 生产者 * @author qjc * * 2016-3-13 */ public class Player implements Runnable{ private Movie m; public Player(Movie m) { super(); this.m = m; } @Override public void run() { for(int i=0;i<20;i++){ if(0==i%2){ m.play("左青龙"); }else{ m.play("右白虎"); } } } } /** * 消费者 * @author qjc * * 2016-3-13 */ public class Watcher implements Runnable { private Movie m; public Watcher(Movie m) { super(); this.m = m; } @Override public void run() { for(int i=0;i<20;i++){ m.watch(); } } } /** * 测试 * @author qjc * * 2016-3-13 */ public class App { public static void main(String[] args) { //共同资源 Movie m = new Movie(); //多线程 Player p = new Player(m); Watcher w = new Watcher(m); new Thread(p).start(); new Thread(w).start(); } }
sleep()与wait()区别
对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。
sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。
在调用sleep()方法的过程中,线程不会释放对象锁。
而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备
获取对象锁进入运行状态。
6、任务调度
Timer 定时器类
TimerTask 任务类
通过java timer timertask :(spring的任务调度就是通过他们来实现的)
在这种实现方式中,Timer类实现的是类似闹钟的功能,也就是定时或者每个一定时间触发一次线程。其实,Timer类本身实现的就是一个线程,只是这个线程是用来实现调用其他线程的。而TimerTask类似一个抽象类,该类实现了Runnable接口,所以按照前面的介绍,该类具备多线程的能力。
在这种实现方式中,通过继承TimerTask是该类获得多线程的能力,将需要多线程执行的代码书写在run方法内部,然后通过Timer类启动线程的执行。
在实际使用时,一个Timer可以启动任意多个TimerTask实现线程,但是多个线程之间会存在阻塞。所以如果多个线程之间如果需要完全独立运行的话,最好还是一个Timer启动一个TimerTask实现。
例4:
/** * @author qjc * * 2016-3-13 */ public class TimeDemo { public static void main(String[] args) { Timer timer = new Timer(); timer.schedule(new TimerTask() { @Override public void run() { System.out.println("so easy.."); } }, new Date(System.currentTimeMillis()+1000) , 500);//参数3:每隔500秒调用一次,没有参数3只调用一次 } }
第三方框架:quartz
juc
7、总结
1、创建线程:
继承Thread
实现Runnable
实现Callable
2、线程状态
1)新生-->start -->就绪-->运行-->阻塞-->终止
2)终止线程
3)阻塞:join yield
sleep
3、线程的信息
1)Thread.currentThread
2)
获取名称设置名称设置优先级判断状态
4、同步 ----对同一份资源