Java线程详解
来源:互联网 发布:java做网络爬虫 编辑:程序博客网 时间:2024/06/06 03:44
引子:由于对线程掌握的不是很扎实,每次在使用到线程的时候都有种心惊胆战的感觉;所以这次决定系统的把线程相关的知识总结一下,一方面巩固基础,另一方面也可用把自己的理解抛出来大家一起探讨。
学习提纲
生命周期
Thread的生命周期有5种:
(1)新建(new)–通过new创建后,Thread thread = new Thread();
(2)就绪(runnable)调用了start方法,进入等待池后的状态,注意调用了start只是开始等待cpu调度,并不一定马上开始运行了。
(3)运行(running)线程开始运行。
(4)阻塞(blocked)wait,sleep,join,synchronized,IO阻塞等都会使线程进入阻塞状态。
(5)死亡(dead)线程执行完毕或者异常终止,结束生命周期。
图:生命周期图例(此图百度所得,非原创):
基本使用
(1)继承Thread:
class newThread extends Thread{ @Override public void run(){ //TODO } }
(2)实现Runnable
class newRunnable implements Runnable{ @Override public void run(){ //TODO } }Thread thread = new Thread(new newRunnable());
(3)sleep()
说明:用法比较简单,Thread.sleep(1000); 使当前正处于运行阶段的线程暂停,不会释放锁。可以被interrupt唤醒(关于interrupt,下面会详细介绍)。它是一个Thread的static方法。其控制范围是当前线程。
public static void main(String[] args) { // TODO Auto-generated method stub //testIo(); Thread thread = new Thread(new Runnable(){ @Override public void run(){ try { System.out.println(Thread.currentThread().getName()+"开始睡觉"); Thread.sleep(5000); System.out.println("睡了5s"); } catch (InterruptedException e) { // TODO Auto-generated catch block System.out.println("被打断了:"+e.toString()); } } }); thread.start(); System.out.println(Thread.currentThread().getName()+"要打断你睡觉了"); try { Thread.sleep(2000); thread.interrupt(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } }
(4)wait()
wait方法是Object的方法,不是属于Thread的方法,只能用在Synchronized中,包括唤醒它的notify()和notifyAll()也必须用在Synchronized中,因为必须拿到Object的锁,否则会报错 java.lang.IllegalMonitorStateException 。
我们把上面的代码修改一下:
public static void main(String[] args) { // TODO Auto-generated method stub //testIo(); final Object object = new Object(); Thread thread = new Thread(new Runnable(){ @Override public void run(){ synchronized(object){ try { object.wait(); System.out.println(Thread.currentThread().getName()+"开始睡觉"); Thread.sleep(5000); System.out.println("睡了5s"); } catch (InterruptedException e) { // TODO Auto-generated catch block System.out.println("被打断了:"+e.toString()); } } } }); thread.start(); System.out.println(Thread.currentThread().getName()+"要打断你睡觉了"); synchronized(object){ object.notify(); } try { Thread.sleep(2000); thread.interrupt(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } }
(5)yield()
这个方法是属于Thread的,作用是暂时让出CPU使用权,让自己和其他的线程重新去让cpu调度,自己还是有可能被调度到。
个人觉得这个方法没什么意思啊,也没有释放锁的功能,说一下可能会用到的地方吧,当你觉得一个线程不那么重要,优先级很低,然后又不希望它占用太多的cpu资源的时候就可以用这个方法稍微暗示一下,只是暗示,不是控制啊,控制不了
It is rarely appropriate to use this method。
虽然比较简单,还是上个代码:
public static void main(String[] args) { // TODO Auto-generated method stub //testIo(); Thread thread1 = new Thread(){ @Override public void run(){ while(i<10){ System.out.println("i:"+i); i++; if(i==5) yield(); } } }; Thread thread2 = new Thread(){ @Override public void run(){ while(j<10){ System.out.println("j:"+j); j++; } } }; thread1.start(); thread2.start(); }
(6)join()
这个方法什么意思呢?这个方法是Thread的方法,当你调用了这个方法后,当前正在运行的线程会等调用了该方法的线程完成后才会继续运行。场景的场景是A线程的运行需要B线程的运行结果,那么A就要等B运行完,这个时候就可以用join()方法了。
注意了,在start()后调用才有意义。
上代码:
public static void main(String[] args) { // TODO Auto-generated method stub //testIo(); final Thread thread1 = new Thread(){ @Override public void run(){ while(i<10){ System.out.println("i:"+i); i++; if(i==5) yield(); } } }; Thread thread2 = new Thread("2"){ @Override public void run(){ while(j<10){ System.out.println("j:"+j); j++; if(j==5) try { System.out.println(Thread.currentThread().getName()+"开始等待"); thread1.join(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }; thread1.start(); thread2.start(); try { System.out.println(Thread.currentThread().getName()+"开始等待"); thread1.join(); System.out.println(Thread.currentThread().getName()+"等待完成了"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } }
说明:上面这段代码还有个彩蛋,在join之前,thread1已结运行完了呢?
join()还有个重载方法thread1.join(50000);参数是毫秒,意思是虽然我要等你的运行结果,但是你也不能让我等太久了,等超过了我设置的时间,我就不等了。
(7)interrupt
现在我们来谈一谈interrupt,嗯,打断线程,让线程抛异常从而退出。你会说了,简单啊,但是让我们先来看看下面的代码:
public static void main(String[] args) { // TODO Auto-generated method stub Thread thread1 = new Thread("thread01"){ @Override public void run(){ while(i<5){ i++; long time = System.currentTimeMillis(); while((System.currentTimeMillis()-time < 1000)) { //什么也不做,让程序飞一会儿 } System.out.println("i:"+i); } } }; thread1.start(); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } //1s后打断线程1 System.out.println("打断前"); thread1.interrupt(); System.out.println("打断完成"); }
别说话,看结果:
打断前i:1打断完成i:2i:3i:4i:5
是不是很意外,为什么没有被打断?好了,我也不卖关子了,我们都知道,当我们用sleep(),join(),wait()的时候都必须try catch,那么有没有注意到catch里面捕获的是什么异常呢?没错,就是InterruptedException,聪明的你现在知道为什么了嘛?
来来来,我们将上面让程序飞的代码换成sleep(),好了,在运行,你熟悉的错误回来了。
打断前java.lang.InterruptedException: sleep interrupted at java.lang.Thread.sleep(Native Method) at wangliang.Test$1.run(Test.java:36)打断完成i:1i:2i:3i:4i:5
当调用interrupt方法时并不是去中断线程,而是改变线程的中断标志位,Thread.currentThread().isInterrupted()可以查看这个标志位,当线程处于被阻塞时,调用interrupt方法才有效。
多线程
上面说了这么多,重点还是多线程,说到多线程,那重点还是线程同步。
(1)Synchronized
每个对象都有monitor,这就是我们常说的锁。使用Synchronized,保证了对对象访问的互斥性。
三条规则:
第一条: 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程对“该对象”的该“synchronized方法”或者“synchronized代码块”的访问将被阻塞。
第二条: 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程仍然可以访问“该对象”的非同步代码块。
第三条: 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程对“该对象”的其他的“synchronized方法”或者“synchronized代码块”的访问将被阻塞。
很多情况下,可以使用synchronized(this){…}来同步代码块,这种情况和用synchronized修饰方法一样。但需要注意的是,使用this作为同步对象的话,如果同一个类中存在多个synchronized(this){…}代码块,其中任何一个synchronized(this)代码块处于被执行状态,则其它线程对其他synchronized(this)代码块的访问也会受到阻塞。
在静态方法中时,可以用XX.class来锁。
(2)volatile
在讲volatile之前,我们先了解一下并发正确执行的三要素:原子性,可见性,有序性。
原子性就是一个操作要么全部完成,要么就不完成,比如i++我们知道是分了三步的,所以它不是一个原子操作。可见性是指一个变量在一个线程中改变了,其他线程可以知道。有序性是指代码执行是有序的,你要知道,因为指令重排序的存在,JVM执行代码的顺序并不一定是你写的顺序。
volatile能保证可见性和部分有序性。为什么说是部分有序性呢,因为volatile可以保证在它前面的一定在它前面运行,它后面的一定在它后面运行。所以,如果我们要使用volatile,就需要保证操作的原子性。
通常来说,使用volatile必须具备以下2个条件:
1)对变量的写操作不依赖于当前值,这个比如像i++这种,i的下一个值依赖于当前值的。
2)该变量没有包含在具有其他变量的不变式中
举个栗子:
class Singleton{ private volatile static Singleton instance = null; private Singleton() { } public static Singleton getInstance() { if(instance==null) { //语句1 synchronized (Singleton.class) { //语句2 if(instance==null) //语句3 instance = new Singleton(); //语句4 } } return instance; }}
上面的程序,假设两个线程都运行到了语句1,然后由于语句2的存在,只有一个线程进入到了语句3,此时,如果instance没有volatile修饰的话,那么另外一个线程可能不知道此时 instance != null 而又new了一个出来。有了volatile修饰,instance的改变立刻回通知到另外一个线程。
这篇博客讲的很详细 http://www.cnblogs.com/dolphin0520/p/3920373.html
(3)Lock
这个我目前阶段不打算深入了解,一方面是熟练掌握有一定难度,如果使用不当容易出错;另一方面在Android开发中上面介绍的内容已足以应对大部分情况。
如果有需要的小伙伴,推荐一个不错的博客:https://my.oschina.net/xianggao/blog/88477
总结,以上算是比较系统的介绍了java线程的知识点,下一篇会就具体的案例来学习,在实战中跟深入了解。
- Java线程详解
- Java 线程 详解
- Java线程详解[转]
- Java 线程 详解
- Java之线程详解
- java 线程详解
- java 线程详解
- Java线程安全详解
- java线程thread详解
- Java线程详解
- java 线程详解
- Java 线程部分详解
- Java线程详解
- java 线程池详解
- Java线程详解
- java线程详解
- java线程池详解
- java 线程池 详解
- Yoshua Bengio等大神传授:26条深度学习经验
- laravel初次使用
- 轻量集群管理工具PSSH
- 关于加载android里 .so文件的问题
- Hibernate5增删改
- Java线程详解
- 交控外包的日子里
- Java多线程同步简单了解
- 奇数
- android 自定义view实现类似圆盘抽奖的效果
- Asset目录与res目录的区别
- 安卓解决高德地图在scrollview的嵌套下滑动出现黑影
- HDU 5933 ArcSoft's Office Rearrangement
- Git命令,分场景