思维导图:
1.创建线程:
- 继承Thread父类,重写run方法。run方法里面的逻辑就是这个线程要执行的工作。创建一个MyThread实例对象不会在系统中真的创建一个线程,只有调用start方法才会创建出新的线程,一直到run里的代码执行完,新的线程就会执行结束。main主线程和MyThread创建出来的新线程是宏观上并发的过程,俩边同时执行各自执行各自的。
1. class MyThread extends Thread{ 2. @Override 3. public void run() { 4. System.out.println("线程MyThread的run方法"); 5. } 6. } 7. 8. public class Demo1 { 9. 10. public static void main(String[] args) { 11. MyThread myThread = new MyThread(); 12. myThread.start(); 13. } 14. }
- 实现Runnable接口,重写run方法。这里把线程要干的工作和线程本身分开了,使用Runnable专门来表示线程要完成的工作,目的是为了解耦合!
1. class MyRunnable implements Runnable{ 2. @Override 3. public void run() { 4. System.out.println("线程MyRunnable的run方法"); 5. } 6. } 7. 8. public class Demo1 { 9. 10. public static void main(String[] args) { 11. MyRunnable myRunnable = new MyRunnable(); 12. Thread thread = new Thread(myRunnable); 13. thread.start(); 14. } 15. }
- 匿名内部类创建Thread对象
1. public static void main(String[] args) { 2. Thread thread = new Thread(){ 3. @Override 4. public void run() { 5. System.out.println("匿名内部类创建Thread对象"); 6. } 7. }; 8. thread.start(); 9. }
- 匿名内部类创建Runnable对象
1. public static void main(String[] args) { 2. Thread thread = new Thread(new Runnable() { 3. @Override 4. public void run() { 5. System.out.println("匿名内部类创建Runnable对象"); 6. } 7. }); 8. 9. thread.start(); 10. }
- lambda表达式
1. public static void main(String[] args) { 2. Thread thread = new Thread( ()-> { 3. System.out.println("lambda表达式创建线程"); 4. }); 5. thread.start(); 6. 7. }
2.Thread类:
- Thread常见的构造方法
方法 | 说明 |
Thread(); | 创建线程对象 |
Thread(Runnable); | 使用Runnable来创建 |
Thread(name); | 创建对象并命名 |
Thread(Runnable,name); | 使用Runnable来创建并命名 |
- Thread的常见属性。默认创建的线程是前台进程,前台进程会阻止进程退出,如果main运行完了,前台线程还没完,进程不会退出!若是后台线程,如果main等其他前台线程执行完了,这个时候即使后台线程还没有执行完,进程也会退出。当run方法执行完了,内核的线程就销毁了,但是Thread对象还在。
属性 | 方法 | 说明 |
ID | getId(); | 线程的唯一标识 |
名称 | getName(); | 构造方法指定的name |
状态 | getState(); | 线程的状态 |
优先级 | getPriority(); | |
是否是后台线程 | isDaemon(); | |
是否存活 | isAlive(); | run方法是否运行结束 |
是否被打断 | isInterrupted(); | 默认为false |
- 获取当前线程的引用:Thread.currentThread();
3.启动线程:
- 调用start()才会真正的创建线程,在内核里创建PCB。
- start和run的区别:直接调用run并没有创建线程,只是在原来的线程中运行代码;调用start则是创建线程,在新的线程中执行代码(和原来的线程是并发的)。
4.中断线程:
- 线程的中断本质上仍然是让run方法尽快结束,而不是run执行一半,强制结束。
- 方法1:自己直接定义一个标志位
1. public class Demo1 { 2. public static boolean isQuit = false; 3. public static void main(String[] args) { 4. Thread thread = new Thread(()->{ 5. while(!isQuit){ 6. System.out.println("hello thread!"); 7. try { 8. Thread.sleep(1000); 9. } catch (InterruptedException e) { 10. throw new RuntimeException(e); 11. } 12. } 13. System.out.println("thread线程执行完了!"); 14. 15. }); 16. thread.start(); 17. 18. 19. try { 20. Thread.sleep(1000); 21. } catch (InterruptedException e) { 22. throw new RuntimeException(e); 23. } 24. 25. isQuit = true; 26. System.out.println("设置让threat线程结束!"); 27. } 28. }
- 方法二:通过interrupt()方法,使用isInterrupted来判定,使用interrupt方法来进行触发中断。如果线程是Runnable状态则设置标志位为true,如果线程是阻塞状态sleep,则会抛出异常,不设置标志位! 在主线程中,通过thread.interrupt();来中断这个线程,如果满足条件则设置标志位true。
1. public class Demo1 { 2. 3. public static void main(String[] args) { 4. Thread thread = new Thread(()->{ 5. //while(!Thread.interrupted()){ 6. while(!Thread.currentThread().isInterrupted()){ 7. //currentThread是Thread类的静态方法 8. System.out.println("hello thread!"); 9. try { 10. Thread.sleep(1000); 11. } catch (InterruptedException e) { 12. // e.printStackTrace(); 13. 14. // [方式一] 立即结束线程 15. break; 16. 17. // [方式二] 啥都不做, 不做理会. 线程继续执行 18. 19. // [方式三] 线程稍后处理 20. // Thread.sleep(1000); 21. // break; 22. } 23. } 24. System.out.println("thread线程执行完了!"); 25. 26. }); 27. thread.start(); 28. 29. try { 30. Thread.sleep(1000); 31. } catch (InterruptedException e) { 32. e.printStackTrace(); 33. } 34. 35. thread.interrupt(); 36. System.out.println("设置让threat线程结束!"); 37. } 38. }
5.线程等待:
- 线程之间的调度顺序是不确定的,可以通过join方法来控制线程之间的结束顺序。
- 在main方法中调用thread.join();效果就是让main线程阻塞等待,等到thread执行完了,main才继续执行。
- 如果调用join之前,main线程已经结束了,此时就不需要阻塞等待了!
- join()不带时间的版本就是有点“不见不散”的意思,带时间的版本就是类似于“过时不候”。
6.休眠线程:
- sleep指定休眠的时间,让线程休息一会(阻塞一会)。操作系统管理这些线程的PCB的时候是有多个链表的。调用了sleep,则这个PCB就会被移动到另外的阻塞队列中,只有就绪队列才会参与调度。
- 当sleep的时间到了,PCB就会被移动回之前的就绪队列中,移回就绪队列不代表立即就能够上CPU上执行,还需要看系统什么时候能够调度到这个线程。
- sleep(1000),不一定真的休眠1000,一般要略多于1000。
7.线程的状态:
- NEW:Thread对象创建出来了,但是线程还没有创建。
- TERMINATED:内核的PCB销毁了,但是Thread对象还在。
- RUNNABLE:就绪状态(正在CPU上运行或者在就绪队列中排队)。
- TIMED_WAITING:按照一定的时间进行堵塞使用.sleep()。
- WAITING:特殊的阻塞状态,调用wait。
- BLOCKED:等待锁的时候进入的阻塞状态。
8.线程安全存在的问题:
看下面代码:正常情况下预期结果是10w,可每次的结果都不是。
1. class Counter{ 2. public int count = 0; 3. public void increase(){ 4. count++; 5. } 6. } 7. public class Demo1 { 8. private static Counter counter = new Counter(); 9. private static Counter counter2 = new Counter(); 10. 11. public static void main(String[] args) throws InterruptedException { 12. Thread thread1 = new Thread(()->{ 13. for (int i = 0; i < 50000; i++) { 14. counter.increase(); 15. } 16. }); 17. Thread thread2 = new Thread(()->{ 18. for (int i = 0; i < 50000; i++) { 19. counter.increase(); 20. } 21. }); 22. 23. thread1.start(); 24. thread2.start(); 25. 26. thread1.join(); 27. thread2.join(); 28. 29. System.out.println("Counter:"+ counter.count); 30. } 31. }
- 进行的count++操作,底层是由三条指令在CPU上完成的,第一是把内存的数据读取到CPU寄存器上->load;第二是把CPU的寄存器中的值进行+1->add;第三是把寄存器中的值写回到内存中->save。
- 由于当前的俩个线程修改的是一个变量,由于每次修改是三个步骤(不是原子的),由于线程之间的调度顺序不确定。因此俩个线程在真正执行这些操作的时候就会有多种执行的排列顺序!
- 整个线程调度过程中,执行的顺序都是随机的。由于在调度过程中出现串行执行的俩种情况次数和其他情况次数不确定,因此得到的结果就是不确定的值,最终结果不会超过10w。
(更多线程安全内容尽在下期博客~)
如果对您有帮助的话,
不要忘记点赞+关注哦,蟹蟹
如果对您有帮助的话,
不要忘记点赞+关注哦,蟹蟹
如果对您有帮助的话,
不要忘记点赞+关注哦,蟹蟹