五分钟带你玩转多线程(一)java多线程基础知识简介

简介: 线程概念进程:是一个执行中的程序,如打开网易云音乐,网易云音乐就是一个进程线程:是进程的组成,一个进程包含多个线程,是jvm最小调度单元。如网易云音乐听歌是一个线程,评价是一个线程。


线程概念

进程:是一个执行中的程序,如打开网易云音乐,网易云音乐就是一个进程

线程:是进程的组成,一个进程包含多个线程,是jvm最小调度单元。如网易云音乐听歌是一个线程,评价是一个线程。

并发:就是多线程,当一个线程执行时 如果cpu有空闲了 就可以执行另一个线程 即使本线程没有结束,但是一个时间段只有一个线程在执行。比如:听歌时候能同时评论

并行:多个线程同时进行。真正的同时发生

为什么使用多线程:如果不使用多线程 只有一件事干完才能干另一件事 那么你在听歌时候只有听歌的线程在执行 就不能评论,而是用多线程后可以在听歌时候同时评论 同时提高对内存的使用率 避免内存空闲(但是不能创建太多线程,速度会变慢)

线程状态

image.png

  • 新建状态: 使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
  • 就绪状态: 当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
  • 运行状态: 如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
  • 阻塞状态:如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
  • 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
  • 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
  • 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)
  • 死亡状态: 一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。

 

实现方式

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抢到资源

image.png

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 。


相关文章
|
1天前
|
消息中间件 缓存 NoSQL
Java多线程实战-CompletableFuture异步编程优化查询接口响应速度
Java多线程实战-CompletableFuture异步编程优化查询接口响应速度
|
1天前
|
数据采集 存储 Java
高德地图爬虫实践:Java多线程并发处理策略
高德地图爬虫实践:Java多线程并发处理策略
|
1天前
|
安全 算法 Java
JavaSE&多线程&线程池
JavaSE&多线程&线程池
15 7
|
2天前
|
缓存 Java
【Java基础】简说多线程(上)
【Java基础】简说多线程(上)
6 0
|
2天前
|
并行计算 算法 安全
Java从入门到精通:2.1.3深入学习Java核心技术——掌握Java多线程编程
Java从入门到精通:2.1.3深入学习Java核心技术——掌握Java多线程编程
|
2天前
|
存储 缓存 NoSQL
为什么Redis使用单线程 性能会优于多线程?
在计算机领域,性能一直都是一个关键的话题。无论是应用开发还是系统优化,我们都需要关注如何在有限的资源下,实现最大程度的性能提升。Redis,作为一款高性能的开源内存数据库,因其出色的单线程性能而备受瞩目。那么,为什么Redis使用单线程性能会优于多线程呢?
15 1
|
17天前
|
存储 Java 数据库连接
java多线程之线程通信
java多线程之线程通信
|
28天前
|
存储 缓存 NoSQL
Redis单线程已经很快了6.0引入多线程
Redis单线程已经很快了6.0引入多线程
31 3
|
30天前
|
消息中间件 安全 Linux
线程同步与IPC:单进程多线程环境下的选择与权衡
线程同步与IPC:单进程多线程环境下的选择与权衡
58 0
|
1月前
|
Java 调度 C#
C#学习系列相关之多线程(一)----常用多线程方法总结
C#学习系列相关之多线程(一)----常用多线程方法总结

热门文章

最新文章