• 48、线程


     一、线程相关概念:

    1、程序(program):

    是为完成特定任务、用某种语言编写的一组指令的集合,即我们写的代码。

    2、进程:
    (1)进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存空间。当我们使用迅雷,又启动了一个进程,操作系统将为迅雷分配新的内存空间。
    (2)进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程:有它自身的产生、存在和消亡的过程.

    3、什么是线程:

    (1)线程由进程创建的,是进程的一个实体

    (2)一个进程可以拥有多个线程

    4、单线程:

    同一个时刻,只允许执行一个线程。

    5、多线程:

    同一个时刻,可以执行多个线程,比如:一个qq进程,可以同时打开多个聊天窗口,一个迅雷进程,可以同时下载多个文件。

    6、并发:

    同一个时刻,多个任务交替执行,造成一种“貌似同时”的错觉,简单地说,单核cpu实现的多任务就是并发

    7、并行:(有同伴一起走)

    同一个时刻,多个任务同时执行,多核cpu可以实现并行

    ·查看电脑CPU的方式:

    方式一:右击屏幕底下的长条形任务栏,打开“任务管理器”,“打开资源管理器” 

    方式二:右击“我的电脑”,选择“管理”,“设备管理器”,“处理器”

    方式三:用java代码查看

    1. package event_;
    2. public class CpuNum {
    3. public static void main(String[] args) {
    4. Runtime runtime = Runtime.getRuntime();
    5. //获取当前电脑的cpu数量/核心数
    6. int cpuNums = runtime.availableProcessors();
    7. System.out.println("当前有cpu个数:" + cpuNums);
    8. }
    9. }
    10. //当前有cpu个数:8

    我的是4核8线程

    二、线程基本使用
    1、创建线程的两种方式

    (1)继承Thread类,重写run方法

    1. package threaduse;
    2. public class Thread01 {
    3. public static void main(String[] args) throws Exception{
    4. //创建Cat对象,可以当做线程使用
    5. Cat cat=new Cat();
    6. cat.start();//启动线程
    7. //当main线程启动一个子线程Thread-0,主线程不会阻塞,会继续执行
    8. //这时,主线程和子线程是交替执行
    9. System.out.println("主线程继续执行"+Thread.currentThread().getName());
    10. for(int i=0;i<10;i++){
    11. System.out.println("主线程 i="+i);
    12. //让主线程休眠
    13. Thread.sleep(1000);
    14. }
    15. }
    16. }
    17. //1、当一个类继承了Thread,该类就可以当做线程使用
    18. //2、要重写run方法,写上自己的业务逻辑
    19. //3、run方法是Thread类实现了Runnable接口用的
    20. /* @Override
    21. public void run() {
    22. Runnable task = holder.task;
    23. if (task != null) {
    24. task.run();
    25. }
    26. }
    27. */
    28. class Cat extends Thread{
    29. int times=0;
    30. public void run() {
    31. //如果不加循环,代码执行一次就退出了
    32. while(true){
    33. //该线程每隔1秒,在控制台输出"喵喵,我是小猫咪"
    34. System.out.println("喵喵,我是小猫咪"+(++times)+" 线程名:"+Thread.currentThread().getName());
    35. //让线程休眠1秒
    36. try {
    37. Thread.sleep(1000);
    38. } catch (InterruptedException e) {
    39. throw new RuntimeException(e);
    40. }
    41. if(times==10){
    42. break;
    43. }
    44. }
    45. }
    46. }
    47. //主线程继续执行main
    48. //主线程 i=0
    49. //喵喵,我是小猫咪1 线程名:Thread-0
    50. //喵喵,我是小猫咪2 线程名:Thread-0
    51. //主线程 i=1
    52. //喵喵,我是小猫咪3 线程名:Thread-0
    53. //主线程 i=2
    54. //喵喵,我是小猫咪4 线程名:Thread-0
    55. //主线程 i=3
    56. //喵喵,我是小猫咪5 线程名:Thread-0
    57. //主线程 i=4
    58. //喵喵,我是小猫咪6 线程名:Thread-0
    59. //主线程 i=5
    60. //主线程 i=6
    61. //喵喵,我是小猫咪7 线程名:Thread-0
    62. //主线程 i=7
    63. //喵喵,我是小猫咪8 线程名:Thread-0
    64. //主线程 i=8
    65. //喵喵,我是小猫咪9 线程名:Thread-0
    66. //主线程 i=9
    67. //喵喵,我是小猫咪10 线程名:Thread-0
    1. 深入解析cat.start();方法
    2. //源码:
    3. /*1、
    4. void start(ThreadContainer container) {
    5. synchronized (this) {
    6. start0();
    7. }
    8. }
    9. 2、
    10. //start0()是酵方法,是JVM调用,底层是c/c++实现
    11. //真正实现多线程的效果,是start0(),而不是run()
    12. private native void start0();
    13. */
    14. cat.start();//启动线程--->最终会执行cat的run方法
    15. cat.run();//如果直接调用run()方法,这里的run()方法就是一个普通的方法,没有真正地启动一个线程
    16. //等执行完这个run()方法后,才继续往下走,此时已经不再是多线程了
    17. //在main里调用run()方法,输出的就是main的线程名,不是要启动的线程名
    18. //输出:喵喵,我是小猫咪1 线程名:main

     补:主线程决定进程开启,最后结束的线程决定进程结束

     (2)实现Runinable接口,重写run方法 

     

    1. package threaduse;
    2. public class Thread02 {
    3. public static void main(String[] args) {
    4. Dog dog=new Dog();
    5. //dog.start();报错,这里不能调用start
    6. //创建了Thread对象,把dog对象(实现Runnable),放入Thread
    7. Thread thread = new Thread(dog);
    8. thread.start();
    9. Tiger tiger = new Tiger();
    10. ThreadProxy threadProxy = new ThreadProxy(tiger);
    11. threadProxy.start();
    12. }
    13. }
    14. class Animal{}
    15. class Tiger extends Animal implements Runnable{
    16. @Override
    17. public void run() {
    18. System.out.println("老虎嗷嗷叫...");
    19. }
    20. }
    21. //线程代理类,模拟了一个极简的Thread类
    22. class ThreadProxy implements Runnable{//
    23. private Runnable target=null;//类型是Runnable
    24. @Override
    25. public void run() {
    26. if (target != null) {
    27. target.run();//动态绑定,运行类型是传进来的参数的类型
    28. }
    29. }
    30. public ThreadProxy(Runnable target) {
    31. this.target = target;
    32. }
    33. public void start(){
    34. start0();//这个方法才是真正实现多线程的方法
    35. }
    36. public void start0(){
    37. run();
    38. }
    39. }
    40. class Dog implements Runnable{
    41. int count=0;
    42. @Override
    43. public void run() {
    44. while(true){
    45. System.out.println("小狗汪汪叫...hi"+(++count)+Thread.currentThread().getName());
    46. try {
    47. Thread.sleep(1000);//休眠一秒
    48. } catch (InterruptedException e) {
    49. e.printStackTrace();
    50. }
    51. if (count == 10) {
    52. break;
    53. }
    54. }
    55. }
    56. }

    线程这样设计的意义,最根本的意义就在于解耦,权责分明:把创建线程交给Thread类,创建线程任务交给实现了Runnable接口的任意类

    这样做的好处:

    (1)解除类的单继承限制,更好地实现多线程;

    (2)解耦之前,线程任务改动需要修改run()方法,解耦后,只需要修改外部类,实现非侵入式的修改,提高代码的可扩展性,这是接口的主要好处

    (3)线程任务执行完后,不需要再次创建、销毁线程,只需要实现Runnable接口提交新的的线程任务即可,提高性能节省开销,后面的线程池设计主要体现这一点

    2、应用线程

    1. package threaduse;
    2. import javax.swing.plaf.TableHeaderUI;
    3. @SuppressWarnings({"all"})
    4. public class Thread03 {
    5. public static void main(String[] args) {
    6. T1 t1 = new T1();
    7. T2 t2 = new T2();
    8. Thread thread1=new Thread(t1);
    9. Thread thread2= new Thread(t2);
    10. thread1.start();
    11. thread2.start();
    12. }
    13. }
    14. class T1 implements Runnable{
    15. int count=0;
    16. @Override
    17. public void run() {
    18. while (true) {
    19. System.out.println("hello, world! "+(++count)+Thread.currentThread().getName());
    20. try {
    21. Thread.sleep(1000);
    22. } catch (InterruptedException e) {
    23. e.printStackTrace();
    24. }
    25. if (count == 10) {
    26. break;
    27. }
    28. }
    29. }
    30. }
    31. class T2 implements Runnable{
    32. int count=0;
    33. @Override
    34. public void run() {
    35. while (true) {
    36. System.out.println("hi "+(++count)+Thread.currentThread().getName());
    37. try {
    38. Thread.sleep(1000);
    39. } catch (InterruptedException e) {
    40. e.printStackTrace();
    41. }
    42. if (count == 5) {
    43. break;
    44. }
    45. }
    46. }
    47. }
    48. //hello, world! 1Thread-0
    49. //hi 1Thread-1
    50. //hello, world! 2Thread-0
    51. //hi 2Thread-1
    52. //hi 3Thread-1
    53. //hello, world! 3Thread-0
    54. //hello, world! 4Thread-0
    55. //hi 4Thread-1
    56. //hello, world! 5Thread-0
    57. //hi 5Thread-1
    58. //hello, world! 6Thread-0
    59. //hello, world! 7Thread-0
    60. //hello, world! 8Thread-0
    61. //hello, world! 9Thread-0
    62. //hello, world! 10Thread-0

    3、继承Thread和实现Runnable的区别:

    (1)从java的设计来看,通过继承Thread或实现Runnable接口来创建线程本质上没有区别,从jdk帮助文档我们可以看到Thread类本身就实现了Runnable接口,都是通过调用start()----->最终实现调用start0();

    (2)实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制,建议使用

    1. T3 t3 = new T3("Hello");//一个对象
    2. Thread thread01=new Thread(t3);
    3. Thread thread02=new Thread(t3);
    4. thread01.start();
    5. thread02.start();
    6. System.out.println("主线程完毕");

     4、

    1. //使用Thread
    2. package ticket;
    3. public class SellTicket {
    4. public static void main(String[] args) {
    5. SellTicket01 sellTicket1 = new SellTicket01();
    6. SellTicket01 sellTicket2 = new SellTicket01();
    7. SellTicket01 sellTicket3 = new SellTicket01();
    8. //三个售票窗口
    9. sellTicket1.start();
    10. sellTicket2.start();
    11. sellTicket3.start();
    12. }
    13. }
    14. class SellTicket01 extends Thread{
    15. private static int ticketNum=10;//让多个线程共享ticketNum
    16. public void run(){
    17. while(true){
    18. if (ticketNum <= 0) {
    19. System.out.println("售票结束");
    20. break;
    21. }
    22. try {
    23. Thread.sleep(50);
    24. } catch (Exception e) {
    25. e.printStackTrace();
    26. }
    27. System.out.println("窗口 "+Thread.currentThread().getName()+" 售出一张票"
    28. +" 剩余票数="+(--ticketNum));
    29. }
    30. }
    31. }
    32. //窗口 Thread-1 售出一张票 剩余票数=9
    33. //窗口 Thread-0 售出一张票 剩余票数=7
    34. //窗口 Thread-2 售出一张票 剩余票数=8
    35. //窗口 Thread-2 售出一张票 剩余票数=6
    36. //窗口 Thread-0 售出一张票 剩余票数=4
    37. //窗口 Thread-1 售出一张票 剩余票数=5
    38. //窗口 Thread-2 售出一张票 剩余票数=3
    39. //窗口 Thread-1 售出一张票 剩余票数=2
    40. //窗口 Thread-0 售出一张票 剩余票数=1
    41. //窗口 Thread-1 售出一张票 剩余票数=0
    42. //窗口 Thread-2 售出一张票 剩余票数=0
    43. //售票结束
    44. //窗口 Thread-0 售出一张票 剩余票数=-1
    45. //售票结束
    46. //售票结束
    47. //出现了超卖现象,因为最后的时候,三个线程同时进入到while循环,相差时间太短
    1. //使用Runnable
    2. package ticket;
    3. public class SellTicket {
    4. public static void main(String[] args) {
    5. SellTicket02 sellTicket = new SellTicket02();
    6. //三个售票窗口
    7. new Thread(sellTicket).start();
    8. new Thread(sellTicket).start();
    9. new Thread(sellTicket).start();
    10. }
    11. }
    12. //使用Thread
    13. class SellTicket02 implements Runnable{
    14. private static int ticketNum=10;//让多个线程共享ticketNum
    15. public void run(){
    16. while(true){
    17. if (ticketNum <= 0) {
    18. System.out.println("售票结束");
    19. break;
    20. }
    21. try {
    22. Thread.sleep(5000);
    23. } catch (Exception e) {
    24. e.printStackTrace();
    25. }
    26. System.out.println("窗口 "+Thread.currentThread().getName()+" 售出一张票"
    27. +" 剩余票数="+(--ticketNum));
    28. }
    29. }
    30. }
    31. //窗口 Thread-1 售出一张票 剩余票数=9
    32. //窗口 Thread-0 售出一张票 剩余票数=8
    33. //窗口 Thread-2 售出一张票 剩余票数=8
    34. //窗口 Thread-2 售出一张票 剩余票数=7
    35. //窗口 Thread-1 售出一张票 剩余票数=7
    36. //窗口 Thread-0 售出一张票 剩余票数=6
    37. //窗口 Thread-0 售出一张票 剩余票数=4
    38. //窗口 Thread-1 售出一张票 剩余票数=5
    39. //窗口 Thread-2 售出一张票 剩余票数=3
    40. //窗口 Thread-1 售出一张票 剩余票数=2
    41. //窗口 Thread-2 售出一张票 剩余票数=2
    42. //窗口 Thread-0 售出一张票 剩余票数=1
    43. //窗口 Thread-2 售出一张票 剩余票数=0
    44. //售票结束
    45. //窗口 Thread-1 售出一张票 剩余票数=-1
    46. //售票结束
    47. //窗口 Thread-0 售出一张票 剩余票数=0
    48. //售票结束
    49. //出现了超卖现象,因为最后的时候,三个线程同时进入到while循环,相差时间太短

    出现了问题:票数超卖,多个线程同时进入循环,都检测到了最后的一个资源,所以都卖了出去,出现了超卖现象。

    5、线程终止:

    (1)基本说明:

    1)当线程完成任务后,会自动退出

    2)还可以通过使用变量来控制run方法退出的方式停止线程,即通知方式

    (2)应用案例:

     6、线程常用方法:

     

    补:中断可以类比闹钟来理解

    1. //解释join:线程插队
    2. package threaduse;
    3. public class ThreadMethod02 {
    4. public static void main(String[] args) throws InterruptedException {
    5. T3 t3 = new T3();
    6. t3.start();
    7. for(int i=1;i<=20;i++){
    8. Thread.sleep(1000);
    9. System.out.println("主线程(小弟)吃了 "+i+" 包子");
    10. if(i==5){
    11. System.out.println("主线程(小弟)让了子线程(老大)先吃");
    12. t3.join();//这里相当于让t2线程先执行完毕
    13. System.out.println("子线程(老大)吃完了,主线程(小弟)接着吃...");
    14. }
    15. }
    16. }
    17. }
    18. class T3 extends Thread{
    19. public void run(){
    20. for(int i=1;i<=20;i++){
    21. try {
    22. Thread.sleep(1000);
    23. } catch (InterruptedException e) {
    24. e.printStackTrace();
    25. }
    26. System.out.println("子线程(老大)吃了 " +i+" 包子");
    27. }
    28. }
    29. }
    30. //主线程(小弟)吃了 1 包子
    31. //子线程(老大)吃了 1 包子
    32. //主线程(小弟)吃了 2 包子
    33. //子线程(老大)吃了 2 包子
    34. //子线程(老大)吃了 3 包子
    35. //主线程(小弟)吃了 3 包子
    36. //子线程(老大)吃了 4 包子
    37. //主线程(小弟)吃了 4 包子
    38. //子线程(老大)吃了 5 包子
    39. //主线程(小弟)吃了 5 包子
    40. //主线程(小弟)让了子线程(老大)先吃
    41. //子线程(老大)吃了 6 包子
    42. //子线程(老大)吃了 7 包子
    43. //子线程(老大)吃了 8 包子
    44. //子线程(老大)吃了 9 包子
    45. //子线程(老大)吃了 10 包子
    46. //子线程(老大)吃了 11 包子
    47. //子线程(老大)吃了 12 包子
    48. //子线程(老大)吃了 13 包子
    49. //子线程(老大)吃了 14 包子
    50. //子线程(老大)吃了 15 包子
    51. //子线程(老大)吃了 16 包子
    52. //子线程(老大)吃了 17 包子
    53. //子线程(老大)吃了 18 包子
    54. //子线程(老大)吃了 19 包子
    55. //子线程(老大)吃了 20 包子
    56. //子线程(老大)吃完了,主线程(小弟)接着吃...
    57. //主线程(小弟)吃了 6 包子
    58. //主线程(小弟)吃了 7 包子
    59. //主线程(小弟)吃了 8 包子
    60. //主线程(小弟)吃了 9 包子
    61. //主线程(小弟)吃了 10 包子
    62. //主线程(小弟)吃了 11 包子
    63. //主线程(小弟)吃了 12 包子
    64. //主线程(小弟)吃了 13 包子
    65. //主线程(小弟)吃了 14 包子
    66. //主线程(小弟)吃了 15 包子
    67. //主线程(小弟)吃了 16 包子
    68. //主线程(小弟)吃了 17 包子
    69. //主线程(小弟)吃了 18 包子
    70. //主线程(小弟)吃了 19 包子
    71. //主线程(小弟)吃了 20 包子
    1. //解释yeild:礼让,不一定成功
    2. package threaduse;
    3. public class ThreadMethod02 {
    4. public static void main(String[] args) throws InterruptedException {
    5. T3 t3 = new T3();
    6. t3.start();
    7. for(int i=1;i<=20;i++){
    8. Thread.sleep(1000);
    9. System.out.println("主线程(小弟)吃了 "+i+" 包子");
    10. if(i==5){
    11. System.out.println("主线程(小弟)让了子线程(老大)先吃");
    12. Thread.yield();//礼让,不一定成功...
    13. System.out.println("子线程(老大)吃完了,主线程(小弟)接着吃...");
    14. }
    15. }
    16. }
    17. }
    18. class T3 extends Thread{
    19. public void run(){
    20. for(int i=1;i<=20;i++){
    21. try {
    22. Thread.sleep(1000);
    23. } catch (InterruptedException e) {
    24. e.printStackTrace();
    25. }
    26. System.out.println("子线程(老大)吃了 " +i+" 包子");
    27. }
    28. }
    29. }
    30. //主线程(小弟)吃了 1 包子
    31. //子线程(老大)吃了 1 包子
    32. //子线程(老大)吃了 2 包子
    33. //主线程(小弟)吃了 2 包子
    34. //子线程(老大)吃了 3 包子
    35. //主线程(小弟)吃了 3 包子
    36. //子线程(老大)吃了 4 包子
    37. //主线程(小弟)吃了 4 包子
    38. //主线程(小弟)吃了 5 包子
    39. //主线程(小弟)让了子线程(老大)先吃
    40. //子线程(老大)吃了 5 包子
    41. //子线程(老大)吃完了,主线程(小弟)接着吃...
    42. //主线程(小弟)吃了 6 包子
    43. //子线程(老大)吃了 6 包子
    44. //子线程(老大)吃了 7 包子
    45. //主线程(小弟)吃了 7 包子
    46. //主线程(小弟)吃了 8 包子
    47. //子线程(老大)吃了 8 包子
    48. //子线程(老大)吃了 9 包子
    49. //主线程(小弟)吃了 9 包子
    50. //主线程(小弟)吃了 10 包子
    51. //子线程(老大)吃了 10 包子
    52. //主线程(小弟)吃了 11 包子
    53. //子线程(老大)吃了 11 包子
    54. //主线程(小弟)吃了 12 包子
    55. //子线程(老大)吃了 12 包子
    56. //主线程(小弟)吃了 13 包子
    57. //子线程(老大)吃了 13 包子
    58. //主线程(小弟)吃了 14 包子
    59. //子线程(老大)吃了 14 包子
    60. //主线程(小弟)吃了 15 包子
    61. //子线程(老大)吃了 15 包子
    62. //主线程(小弟)吃了 16 包子
    63. //子线程(老大)吃了 16 包子
    64. //主线程(小弟)吃了 17 包子
    65. //子线程(老大)吃了 17 包子
    66. //主线程(小弟)吃了 18 包子
    67. //子线程(老大)吃了 18 包子
    68. //主线程(小弟)吃了 19 包子
    69. //子线程(老大)吃了 19 包子
    70. //主线程(小弟)吃了 20 包子
    71. //子线程(老大)吃了 20 包子

    7、练习题:

     //我的代码

    1. package threaduse;
    2. public class ThreadMethodExercise {
    3. public static void main(String[] args) throws InterruptedException {
    4. T4 t4 = new T4();
    5. Thread thread = new Thread(t4);
    6. for(int i=1;i<=10;i++){
    7. System.out.println("hi "+i);
    8. if (i == 5) {
    9. thread.start();
    10. thread.join();
    11. }
    12. Thread.sleep(1000);
    13. }
    14. System.out.println("主线程结束...");
    15. }
    16. }
    17. class T4 implements Runnable{
    18. public void run(){
    19. for(int i=1;i<=10;i++){
    20. System.out.println("hello"+i);
    21. try {
    22. Thread.sleep(1000);
    23. } catch (InterruptedException e) {
    24. e.printStackTrace();
    25. }
    26. }
    27. System.out.println("子线程结束...");
    28. }
    29. }
    30. //hi 1
    31. //hi 2
    32. //hi 3
    33. //hi 4
    34. //hi 5
    35. //hello1
    36. //hello2
    37. //hello3
    38. //hello4
    39. //hello5
    40. //hello6
    41. //hello7
    42. //hello8
    43. //hello9
    44. //hello10
    45. //子线程结束...
    46. //hi 6
    47. //hi 7
    48. //hi 8
    49. //hi 9
    50. //hi 10
    51. //主线程结束...

    8、用户线程和守护线程:

    (1)用户线程:也叫工作线程,当线程的任务执行完或通知方式结束

    (2)守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束

    (3)常见的守护线程:垃圾回收机制

    1. package threaduse;
    2. public class ThreadMethod03 {
    3. public static void main(String[] args) throws InterruptedException {
    4. MyDaemonThread myDaemonThread = new MyDaemonThread();
    5. //如果我们希望当main线程结束后,子线程自动结束
    6. //只需将子线程设置守线程即可
    7. myDaemonThread.setDaemon(true);
    8. myDaemonThread.start();
    9. for(int i=1;i<=10;i++){
    10. System.out.println("宝强在辛苦地工作...");
    11. Thread.sleep(1000);
    12. }
    13. }
    14. }
    15. class MyDaemonThread extends Thread{
    16. public void run(){
    17. for(;;){//无限循环
    18. try {
    19. Thread.sleep(1000);
    20. } catch (InterruptedException e) {
    21. e.printStackTrace();
    22. }
    23. System.out.println("马蓉和宋喆居心叵测聊天,哈哈哈~~~");
    24. }
    25. }
    26. }
    27. //宝强在辛苦地工作...
    28. //马蓉和宋喆居心叵测聊天,哈哈哈~~~
    29. //宝强在辛苦地工作...
    30. //宝强在辛苦地工作...
    31. //马蓉和宋喆居心叵测聊天,哈哈哈~~~
    32. //马蓉和宋喆居心叵测聊天,哈哈哈~~~
    33. //宝强在辛苦地工作...
    34. //宝强在辛苦地工作...
    35. //马蓉和宋喆居心叵测聊天,哈哈哈~~~
    36. //马蓉和宋喆居心叵测聊天,哈哈哈~~~
    37. //宝强在辛苦地工作...
    38. //马蓉和宋喆居心叵测聊天,哈哈哈~~~
    39. //宝强在辛苦地工作...
    40. //宝强在辛苦地工作...
    41. //马蓉和宋喆居心叵测聊天,哈哈哈~~~
    42. //马蓉和宋喆居心叵测聊天,哈哈哈~~~
    43. //宝强在辛苦地工作...
    44. //马蓉和宋喆居心叵测聊天,哈哈哈~~~
    45. //宝强在辛苦地工作...
    46. //马蓉和宋喆居心叵测聊天,哈哈哈~~~

    9、线程的生命周期

     

     ·如上为官方的文档,所以线程一共有6种状态

    1. package threaduse;
    2. public class ThreadState {
    3. public static void main(String[] args) throws InterruptedException {
    4. T5 t5 = new T5();
    5. System.out.println(t5.getName()+" 状态"+ t5.getState());
    6. t5.start();
    7. while (Thread.State.TERMINATED != t5.getState()) {
    8. System.out.println(t5.getName()+" 状态"+ t5.getState());
    9. Thread.sleep(500);
    10. }
    11. System.out.println(t5.getName()+" 状态"+ t5.getState());
    12. }
    13. }
    14. class T5 extends Thread{
    15. public void run(){
    16. while (true) {
    17. for(int i=0;i<10;i++){
    18. System.out.println("hi "+i);
    19. try {
    20. Thread.sleep(1000);
    21. } catch (InterruptedException e) {
    22. e.printStackTrace();
    23. }
    24. }
    25. break;
    26. }
    27. }
    28. }
    29. //Thread-0 状态NEW
    30. //Thread-0 状态RUNNABLE
    31. //hi 0
    32. //Thread-0 状态TIMED_WAITING
    33. //hi 1
    34. //Thread-0 状态TIMED_WAITING
    35. //Thread-0 状态TIMED_WAITING
    36. //hi 2
    37. //Thread-0 状态TIMED_WAITING
    38. //Thread-0 状态TIMED_WAITING
    39. //hi 3
    40. //Thread-0 状态TIMED_WAITING
    41. //Thread-0 状态TIMED_WAITING
    42. //hi 4
    43. //Thread-0 状态TIMED_WAITING
    44. //Thread-0 状态TIMED_WAITING
    45. //hi 5
    46. //Thread-0 状态TIMED_WAITING
    47. //Thread-0 状态TIMED_WAITING
    48. //hi 6
    49. //Thread-0 状态TIMED_WAITING
    50. //Thread-0 状态TIMED_WAITING
    51. //hi 7
    52. //Thread-0 状态TIMED_WAITING
    53. //Thread-0 状态TIMED_WAITING
    54. //hi 8
    55. //Thread-0 状态TIMED_WAITING
    56. //Thread-0 状态TIMED_WAITING
    57. //hi 9
    58. //Thread-0 状态TIMED_WAITING
    59. //Thread-0 状态TIMED_WAITING
    60. //Thread-0 状态TERMINATED

    10、线程的同步:

    (1)在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性

    (2)或者理解:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作

    (3)

    (4)解决售票超卖问题:

    1. //使用Runnable
    2. package ticket;
    3. public class SellTicket {
    4. public static void main(String[] args) throws InterruptedException {
    5. SellTicket02 sellTicket = new SellTicket02();
    6. //三个售票窗口
    7. new Thread(sellTicket).start();
    8. new Thread(sellTicket).start();
    9. new Thread(sellTicket).start();
    10. }
    11. }
    12. //使用Thread
    13. class SellTicket02 implements Runnable{
    14. private static int ticketNum=100;
    15. private boolean loop=true;
    16. public boolean isLoop() {
    17. return loop;
    18. }
    19. public void setLoop(boolean loop) {
    20. this.loop = loop;
    21. }
    22. public synchronized void sell(){//同步方法,在同一时刻,只能有一个线程来执行sell方法
    23. if (ticketNum <= 0) {
    24. System.out.println("售票结束");
    25. loop=false;
    26. return;
    27. }
    28. try {
    29. Thread.sleep(50);
    30. } catch (Exception e) {
    31. e.printStackTrace();
    32. }
    33. System.out.println("窗口 "+Thread.currentThread().getName()+" 售出一张票"
    34. +" 剩余票数="+(--ticketNum));
    35. }
    36. public void run(){
    37. while(loop){
    38. sell();
    39. }
    40. }
    41. }
    42. //窗口 Thread-0 售出一张票 剩余票数=99
    43. //窗口 Thread-0 售出一张票 剩余票数=98
    44. //窗口 Thread-0 售出一张票 剩余票数=97
    45. //窗口 Thread-0 售出一张票 剩余票数=96
    46. //窗口 Thread-0 售出一张票 剩余票数=95
    47. //窗口 Thread-0 售出一张票 剩余票数=94
    48. //窗口 Thread-0 售出一张票 剩余票数=93
    49. //窗口 Thread-0 售出一张票 剩余票数=92
    50. //窗口 Thread-0 售出一张票 剩余票数=91
    51. //窗口 Thread-0 售出一张票 剩余票数=90
    52. //窗口 Thread-0 售出一张票 剩余票数=89
    53. //窗口 Thread-2 售出一张票 剩余票数=88
    54. //窗口 Thread-1 售出一张票 剩余票数=87
    55. //窗口 Thread-1 售出一张票 剩余票数=86
    56. //窗口 Thread-1 售出一张票 剩余票数=85
    57. //窗口 Thread-2 售出一张票 剩余票数=84
    58. //窗口 Thread-2 售出一张票 剩余票数=83
    59. //窗口 Thread-2 售出一张票 剩余票数=82
    60. //窗口 Thread-2 售出一张票 剩余票数=81
    61. //窗口 Thread-2 售出一张票 剩余票数=80
    62. //窗口 Thread-2 售出一张票 剩余票数=79
    63. //窗口 Thread-2 售出一张票 剩余票数=78
    64. //窗口 Thread-2 售出一张票 剩余票数=77
    65. //窗口 Thread-2 售出一张票 剩余票数=76
    66. //窗口 Thread-2 售出一张票 剩余票数=75
    67. //窗口 Thread-0 售出一张票 剩余票数=74
    68. //窗口 Thread-0 售出一张票 剩余票数=73
    69. //窗口 Thread-0 售出一张票 剩余票数=72
    70. //窗口 Thread-0 售出一张票 剩余票数=71
    71. //窗口 Thread-0 售出一张票 剩余票数=70
    72. //窗口 Thread-0 售出一张票 剩余票数=69
    73. //窗口 Thread-0 售出一张票 剩余票数=68
    74. //窗口 Thread-0 售出一张票 剩余票数=67
    75. //窗口 Thread-0 售出一张票 剩余票数=66
    76. //窗口 Thread-0 售出一张票 剩余票数=65
    77. //窗口 Thread-0 售出一张票 剩余票数=64
    78. //窗口 Thread-0 售出一张票 剩余票数=63
    79. //窗口 Thread-0 售出一张票 剩余票数=62
    80. //窗口 Thread-0 售出一张票 剩余票数=61
    81. //窗口 Thread-0 售出一张票 剩余票数=60
    82. //窗口 Thread-0 售出一张票 剩余票数=59
    83. //窗口 Thread-0 售出一张票 剩余票数=58
    84. //窗口 Thread-2 售出一张票 剩余票数=57
    85. //窗口 Thread-2 售出一张票 剩余票数=56
    86. //窗口 Thread-2 售出一张票 剩余票数=55
    87. //窗口 Thread-2 售出一张票 剩余票数=54
    88. //窗口 Thread-1 售出一张票 剩余票数=53
    89. //窗口 Thread-2 售出一张票 剩余票数=52
    90. //窗口 Thread-2 售出一张票 剩余票数=51
    91. //窗口 Thread-2 售出一张票 剩余票数=50
    92. //窗口 Thread-2 售出一张票 剩余票数=49
    93. //窗口 Thread-2 售出一张票 剩余票数=48
    94. //窗口 Thread-2 售出一张票 剩余票数=47
    95. //窗口 Thread-2 售出一张票 剩余票数=46
    96. //窗口 Thread-0 售出一张票 剩余票数=45
    97. //窗口 Thread-0 售出一张票 剩余票数=44
    98. //窗口 Thread-0 售出一张票 剩余票数=43
    99. //窗口 Thread-2 售出一张票 剩余票数=42
    100. //窗口 Thread-2 售出一张票 剩余票数=41
    101. //窗口 Thread-2 售出一张票 剩余票数=40
    102. //窗口 Thread-2 售出一张票 剩余票数=39
    103. //窗口 Thread-1 售出一张票 剩余票数=38
    104. //窗口 Thread-1 售出一张票 剩余票数=37
    105. //窗口 Thread-2 售出一张票 剩余票数=36
    106. //窗口 Thread-2 售出一张票 剩余票数=35
    107. //窗口 Thread-2 售出一张票 剩余票数=34
    108. //窗口 Thread-2 售出一张票 剩余票数=33
    109. //窗口 Thread-2 售出一张票 剩余票数=32
    110. //窗口 Thread-2 售出一张票 剩余票数=31
    111. //窗口 Thread-2 售出一张票 剩余票数=30
    112. //窗口 Thread-2 售出一张票 剩余票数=29
    113. //窗口 Thread-2 售出一张票 剩余票数=28
    114. //窗口 Thread-0 售出一张票 剩余票数=27
    115. //窗口 Thread-0 售出一张票 剩余票数=26
    116. //窗口 Thread-0 售出一张票 剩余票数=25
    117. //窗口 Thread-0 售出一张票 剩余票数=24
    118. //窗口 Thread-0 售出一张票 剩余票数=23
    119. //窗口 Thread-0 售出一张票 剩余票数=22
    120. //窗口 Thread-2 售出一张票 剩余票数=21
    121. //窗口 Thread-2 售出一张票 剩余票数=20
    122. //窗口 Thread-2 售出一张票 剩余票数=19
    123. //窗口 Thread-2 售出一张票 剩余票数=18
    124. //窗口 Thread-1 售出一张票 剩余票数=17
    125. //窗口 Thread-2 售出一张票 剩余票数=16
    126. //窗口 Thread-2 售出一张票 剩余票数=15
    127. //窗口 Thread-2 售出一张票 剩余票数=14
    128. //窗口 Thread-0 售出一张票 剩余票数=13
    129. //窗口 Thread-2 售出一张票 剩余票数=12
    130. //窗口 Thread-2 售出一张票 剩余票数=11
    131. //窗口 Thread-1 售出一张票 剩余票数=10
    132. //窗口 Thread-1 售出一张票 剩余票数=9
    133. //窗口 Thread-1 售出一张票 剩余票数=8
    134. //窗口 Thread-1 售出一张票 剩余票数=7
    135. //窗口 Thread-1 售出一张票 剩余票数=6
    136. //窗口 Thread-2 售出一张票 剩余票数=5
    137. //窗口 Thread-2 售出一张票 剩余票数=4
    138. //窗口 Thread-2 售出一张票 剩余票数=3
    139. //窗口 Thread-2 售出一张票 剩余票数=2
    140. //窗口 Thread-0 售出一张票 剩余票数=1
    141. //窗口 Thread-0 售出一张票 剩余票数=0
    142. //售票结束
    143. //售票结束
    144. //售票结束

    11、互斥锁:

     (1)基本介绍:
    1)Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。
    2)每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
    3)关键字synchronized来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问
    4)同步的局限性:导致程序的执行效率要降低
    5)同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象)
    6)同步方法(静态的)的锁为当前类本身。

    1. class SellTicket02 implements Runnable {
    2. //...
    3. //1、public synchronized void sell(){}就是一个同步方法
    4. public synchronized void sell01() {//同步方法,在同一时刻,只能有一个线程来执行sell方法,靠抢夺得到
    5. if (ticketNum <= 0) {
    6. System.out.println("售票结束");
    7. loop = false;
    8. return;
    9. }
    10. try {
    11. Thread.sleep(50);
    12. } catch (Exception e) {
    13. e.printStackTrace();
    14. }
    15. System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
    16. + " 剩余票数=" + (--ticketNum));
    17. }
    18. //2、这里锁在this对象
    19. public void sell02() {//同步方法,在同一时刻,只能有一个线程来执行sell方法,靠抢夺得到
    20. synchronized (this) {
    21. if (ticketNum <= 0) {
    22. System.out.println("售票结束");
    23. loop = false;
    24. return;
    25. }
    26. }
    27. try {
    28. Thread.sleep(50);
    29. } catch (Exception e) {
    30. e.printStackTrace();
    31. }
    32. System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
    33. + " 剩余票数=" + (--ticketNum));
    34. }
    35. //3、也可以在代码块上写synchronized,同步代码块
    36. Object object=new Object();
    37. public void sell03() {//同步方法,在同一时刻,只能有一个线程来执行sell方法,靠抢夺得到
    38. synchronized (object) {
    39. if (ticketNum <= 0) {
    40. System.out.println("售票结束");
    41. loop = false;
    42. return;
    43. }
    44. }
    45. try {
    46. Thread.sleep(50);
    47. } catch (Exception e) {
    48. e.printStackTrace();
    49. }
    50. System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
    51. + " 剩余票数=" + (--ticketNum));
    52. }
    53. //静态的同步方法的锁为当前类本身
    54. //1、
    55. public synchronized static void sell04() {}//锁是加在SellTicket02.class
    56. //2、
    57. public static void sell05(){
    58. synchronized(SellTicket02.class){
    59. System.out.println("sell05");
    60. }
    61. }
    62. //...
    63. }

    (2) 注意事项和细节
    1)同步方法如果没有使用static修饰:默认锁对象为this

    2)如果方法使用static修饰,默认锁对象:当前类.class

    3)实现的落地步骤:
      需要先分析上锁的代码
      选择同步代码块或同步方法
      要求多个线程的锁对象为同一个即可!

    12、线程的死锁

    (1)基本介绍:

    多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程是一定要避免死锁的发生

    1. package threaduse;
    2. public class DeadLock {
    3. public static void main(String[] args) {
    4. //模拟死锁现象
    5. DeadLockDemo A = new DeadLockDemo(true);
    6. A.setName("A线程");
    7. DeadLockDemo B = new DeadLockDemo(false);
    8. B.setName("B线程");
    9. A.start();
    10. B.start();
    11. }
    12. }
    13. class DeadLockDemo extends Thread{
    14. static Object o1=new Object();//保证多线程,共享一个对象,这里使用static
    15. static Object o2=new Object();
    16. boolean flag;
    17. public DeadLockDemo(boolean flag) {
    18. this.flag = flag;
    19. }
    20. public void run(){
    21. //下面业务逻辑分析:
    22. //1、如果flag为T,线程A就会先得到/持有o1对象锁,然后尝试去获取o2对象锁
    23. //2、如果线程A得不到o2对象锁,就会Blocked
    24. //3、如果flag为T,线程B就会先得到/持有o1对象锁,然后尝试去获取o2对象锁
    25. //4、如果线程B得不到o2对象锁,就会Blocked
    26. if (flag) {
    27. synchronized (o1){//对象互斥锁,下面就是同步代码
    28. System.out.println(Thread.currentThread().getName()+"进入1");
    29. synchronized (o2){//这里获得li对象的监视权
    30. System.out.println(Thread.currentThread().getName()+"进入2");
    31. }
    32. }
    33. }else{
    34. synchronized (o2){//对象互斥锁,下面就是同步代码
    35. System.out.println(Thread.currentThread().getName()+"进入3");
    36. synchronized (o1){//这里获得li对象的监视权
    37. System.out.println(Thread.currentThread().getName()+"进入4");
    38. }
    39. }
    40. }
    41. //我的理解:
    42. //有线程走if,拿到了o1的锁,等待着o2的锁,没拿到就没法释放资源,但同时有线程走else,拿到了o2的锁,等待着o1的锁,所以它们永远不可能等到对方
    43. }
    44. }
    45. //输出:(卡住了)
    46. //A线程进入1
    47. //B线程进入3

    13、释放锁:

    下面操作会释放锁:

    (1)当前线程的同步方法、同步代码块执行结束

    上厕所,完事出来

    (2)当前线程的同步代码块、同步方法中遇到break, return

    没有正常地完事,经理叫他修改bug,不得已出来

    (3)当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束

    没有正常地完事,发现忘带纸,不得已出来

    (4)当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁

    没有正常完事,觉得需要酝酿下,所以出来等会再进去

     下面操作不会释放锁:

    (1)线程执行同步代码块或同步方法时,程序调用Thread.sleep(), Thread.yield()方法暂停当前线程的执行,不会释放锁

    上厕所,太困了,在坑位上眯了一会

    (2)线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁

    提示:应尽量避免使用suspend()和resume()来控制线程,方法不再推荐使用

  • 相关阅读:
    数字孪生技术:金融业合规与自动化的未来
    ECR - Elastic Container Registry
    LeetCode刷题之HOT100之下一个排列
    数据结构和常用排序算法复杂度
    Identity Server 4资源拥有者密码认证控制访问API
    手机网页,输入时 软键盘盖住输入框完整解决方案,兼容安卓、鸿蒙、苹果IOS
    c++ openssl实现https
    对数的应用:放缩x轴或者y轴以更好地表达函数的结果
    java毕业设计基于Bootstrap的家具商城系统设计mybatis+源码+调试部署+系统+数据库+lw
    monaco-editor 的 Language Services
  • 原文地址:https://blog.csdn.net/weixin_72052233/article/details/128069475