线程概念
进程:是一个执行中的程序,如打开网易云音乐,网易云音乐就是一个进程
线程:是进程的组成,一个进程包含多个线程,是jvm最小调度单元。如网易云音乐听歌是一个线程,评价是一个线程。
并发:就是多线程,当一个线程执行时 如果cpu有空闲了 就可以执行另一个线程 即使本线程没有结束,但是一个时间段只有一个线程在执行。比如:听歌时候能同时评论
并行:多个线程同时进行。真正的同时发生
为什么使用多线程:如果不使用多线程 只有一件事干完才能干另一件事 那么你在听歌时候只有听歌的线程在执行 就不能评论,而是用多线程后可以在听歌时候同时评论 同时提高对内存的使用率 避免内存空闲(但是不能创建太多线程,速度会变慢)
线程状态
-
新建状态: 使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
-
就绪状态: 当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
-
运行状态: 如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
- 阻塞状态:如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
- 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
- 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
- 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)
- 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
-
死亡状态: 一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
实现方式
1继承Thread
1. class RunnableDemo extends Thread { 2. @Override 3. public void run() { 4. for (int i = 0; i < 100; i++) { 5. System.out.println("当前为" + i); 6. try { 7. Thread.sleep(1000); 8. } catch (InterruptedException e) { 9. e.printStackTrace(); 10. } 11. } 12. } 13. } 14. 15. 16. public class TestThread extends Thread{ 17. public static void main(String [] args){ 18. RunnableDemo test = new RunnableDemo(); 19. test.start(); 20. } 21. }
2.实现Runnable
1. class RunnableDemo implements Runnable { 2. @Override 3. public void run() { 4. for (int i = 0; i < 100; i++) { 5. System.out.println("当前为" + i); 6. try { 7. Thread.sleep(1000); 8. } catch (InterruptedException e) { 9. e.printStackTrace(); 10. } 11. } 12. } 13. } 14. 15. 16. public class TestThread extends Thread{ 17. public static void main(String [] args){ 18. RunnableDemo test = new RunnableDemo(); 19. Thread t = new Thread(test); 20. t.start(); 21. } 22. }
尽量选择runable
1):适合多个相同的程序代码的线程去处理同一个资源
2):可以避免java中的单继承的限制
3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
4):线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类
多线程关键字使用
1 |
public void start() 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。 |
2 |
public void run() 如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。 |
3 |
public final void setName(String name) 改变线程名称,使之与参数 name 相同。 |
4 |
public final void setPriority(int priority) 更改线程的优先级。 |
5 |
public final void setDaemon(boolean on) 将该线程标记为守护线程或用户线程。 |
6 |
public final void join(long millisec) 等待该线程终止的时间最长为 millis 毫秒。 |
7 |
public void interrupt() 中断线程。 |
8 |
public final boolean isAlive() 测试线程是否处于活动状态。 |
9 |
public static void yield() 暂停当前正在执行的线程对象,并执行其他线程。 |
10 |
public static void sleep(long millisec) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。 |
11 |
public static boolean holdsLock(Object x) 当且仅当当前线程在指定的对象上保持监视器锁时,才返回 true。 |
12 |
public static Thread currentThread() 返回对当前正在执行的线程对象的引用。 |
13 |
public static void dumpStack() 将当前线程的堆栈跟踪打印至标准错误流。 |
sleep
线程睡眠的原因:线程执行的太快,或需要强制执行到下一个线程,睡眠后进入runnable中,但是睡眠完毕后不一定立即执行,需要重新争夺cpu,除非拥有更高的优先级,可以使用interrupt()方法打断,抛出InterruptedException 异常。
1. class RunnableDemo2 implements Runnable { 2. @Override 3. public void run() { 4. for (int i = 0; i < 100; i++) { 5. System.out.println("当前为" + i); 6. try { 7. Thread.sleep(100); 8. } catch (InterruptedException e) { 9. e.printStackTrace(); 10. } 11. } 12. } 13. }
扩展:Java线程调度是Java多线程的核心,只有良好的调度,才能充分发挥系统的性能,提高程序的执行效率。但是不管程序员怎么编写调度,只能最大限度的影响线程执行的次序,而不能做到精准控制。因为使用sleep方法之后,线程是进入阻塞状态的,只有当睡眠的时间结束,才会重新进入到就绪状态,而就绪状态进入到运行状态,是由系统控制的,我们不可能精准的去干涉它,所以如果调用Thread.sleep(1000)使得线程睡眠1秒,可能结果会大于1秒。
yield
线程让步 根据上图 可以得知 是直接进入准备状态。本线程和其他线程一样机会获取cpu资源。使用yield()的目的是让同样优先级的线程之间能适当的轮转执行。
1. class RunnableDemo implements Runnable { 2. 3. @Override 4. public void run() { 5. for (int i = 0; i < 50; i++) { 6. 7. System.out.println(Thread.currentThread().getName() + i); 8. if (i ==30) { 9. Thread.yield(); 10. } 11. } 12. } 13. } 14. 15. 16. public class TestThread extends Thread{ 17. public static void main(String [] args) throws InterruptedException { 18. RunnableDemo runnable1=new RunnableDemo(); 19. RunnableDemo runnable2=new RunnableDemo(); 20. Thread t1 = new Thread(runnable1,"线程1-"); 21. Thread t2 = new Thread(runnable1,"线程2-"); 22. t1.start(); 23. t2.start(); 24. 25. } 26. }
注意:yield不会抛出异常
通过运行 我们发现 可以线程t1又抢到了资源 依然执行t1 或者t2抢到资源
join
会保证join线程完 先执行
在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。】
如下图 主线程在t1和t2之前完成了
1. class RunnableDemo implements Runnable { 2. 3. @Override 4. public void run() { 5. for (int i = 0; i < 10; i++) { 6. System.out.println(Thread.currentThread().getName() + i); 7. } 8. } 9. } 10. 11. 12. public class TestThread extends Thread{ 13. public static void main(String [] args) throws InterruptedException { 14. System.out.println(Thread.currentThread().getName()+"main开始"); 15. RunnableDemo runnable1=new RunnableDemo(); 16. RunnableDemo runnable2=new RunnableDemo(); 17. Thread t1 = new Thread(runnable1,"线程1-"); 18. Thread t2 = new Thread(runnable1,"线程2-"); 19. t1.start(); 20. t2.start(); 21. System.out.println(Thread.currentThread().getName()+ "main结束!"); 22. } 23. }
修改如下 可以发现主线程在最后完成
1. public class TestThread extends Thread{ 2. public static void main(String [] args) throws InterruptedException { 3. System.out.println(Thread.currentThread().getName()+"main开始"); 4. RunnableDemo runnable1=new RunnableDemo(); 5. RunnableDemo runnable2=new RunnableDemo(); 6. Thread t1 = new Thread(runnable1,"线程1-"); 7. Thread t2 = new Thread(runnable1,"线程2-"); 8. t1.start(); 9. t2.start(); 10. t1.join(); 11. t2.join(); 12. System.out.println(Thread.currentThread().getName()+ "main结束!"); 13. } 14. }
其原理是:调用了先获取到子线程的锁然后调用wait方法来实现的,因为当子线程运行结束后会调用notifyall所以主线程会被唤醒并且再次获取到子线程的锁继续运行。
interrupt
https://segmentfault.com/a/1190000018191053。
https://www.cnblogs.com/kaituorensheng/p/10644267.html
wait/notify/notifyall
如图,调用wait(),会使线程休眠,使该线程处于等待池(丢失锁),直到notify()/notifyAll(),会唤醒线程,线程被唤醒被放到锁定池,其他线程释放同步锁使线程回到可运行状态(Runnable)
注意:
1.Obj.wait(),与Obj.notify()必须要与synchronized(Obj)一起使用(在synchronized(Obj){...}语句块内).也就是wait,与notify是针对已经获取了Obj锁进行操作,并且,这三个关键字针对的是同一个监视器(某对象的监视器)。
2.因为wait之后会放开锁,所以,其他线程可以进入同步块执行。直到有其它线程调用对象的notify()唤醒该线程。才干继续获取对象锁。并继续执行。
2.notify()方法,唤醒在此对象监视器上等待的单个线程。如果多个线程在等待 那么会随机选择一个唤醒 而notifyAll()会全部唤醒
4.notify()可以唤醒wait后线程 但是实在本synchronized执行完在唤醒 唤醒后的线程和其他线程一起争夺资源
1. public class WaitNotify { 2. 3. public static void main(String[] args) { 4. 5. final Object A = new Object(); 6. final Object B = new Object(); 7. 8. Thread t1 = new Thread("t1-thread") { 9. @Override 10. public void run() { 11. synchronized (A) { 12. System.out.println(Thread.currentThread().getName() + "拿到 A 的监视器锁"); 13. System.out.println(Thread.currentThread().getName() + "尝试获取 B 的监视器锁"); 14. try { 15. System.out.println(Thread.currentThread().getName() + "休眠 2s,不释放 A 的监视器锁"); 16. TimeUnit.SECONDS.sleep(2); 17. System.out.println(Thread.currentThread().getName() + "挂起自己,释放 A 的监视器锁"); 18. A.wait(); 19. System.out.println(Thread.currentThread().getName() + "被唤醒,等待获取 B 的监视器锁"); 20. } catch (InterruptedException e) { 21. e.printStackTrace(); 22. } 23. synchronized (B) { 24. System.out.println(Thread.currentThread().getName() + "拿到 B 的监视器锁"); 25. B.notify(); 26. } 27. } 28. } 29. }; 30. 31. Thread t2 = new Thread("t2-thread") { 32. @Override 33. public void run() { 34. synchronized (B) { 35. System.out.println(Thread.currentThread().getName() + "拿到 B 的监视器锁"); 36. System.out.println(Thread.currentThread().getName() + "尝试获取 A 的监视器锁"); 37. synchronized (A) { 38. System.out.println(Thread.currentThread().getName() + "拿到 A 的监视器锁"); 39. try { 40. System.out.println(Thread.currentThread().getName() + "休眠 2s,不释放 A 的监视器锁"); 41. TimeUnit.SECONDS.sleep(2); 42. } catch (InterruptedException e) { 43. e.printStackTrace(); 44. } 45. System.out.println(Thread.currentThread().getName() + "挂起自己,释放 A 的监视器锁,唤醒 t0"); 46. A.notify(); 47. } 48. try { 49. System.out.println(Thread.currentThread().getName() + "休眠 2s,不释放 B 的监视器锁"); 50. TimeUnit.SECONDS.sleep(2); 51. System.out.println(Thread.currentThread().getName() + "挂起自己,释放 B 的监视器锁"); 52. B.wait(); 53. System.out.println(Thread.currentThread().getName() + "被唤醒"); 54. } catch (InterruptedException e) { 55. e.printStackTrace(); 56. } 57. } 58. } 59. }; 60. 61. t1.start(); 62. t2.start(); 63. } 64. 65. }
1. t2-thread拿到 B 的监视器锁 2. t2-thread尝试获取 A 的监视器锁 3. t1-thread拿到 A 的监视器锁 4. t1-thread尝试获取 B 的监视器锁 5. t1-thread休眠 2s,不释放 A 的监视器锁 6. t1-thread挂起自己,释放 A 的监视器锁 7. t2-thread拿到 A 的监视器锁 8. t2-thread休眠 2s,不释放 A 的监视器锁 9. t2-thread挂起自己,释放 A 的监视器锁,唤醒 t0 10. t2-thread休眠 2s,不释放 B 的监视器锁 11. t1-thread被唤醒,等待获取 B 的监视器锁 12. t2-thread挂起自己,释放 B 的监视器锁 13. t1-thread拿到 B 的监视器锁 14. t2-thread被唤醒
优先级
static int MAX_PRIORITY:线程可以具有的最高优先级,取值为10。
static int MIN_PRIORITY: 线程可以具有的最低优先级,取值为1。
static int NORM_PRIORITY: 分配给线程的默认优先级,取值为5,主线程默认的优先级。
t.setPriority(1); 设置优先级 数字越高 先执行线程的机会越多
1. public class TestThread extends Thread{ 2. public static void main(String [] args) throws InterruptedException { 3. RunnableDemo test1 = new RunnableDemo(); 4. 5. Thread t = new Thread(test1,"线程1-"); 6. Thread t2 = new Thread(test1,"线程2-"); 7. Thread t3 = new Thread(test1,"线程3-"); 8. t.setPriority(1); 9. t2.setPriority(8); 10. t3.setPriority(9); 11. 12. t.start(); 13. t2.start(); 14. t3.start(); 15. } 16. }
同步
线程安全问题:多个线程同时进行,会发现与单线程运行结果不同(经典的卖票问题)
1. class RunnableDemo implements Runnable { 2. int x = 10; 3. @Override 4. public void run() { 5. while (x>0){ 6. try { 7. Thread.sleep(10); 8. } catch (InterruptedException e) { 9. e.printStackTrace(); 10. } 11. --x; 12. System.out.println(Thread.currentThread().getName() + x); 13. } 14. } 15. } 16. 17. 18. public class TestThread extends Thread{ 19. public static void main(String [] args) throws InterruptedException { 20. RunnableDemo test1 = new RunnableDemo(); 21. 22. Thread t = new Thread(test1,"线程1-"); 23. Thread t2 = new Thread(test1,"线程2-"); 24. Thread t3 = new Thread(test1,"线程3-"); 25. 26. t.start(); 27. t2.start(); 28. t3.start(); 29. } 30. }
处理办法1:synchronized关键字 会保证当前只有一个线程调用 但是运行效率会变低
1. class RunnableDemo implements Runnable { 2. int x = 10; 3. @Override 4. public synchronized void run() { 5. while (x>0){ 6. try { 7. Thread.sleep(10); 8. } catch (InterruptedException e) { 9. e.printStackTrace(); 10. } 11. --x; 12. System.out.println(Thread.currentThread().getName() + x); 13. } 14. } 15. }
处理办法2:
1. class RunnableDemo implements Runnable { 2. 3. private Lock lock = new ReentrantLock(); 4. 5. int x = 10; 6. @Override 7. public void run() { 8. lock.lock(); //上锁 9. try { 10. while (x>0){ 11. try { 12. Thread.sleep(10); 13. } catch (InterruptedException e) { 14. e.printStackTrace(); 15. } 16. --x; 17. System.out.println(Thread.currentThread().getName() + x); 18. } 19. } finally { 20. lock.unlock(); //解锁 21. } 22. } 23. }
处理方式3:同步代码块
1. public class ThreadTrain2 implements Runnable { 2. private int tickets = 50; 3. private static Object obj = new Object();//锁的对象,可以是任意的对象 4. @Override 5. public void run() { 6. while(tickets > 0){ 7. synchronized (obj) {// 同步代码块 8. if (tickets > 0) { 9. System.out.println(Thread.currentThread().getName() + "卖了第" + (50 - tickets + 1) + "张票"); 10. tickets--; 11. } 12. } 13. } 14. } 15. public static void main(String[] args) { 16. ThreadTrain2 tt = new ThreadTrain2(); 17. Thread th1 = new Thread(tt, "1号窗口"); 18. Thread th2 = new Thread(tt, "2号窗口"); 19. th1.start(); 20. th2.start(); 21. } 22. }
死锁
A,B两个线程 而A,B互相依赖对象 所以A会等待B释放资源 B会等待A释放资源 这样就会形成死锁
1. public class WaitNotify { 2. 3. public static void main(String[] args) { 4. 5. final Object A = new Object(); 6. final Object B = new Object(); 7. 8. Thread t1 = new Thread(new Runnable() { 9. 10. @Override 11. public void run() { 12. synchronized (A) { 13. try { 14. Thread.sleep(1000); 15. } catch (InterruptedException e) { 16. e.printStackTrace(); 17. } 18. synchronized (B) { 19. } 20. } 21. } 22. }); 23. Thread t2 = new Thread(new Runnable() { 24. @Override 25. public void run() { 26. synchronized (B) { 27. synchronized (A) { 28. } 29. } 30. } 31. }); 32. t1.start(); 33. t2.start(); 34. } 35. }
守护线程
后台线程:指为其它线程提供服务的线程,也称为守护线程。JVM的垃圾回收线程就是一个后台线程。用户线程和守护线程的差别在于,是否等待主线程依赖于主线程结束而结束
前台线程:是指接受后台线程服务的线程。事实上前台后台线程是联系在一起。就像傀儡和幕后操纵者一样的关系。傀儡是前台线程、幕后操纵者是后台线程。
sleep,yield,wait差别
sleep:
1.sleep()使当前线程进入停滞状态。所以执行sleep()的线程在指定的时间内肯定不会被执行,等sleep结束进入runnable状态再次争夺资源 ,如果有更高的优先级,会先执行。
2.Thread类的方法:sleep(),yield()等
yield:
1.yield()立刻回到runnable状态
wait:
1. Object的方法:wait()和notify()等
2.wait,notify和notifyAll仅仅能在同步控制方法或者同步控制块里面使用。而sleep能够在不论什么地方使用.所以sleep()和wait()方法的最大差别是:
sleep()睡眠时,保持对象锁,仍然占有该锁;
而wait()睡眠时。释放对象锁,使得其它线程能够使用同步控制块或者方法。。
可是wait()和sleep()都能够通过interrupt()方法打断线程的暂停状态,从而使线程立马抛出InterruptedException(但不建议使用该方法)。
3.sleep()是Thread类的Static(静态)的方法;因此他不能改变对象的机锁。所以当在一个Synchronized块中调用Sleep()方法是,线程尽管休眠了。可是对象的机锁并木有被释放,其它线程无法訪问这个对象(即使睡着也持有对象锁)
4.wait之后进入锁定池 需要用notify或者notifyAlll
相同点
1:都可以使线程停止
2:wait()和sleep()都能够通过interrupt()方法 打断线程的暂停状态 ,从而使线程立马抛出InterruptedException。
例:假设线程A希望马上结束线程B,则能够对线程B对应的Thread实例调用interrupt方法。
假设此刻线程B正在wait/sleep /join。则线程B会立马抛出InterruptedException。在catch() {} 中直接return就可以安全地结束线程。
须要注意的是,InterruptedException是线程自己从内部抛出的。并非interrupt()方法抛出的。对某一线程调用 interrupt()时。假设该线程正在执行普通的代码,那么该线程根本就不会抛出InterruptedException。
可是,一旦该线程进入到 wait()/sleep()/join()后,就会立马抛出InterruptedException 。