Day24_多线程第一天

时间:2023-12-10 13:49:44

1、线程

1、概述

     宏观来讲
     进程:就是正在运行的程序
     线程:就是进程的执行路径,执行单元

2、创建并启动线程的两种方式(掌握)
     1、定义一个类继承Thread类
  1. package cn.itcast.createThread;
  2. class MyThread extends Thread{
  3. @Override
  4. public void run() {
  5. System.out.println("线程在运行");
  6. }
  7. }
  8. public class Demo1 {
  9. public static void main(String[] args) {
  10. MyThread mt = new MyThread();
  11. //启动线程
  12. mt.start();
  13. }
  14. }

     2、定义一个类实现Runnable接口,并且重写run()方法
  1. package cn.itcast.createThread;
  2. class MyRunnable implements Runnable {
  3. @Override
  4. public void run() {
  5. System.out.println("启动了");
  6. }
  7. }
  8. public class Demo {
  9. public static void main(String[] args) {
  10. //第一步:创建实现了Runnable接口的子类对象
  11. MyRunnable mr = new MyRunnable();
  12. //第二步:通过刚创建的mr对象创建Thread对象
  13. Thread thread = new Thread(mr);
  14. //第三步:启动线程
  15. thread.start();
  16. }
  17. }

3、给线程设置名字(掌握)
    1、通过Thread类的有参构造
    2、通过Thread类的setName方法
    3、通过Thread.currentThread().setName()方法
  1. public class MyThread extends Thread{
  2. @Override
  3. public void run() {
  4. //设置线程名
  5. Thread.currentThread().setName("线程A");
  6. System.out.println(Thread.currentThread().getName());
  7. }
  8. public static void main(String[] args) {
  9. MyThread mt = new MyThread();
  10. mt.start();
  11. }
  12. }
  1. package cn.itcast.create;
  2. public class MyRunnable implements Runnable{
  3. @Override
  4. public void run() {
  5. //设置线程名
  6. Thread.currentThread().setName("线程A");
  7. System.out.println(Thread.currentThread().getName());
  8. }
  9. public static void main(String[] args) {
  10. Thread t = new Thread(new MyRunnable());
  11. t.start();
  12. }
  13. }



4、获取线程的名字(掌握)
    1、通过Thread类的getName方法
    2、通过Thread.currentThread().getName()方法

5、线程的随机性原理
     多个程序实际是通过CPU在做高效切换实现的

6、线程的声明周期(了解)
     新建 --> 就绪 --> 运行 -->阻塞 --> 死亡
     这里要注意,线程阻塞后就无法执行,回到就绪状态


Day24_多线程第一天

2、卖票案例(掌握)

1、有问题的代码(出现错误票)

  1. /**
  2. 卖票程序
  3. */
  4. public class TicketRunnable implements Runnable{
  5. private int tickets = 100;
  6. @Override
  7. public void run() {
  8. while(true){
  9. if(tickets > 0){
  10. try {
  11. Thread.sleep(1000);//必须加这个,由于CPU太快否则不一定出现负数-----语句1
  12. } catch (InterruptedException e) {
  13. e.printStackTrace();
  14. }
  15. System.out.println(Thread.currentThread().getName()+"正在卖第"+tickets--+"张的票");---语句2
  16. }else{
  17. break;//必须加这个,否则无法跳出循环,造成死机
  18. }
  19. }
  20. }
  21. }
  22. /**
  23. 测试类
  24. */
  25. public class TicketDemo {
  26. public static void main(String[] args) {
  27. TicketRunnable runnable = new TicketRunnable();
  28. Thread t1 = new Thread(runnable, "窗口1");
  29. Thread t2 = new Thread(runnable, "窗口2");
  30. Thread t3 = new Thread(runnable, "窗口3");
  31. t1.start();
  32. t2.start();
  33. t3.start();
  34. }
  35. }

2、产生问题的原因
     比如现在只剩一张票了tickets=1,现在有两个线程,线程1和线程2
     线程1先执行,判断tickets >0,执行线程1的语句1,然后被休眠1s
     在这个时候线程2抢到了执行权,首先判断tickets>0,继续往下走,执行线程2的语句1,然后被休眠1秒
     在线程2休眠的时候,线程1醒了,执行语句1,然后线程1停止,这时候tickets=0
     线程2醒了,执行语句2,这时候tickets=-1


3、如何查找问题(掌握)
     1、看有没有共享数据
     2、看操作共享数据的语句是不是多条语句
     3、看是不是在多线程的环境中

     最后,把操作共享数据的多条语句用 锁起来,注意我们的这把锁必须被多个线程所共享

4、修改后的代码(掌握)
  1. 修改后的代码/卖票案例完整代码
  2. public class TicketRunnable implements Runnable {
  3. private int tickets = 50;
  4. private Object lock = new Object();
  5. @Override
  6. public void run() {
  7. while (true) {
  8. synchronized (lock) {
  9. if (tickets > 0) {
  10. try {
  11. Thread.sleep(100);
  12. } catch (InterruptedException e) {
  13. e.printStackTrace();
  14. }
  15. System.out.println(Thread.currentThread().getName()+ "正在卖第" + tickets-- + "张的票");
  16. } else {
  17. break;
  18. }
  19. }
  20. }
  21. }
  22. }

3、锁,同步代码块,同步方法(这块记住结论就好,根据自身情况自行测试)

1、同步代码块
     synchronized(锁对象){
          需要被锁的代码//线程只有拿到了锁对象,才能执行这里的代码!!!换言之,这里的代码如果执行了,说明该线程拿到了锁对象,其他线程不能拿到该锁对象
     }

     注意
          多个线程必须使用同一个锁对象,要不然锁无效

2、同步方法(掌握)
     public synchronized void show(){}               //普通方法的锁是this
     public static synchronized void show(){}    //静态方法的锁是当前类的字节码文件对象 类名.class

3、注意问题(掌握)
     多个线程必须使用同一个锁对象,要不然锁无效
     同步代码块锁可以是任意对象
     同步方法的锁是this
     静态方法的锁是当前类的字节码文件对象 类名.class 

4、什么时候用同步代码块,什么时候用同步方法
     尽可能用同步代码块
     如果一个方法内的所有代码都被同步代码块包住了,那就用同步方法就可以了

4、死锁(掌握)

死锁原因总结
     线程1自身拿着一个锁:A锁,线程2自身拿着一个锁:B锁
     当线程1要用B锁,线程B要用A锁的时候就会发生死锁
  1. /**锁对象*/
  2. public class Lock {
  3. public static final Object LOCK_A = new Object();
  4. public static final Object LOCK_B = new Object();
  5. }
  6. /**线程1*/
  7. public class Thread1 extends Thread {
  8. @Override
  9. public void run() {
  10. synchronized (Lock.LOCK_A) {
  11. System.out.println("我是线程1,已经拿到A锁,将要去哪B锁");
  12. synchronized (Lock.LOCK_B) {
  13. System.out.println("我是线程1,成功拿到B锁");
  14. }
  15. }
  16. }
  17. }
  18. /**线程2*/
  19. public class Thread2 extends Thread {
  20. @Override
  21. public void run() {
  22. synchronized (Lock.LOCK_B) {
  23. System.out.println("我是线程2,已经拿到B锁,将要去哪A锁");
  24. synchronized (Lock.LOCK_A) {
  25. System.out.println("我是线程2,成功拿到A锁");
  26. }
  27. }
  28. }
  29. }
  30. /**测试代码*/
  31. public class Test {
  32. public static void main(String[] args) {
  33. Thread1 t1= new Thread1();
  34. Thread2 t2= new Thread2();
  35. t1.start();
  36. t2.start();
  37. }
  38. }
  39. //注意:以上代码可能不会死锁,如果必须产生死锁效果将run()方法中的所有内容用while(true)包裹起来

5、休眠线程(掌握)

Thread.sleep(毫秒,纳秒), 控制当前线程休眠若干毫秒1秒= 1000毫秒 1秒 = 1000 * 1000 * 1000纳秒 1000000000

  1. package cn.itcast.createThread;
  2. public class MyThread extends Thread{
  3. @Override
  4. public void run() {
  5. try {
  6. Thread.sleep(1000);
  7. } catch (InterruptedException e) {
  8. e.printStackTrace();
  9. }
  10. }
  11. }

6、守护线程

setDaemon(), 设置一个线程为守护线程, 该线程不会单独执行, 当其他非守护线程都执行结束后, 自动退出
  1. package cn.itcast.createThread;
  2. public class MyThread extends Thread{
  3. @Override
  4. public void run() {
  5. while(true){
  6. System.out.println("xx");
  7. }
  8. }
  9. public static void main(String[] args) throws Exception {
  10. MyThread mt = new MyThread();
  11. mt.setDaemon(true);
  12. mt.start();
  13. //主线程在1S后结束,结束后mt线程也随之停止
  14. Thread.sleep(1000);
  15. }
  16. }

7、加入线程

  1. package cn.itcast.createThread;
  2. public class MyThread extends Thread{
  3. @Override
  4. public void run() {
  5. while(true){
  6. System.out.println("xx");
  7. }
  8. }
  9. public static void main(String[] args) throws Exception {
  10. MyThread mt = new MyThread();
  11. mt.start();
  12. mt.join();
  13. //因为mt线程被设置为加入线程,所以只有当mt线程执行完后你好才会执行
  14. System.out.println("你好");
  15. }
  16. }


8、设置线程优先级

1、线程优先级级别

线程默认优先级是5。范围是1-10

     Thread.MAX_PRIORITY         //10
     Thread.MIN_PRIORITY         //1
     Thread.NORM_PRIORITY     //5

2、方法
     public final int getPriority():获取线程优先级
     public final void setPriority(int newPriority):更改线程的优先级

3、注意
     优先级可以在一定的程度上,让线程获较多的执行机会

4、举例
     MyThread t = new MyThread();
     System.out.println(t.getPriority());
     t.setPriority(Thread.MAX_PRIORITY);



sleep(...) 线程休眠多久
join(..)  执行多久后就不插队了
wait(..) 多久后开始等待


13、今天必须掌握的内容,面试题,笔试题。(掌握这个就可以放心学习后面的知识了)

1、说说线程和进程
2、说说创建线程的两种方式,分别在什么情况下使用
3、如何启动线程
4、如何给线程设置名字,如何获取线程的名字
5、请描述下线程的生命周期
6、多个线程访问同一数据如果发生数据异常,我们应该如何查找问题,找到问题后应该如何处理
7、同步代码块的锁是什么,同步方法的锁是什么,静态同步方法的锁是什么
8、说说死锁原理
9、如何让线程进入休眠状态
10、sleep和wait的区别(都释放CPU执行权,sleep不释放锁,wait释放锁)
11、一个线程的优先级越高,那么这个线程就一定先执行完毕其他线程才能执行,错还是对,为什么
12、代码题:卖票案例,增加需求:统计每个窗口各卖了多少张票。(分别用线程的两种方式实现)
13、代码题:死锁案例