Java基础——多线程基础

一、线程介绍

  1. 程序:是为完成特定任务,用某种语言编写的一组指令的集合。简单地说,就是我们写的代码
  2. 进程:
    1. 进程是指运行中的程序,比如我们使用qq,就启动了一个进程。操作系统会为该进程分配内存空间。当我们使用迅雷,又启动了一个进程,操作系统将为迅雷分配新的内存空间
    2. 进程是程序的一次执行过程,或是正在运行的一个程序,是动态过程:有它自身的产生、存在和消亡的过程
  3. 线程:
    1. 线程是由进程创建(比如我们启动了迅雷,它就是一个进程。我们又想通过迅雷下载文件,那么这个下载任务就是一个线程),是进程的一个实体。一个线程还可以创建另一个线程。不仅main线程(主线程)可以开启一个子线程,子线程也可以开启一个子线程
    2. 一个进程可以拥有多个线程(比如迅雷在同时下载多个文件,每个下载任务就是一个线程)
  4. 单线程:同一时刻,只允许执行一个线程
  5. 多线程:同一时刻,可以执行多个线程。比如:一个qq进程,可以同时打开多个聊天窗口。一个迅雷进程,可以同时下载多个文件 
  6. 并发:同一时刻,多个任务交替执行,造成一种“貌似同时”的错觉,简单地说,单核CPU实现的多任务就是并发(比如,一个人在同时做两件事,但是他只有一个大脑,并不能真的同时做两件事。他只能在两个任务之间来回切换,一会儿做做这个,一会儿做做那个。由于切换的速度极快,于是造成了一种在同时做两件事的错觉)
  7. 并行:同一时刻,多个任务同时执行。多核CPU可以实现并行(比如,有多个人,在同一时间,每个人都在做自己的事)。并发和并行是可以同时出现的,比如同一时间有很多人,每个人都在做自己的事情,而每个人又在来回切换地做多个事
  8.  获取当前电脑CPU个数地代码:
    package com.study;public class CpuNum {public static void main(String[] args) {Runtime runtime = Runtime.getRuntime();//底层是单例模式//获取当前电脑地CPU数量int cpuNums = runtime.availableProcessors();System.out.println("当前有CPU个数="+cpuNums);}
    }

二、线程使用

  1. 创建线程的两种方式:
    1. 继承Thread类,重写run方法(Thread类里的run方法实际上是实现了Runnable接口的run方法
    2. 实现Runnable接口,重写run方法
  2. 继承Thread创建线程:
    1. 线程应用案例1
      public class Thread01 {public static void main(String[] args) {//创建一个Cat对象,可以当作线程使用了Cat cat = new Cat();cat.start();//调用了run方法}
      }
      //当一个类继承了Thread类,该类的对象就可以当作一个线程使用
      class Cat extends Thread{@Overridepublic void run() {//重写run方法,写上自己的业务逻辑int times = 0;while (true) {if (times == 80){break;//当次数等于80时,就退出,这时线程也退出}//让线程每隔1秒。在控制台输出"喵喵,我是小猫咪"System.out.println("喵喵,我是小猫咪"+(++times));//让该线程休眠一秒try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
      }
    2. 当主线程(main线程)开启了一个子线程后,主线程不会阻塞,会继续往下执行。接下来,主线程和子线程会交替执行(单核CPU,并发)
      //测试代码
      package com.study;public class Thread01 {public static void main(String[] args) throws InterruptedException{//创建一个Cat对象,可以当作线程使用了Cat cat = new Cat();cat.start();//调用了run方法//说明:当main线程启动一个子线程后,主线程不会阻塞,会继续执行//这时,主线程和子线程会交替执行System.out.println("主线程继续执行"+Thread.currentThread().getName());for (int i = 0; i < 10; i++) {System.out.println("主线程 i=" + i);Thread.sleep(1000);}}
      }
      //当一个类继承了Thread类,该类的对象就可以当作一个线程使用
      class Cat extends Thread{@Overridepublic void run() {//重写run方法,写上自己的业务逻辑int times = 0;while (true) {if (times == 80){break;//当次数等于80时,就退出,这时线程也退出}//让线程每隔1秒。在控制台输出"喵喵,我是小猫咪"System.out.println("喵喵,我是小猫咪"+(++times)+"  线程名="+Thread.currentThread().getName());//让该线程休眠一秒try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
      }
    3. 主线程的名字就叫main,主线程开启的第一个子线程的名字叫Thread-0,其它以此类推......获取当前线程名的方法Thread.currentThread().getName()
    4. 可以使用JConsole(在idea下面的终端输入jconsole)监控线程的执行情况(如图,这就是一个进程,左下是它拥有的线程,当main线程里的程序执行完,main就从左下角消失。但是Thread-0还没执行完,所以它还在继续输出“喵喵”
    5. 当我们RunJava源程序的时候,就是开启了一个进程。然后它马上开启了main线程,这个主线程里又开启了Thread-0线程
    6. 只有当进程中所有线程都执行完毕,进程才会结束!!!为什么之前RunJava源程序,当main里的程序执行完毕,进程就退出了呢,因为进程中只有main这一个线程
    7. 为什么是通过start方法来开启子线程???(1)start方法最终会执行线程重写的run方法(2)run方法就是一个普通的方法,没有真正启动一个线程(3)start方法里的start0()方法才是真正开启线程的。start0()方法是一个本地方法(由JVM调用),native
    8. 如果我们想让两个程序同时(交替)执行,就让main线程开启一个子线程,然后一个程序写在main线程里,还有一个程序写在子线程的run方法里
    9. start()方法调用start0()方法后,该线程并不一定会立马执行,只是将线程变成了可运行状态。具体什么时候执行,取决于CPU,由CPU统一调度
  3. 使用Runnable接口创建线程说明:
    1. Java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时再用继承Thread类的方法来创建线程显然不可能了
    2. java设计者们提供了另外一个方式创建线程,就是通过实现Runnable接口来创建线程
  4. 使用Runnable接口创建线程的应用案例:
    package com.study;public class Thread02 {public static void main(String[] args) {T2 t2 = new T2();Thread thread = new Thread(t2);thread.start();//因为start方法是Thread类里定义的//所以只能由Thread类的对象,或它的子类的对象来调用//而此时T2没有继承Thread类}
    }
    class T2 implements Runnable{int times = 0;@Overridepublic void run() {while(true){//休眠一秒try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("hi!" + (++times));if(times == 10){break;}}}
    }
  5. 使用Runnable接口创建线程,因为它没有继承Thread类,所以就不能用它来调用start方法。而start方法又是Thread类里的,所以我们创建一个Thread对象,并在构造器里,把自己编写的线程类的对象传进去,然后通过这个Thread类的对象来调用start方法。这个设计模式叫作静态代理
  6. 代码模拟静态代理:
    //线程代理类,模拟了一个极简的Thread
    //它的一个有参构造器的参数是实现了Runnable接口的实现类的对象
    //ThreadProxy类的对象去调用start:start→start0→run(里面调用的是实现类对象重写的run方法)
    class ThreadProxy implements Runnable{private Runnable target = null;public ThreadProxy(Runnable target){this.target = target;}@Overridepublic void run() {if (target!=null){target.run();}}public void start(){start0();//这个方法真正实现类开启线程}public void start0(){run();}
    }
  7. 多个子线程案例(请编写一个程序,创建两个线程,一个线程每隔一秒输出“Hello World”,输出10次退出,一个线程每隔1秒输出“hi”,输出5次退出):
    package com.study;public class Thread03 {public static void main(String[] args) {T3 t3 = new T3();T4 t4 = new T4();Thread thread1 = new Thread(t3);Thread thread2 = new Thread(t4);thread1.start();thread2.start();System.out.println("线程继续");}
    }
    class T3 implements Runnable{@Overridepublic void run() {int times = 0;while(true){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("hello,world"+(++times)+"  子线程名="+Thread.currentThread().getName());if (times == 10){break;}}}
    }
    class T4 implements Runnable{@Overridepublic void run() {int times = 0;while(true){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("hi" + (++times)+"  子线程名="+Thread.currentThread().getName());if (times == 5){break;}}}
    }
  8. 继承Thread和实现Runnable的区别
    1. 从java的设计来看,通过继承Thread或者实现Runnable接口来创建线程本质上没有任何区别。Thread类本身就实现了Runnable接口
    2. 实现Runnable接口方式更适合多个线程共享一个资源的情况,并且避免了单继承的限制。什么是多个线程共享一个资源呢?看下图
  9. 使用多线程模拟三个窗口同时售票(继承Thread类,会出现问题,当剩余一张票时,被三个线程访问,但是它们都还没来得及做--操作):
    package com.study;public class SellTicket {public static void main(String[] args) {SellTicket01 sellTicket01 = new SellTicket01();SellTicket01 sellTicket02 = new SellTicket01();SellTicket01 sellTicket03 = new SellTicket01();//启动售票sellTicket01.start();sellTicket02.start();sellTicket03.start();}
    }
    class SellTicket01 extends Thread{private static int ticketNum = 100;//三个线程同时售票,所以要共享它,用static@Overridepublic void run() {while(true){if (ticketNum <=0 ){System.out.println("售票结束...");break;}//休眠50毫秒try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("窗口" + Thread.currentThread().getName() + "售出一张票,剩余票数=" + (--ticketNum));}}
    }

  10. 使用多线程模拟三个窗口同时售票(实现Runnable接口,还是会出现超卖现象,跟上面一样):
    package com.study;public class SellTicket_ {public static void main(String[] args) {SellTicket02 sellTicket02 = new SellTicket02();Thread thread1 = new Thread(sellTicket02);Thread thread2 = new Thread(sellTicket02);Thread thread3 = new Thread(sellTicket02);thread1.start();thread2.start();thread3.start();}
    }
    class SellTicket02 implements Runnable{private static int ticketNum = 100;//三个线程同时售票,所以要共享它,用static@Overridepublic void run() {while(true){if (ticketNum <=0 ){System.out.println("售票结束...");break;}//休眠50毫秒try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("窗口" + Thread.currentThread().getName() + "售出一张票,剩余票数=" + (--ticketNum));}}
    }
  11. 线程终止的基本说明:
    1. 当线程完成任务后,会自动退出
    2. 还可以通过使用变量控制run方法退出的方式停止线程,即通知线程退出
  12. 线程终止的应用案例(启动一个线程t,要求在main线程中去停止线程,请编程实现):
    package com.study;public class ThreadExit {public static void main(String[] args) {AThread st = new AThread();new Thread(st).start();//启动线程for (int i = 1; i <= 60; i++) {try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("main线程运行中" + i);if (i==30){st.setLoop(false);}}}
    }
    class AThread implements Runnable{boolean loop = true;//步骤一:定义标记变量,默认为true@Overridepublic void run() {while(loop){//步骤二:将loop作为循环条件try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("AThread运行中...");}}//步骤三:提供公共的set方法,用于更新looppublic void setLoop(boolean loop) {this.loop = loop;}
    }
  13. 通知线程终止的步骤:(1)在自己定义的线程类里设置一个可以终止run方法的变量(2)并为该变量提供公共的set方法,让它在main线程里可以被使用(3)main线程里调用set方法来控制变量

三、线程方法

  1. 线程常用方法第一组:

    setName设置线程名称
    getName返回该线程的名称
    start使该线程开始执行,Java虚拟机底层调用该线程的start0方法
    run调用线程对象的run方法
    setPriority更改线程的优先级
    getPriority获取线程的优先级
    sleep在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
    interrupt中断线程
  2. 注意事项和细节:

    1. 线程优先级的范围(MAX_PRIORITY:10,MIN_PRIORITY:1,NORM_PRIORITY:5)

    2. interrupt:中断线程,但并没有结束线程,所以一般用于中断正在休眠的线程(醒醒别睡了)

    3. sleep:Thread类的静态方法,是当前线程休眠

  3. 常用方法的测试代码:

    package com.study;public class ThreadMethod {public static void main(String[] args) {//测试相关的方法T t = new T();t.setName("jack");t.setPriority(Thread.MIN_PRIORITY);t.start();//启动子线程//主线程打印5个hi,然后就中断子线程的休眠for (int i = 0; i < 5; i++) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("hi "+i);}System.out.println("该线程的优先级是="+t.getPriority());t.interrupt();}
    }
    //我们自定义的线程类
    class T extends Thread{@Overridepublic void run() {//吃完100个包子,本来想休息20秒再继续吃,但是它被打断了,于是又只能继续吃while (true) {for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + "   吃包子~~~" + i);}try {System.out.println(Thread.currentThread().getName() + "   休眠中~~~");Thread.sleep(20000);//1秒=1000毫秒} catch (InterruptedException e) {System.out.println(Thread.currentThread().getName() + "被 interrupt了");}}}
    }
  4. 线程常用方法第二组:(1)yield:线程的礼让。让出CPU,让其它线程执行,但礼让的时间不确定,所以也不一定礼让成功(如果CPU或内存资源不是很紧张)(2)join:线程的插队。插队的线程一旦成功,则肯定先执行完插入线程的所有任务(可以这么理解:如果CPU在并发执行两个线程t1和t2,t1可以礼让或者强制让,比如到了某个时刻,轮到t1执行,但是t1想把执行机会让给t2,如果礼让,不一定让成功;如果强制让,一定能让成功。可以看成t2插队了

  5. 例如,如果在main线程中有t1.join()。那么,会让t1线程先执行完毕,main线程才继续执行

  6. 线程插队的案例:main线程创建一个子线程,每隔一秒输出hello,输出20次,主线程每隔一秒输出hi,输出20次。要求:两个线程同时执行,当主线程输出5次后,就让子线程运行完毕,主线程再继续(main方法可以浅浅理解成线程里的run方法)

    package com.study;public class ThreadMethod02 {public static void main(String[] args) throws InterruptedException {T1 t = new T1();Thread thread = new Thread(t);thread.start();for (int i = 0; i < 20; i++) {if (i==5){thread.join();}Thread.sleep(1000);System.out.println("hi");}}
    }
    class T1 implements Runnable{@Overridepublic void run() {for (int i = 0; i < 20; i++) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("hello");}}
    }
  7. 例如,(1)如果想让main线程礼让给t1线程,main里调用Thread.yield(),该方法是Thread类的静态方法(2)如果想让main线程强制让给t1线程,也就是t1插队,main里调用t1.join()

  8. 多线程插队练习:(1)主线程每隔一秒输出hi,一共输出10次(2)当输出到hi5时,启动一个子线程(要求实现Runnable),每隔一秒输出hello,等该线程输出10次hello后,退出(3)主线程继续输出hi,直到主线程退出

    package com.study;public class ThreadMethodExercise {public static void main(String[] args) throws InterruptedException {for (int i = 1; i <= 10; i++) {System.out.println("hi" + i);Thread.sleep(1000);if (i==5){T5 t5 = new T5();Thread thread = new Thread(t5);thread.start();//如果不插队,会两个线程交替执行thread.join();}}System.out.println("主线程退出...");}
    }
    class T5 implements Runnable{@Overridepublic void run() {for (int i = 1; i <= 10; i++) {System.out.println("hello" + i);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("子线程退出...");}
    }
  9. 用户线程:也叫工作线程,当线程的任务执行完或以通知方式结束
  10.  守护线程:一般是为工作线程服务的,当所有的用户线程(工作线程)结束,守护线程自动结束
  11. 常见的守护线程:垃圾回收机制
  12. 我们如何将一个线程设置成守护线程呢?(如果我们希望main线程结束后,子线程就自动结束,只需将子线程设置为守护线程即可)
    package com.study;public class ThreadMethod03 {public static void main(String[] args) throws InterruptedException{MyDaemonThread dt = new MyDaemonThread();//将dt设置成守护线程,当所有线程结束后(包括main线程),dt也就自动结束dt.setDaemon(true);dt.start();for (int i = 1; i <= 100; i++) {Thread.sleep(50);System.out.println("宝强辛苦工作----------------------"+i);}}
    }
    class MyDaemonThread extends Thread{@Overridepublic void run() {for (;;) {try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("宋哲与马蓉快乐聊天,哈哈哈~~~");}}
    }

四、线程生命周期

  1. JDK中,用Thread.State枚举表示了线程的几种状态(可以看源码)
  2. 线程状态:线程可以处于以下状态之一:
    1. NEW:尚未启动的线程处于此状态
    2. RUNNABLE:在Java虚拟机中执行的线程处于此状态
    3. BLOCKED:线程在Runnable状态下,如果在等待进入同步代码块的锁,就会BLOCKED。如果获取到了这把锁,它就会进入Runnable状态
    4. WAITING:线程在Runnable状态下,如果执行了o.wait(),t.join(),LockSupport.park()方法,就会进入WAITING状态

    5. TIMED_WAITING(超时等待):线程在Runnable状态下,如果执行了Thread.sleep(time),o.wait(time),t.join(time),LockSupport.park(time)等方法,它就会进入TIMED_WAITING状态

    6. TERMINATED:终止状态,线程运行结束

      //测试代码
      package com.study.state_;public class ThreadState_ {public static void main(String[] args) throws InterruptedException{T t = new T();System.out.println(t.getName() + " 状态 " + t.getState());//Thread-0 状态 NEWt.start();while(t.getState()!=Thread.State.TERMINATED){System.out.println(t.getName() + " 状态 " + t.getState());Thread.sleep(500);}System.out.println(t.getName() + " 状态 " + t.getState());}
      }
      class T extends Thread{@Overridepublic void run() {while (true) {for (int i = 0; i < 10; i++) {System.out.println("hi " + i);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}break;}}
      }
  3. 一旦线程调用了start方法,它就进入可运行状态Runnable。细分可以将Runnable分为两类:Ready(就绪)、Running(运行)。
  4. 如果线程在Running状态调用Thread.yield()方法,它就进入Ready状态。如果Running状态下的线程被挂起,它也会进入Ready状态。而线程在Ready状态下时,如果它被调度器选中执行,它会进入Running状态

五、Synchronized

  1. 线程同步机制:
    1. 在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多只有一个线程访问,以保证数据的完整性
    2. 也可以这样理解:线程同步,即当有一个线程在对内存进行操作时,其它线程都不能对这个内存进行操作,直到该线程操作完成,其他线程才能对该内存进行操作
  2. 同步的具体方法——Synchronized
    1. 同步代码块:
      synchronized (对象){  //得到对象的锁,才能操作同步代码//同一时刻只能被一个对象访问的代码
      }
    2. 同步方法(synchronized还可以放在方法声明中,表示整个方法为同步方法):
      public synchronized void m(){//同一时刻只能被一个线程访问的代码
      }
  3. 用同步来解决多窗口售票问题(如果通过实现Runnable接口的方法来创建线程,那么这三个线程就会叫同一个名字,不太合理。有点类似于,一个人找了三个代购帮他买东西,但本质上买东西的还是这个人。):
    package com.study.syn;public class SellTicket {public static void main(String[] args) {SellTicket03 sellTicket03 = new SellTicket03();Thread thread1 = new Thread(sellTicket03);Thread thread2 = new Thread(sellTicket03);Thread thread3 = new Thread(sellTicket03);thread1.start();thread2.start();thread3.start();}
    }
    class SellTicket03 implements Runnable{private int ticketNum = 100;//三个线程同时售票,所以要共享它private boolean loop = true;public synchronized void sell(){  //同步方法,在同一时刻,只能有一个线程来执行run方法if (ticketNum <=0 ){System.out.println("售票结束...");loop =false;return;}//休眠50毫秒try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("窗口" + Thread.currentThread().getName() + "售出一张票,剩余票数=" + (--ticketNum));}@Overridepublic void run() {while(loop){sell();}}
    }
  4. 当我试图通过,继承Thread类创建线程,还用了synchronized修饰sell方法。但是却没有解决超卖问题。原因是,继承Thread类创建线程,实际上创建了三个线程对象,而sell方法又不是共享的(没有被static修饰),也就是说这三个线程各自在访问自己的sell方法。而同步:是为了解决同一资源不能同时被多个线程访问,也就是说它是一个共享的资源,如果我们要用继承Thread类来创建线程,那我们要在sell方法前加static

六、互斥锁

  1. 分析同步原理:假如某一时刻有三个线程(t1、t2、t3)想访问一个同步代码块(同步方法),那么谁先抢到这把锁,谁就能访问这个资源。例如,现在是t1抢到了这把锁,它进去访问资源,在t1访问该资源的时候,其它线程就不能访问这个资源了。等t1访问完这个资源,就把锁还回去。然后这三个线程又同时抢这把锁......也就是说,谁抢到这把锁,谁就能执行(注意:这个锁是对象锁)
  2. 线程在Runnable状态下,如果要等待进入同步代码块的锁,那么它就会被阻塞(状态变成BLOCKED)。如果该线程获得锁,那它就会变成Runnable状态
  3. 互斥锁的基本介绍:
    1. Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性
    2. 同步的局限性:导致程序的执行效率要降低
    3. 同步方法(非静态的)的锁可以是this,也可以是其它对象(要求是同一个对象)
    4. 同步方法(静态的)的锁为当前类本身
  4. 锁就是一个对象。要求多个线程的锁对象是同一个(即多个线程必须抢同一把锁)即可!!!也就是说
    1. 如果我们通过实现Runnable接口的方式来创建线程,那么这个锁可以是this对象,也可以是自定义线程类中任意一个对象(这三个thread只是一个代理,本质上运作的还是sellTicket03,如果这三个thread线程去抢同一把锁,也就是在抢这个sellTicket03)
      package com.study.syn;public class SellTicket {public static void main(String[] args) {SellTicket03 sellTicket03 = new SellTicket03();Thread thread1 = new Thread(sellTicket03);Thread thread2 = new Thread(sellTicket03);Thread thread3 = new Thread(sellTicket03);thread1.start();thread2.start();thread3.start();}
      }
      class SellTicket03 implements Runnable{private int ticketNum = 100;private boolean loop = true;public void sell(){  synchronized (this){if (ticketNum <=0 ){System.out.println("售票结束...");loop =false;return;}//休眠50毫秒try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("窗口" + Thread.currentThread().getName() + "售出一张票,剩余票数=" + (--ticketNum));}}@Overridepublic void run() {while(loop){sell();}}
      }
    2. 如果我们通过继承Thread类的方式来创建线程,那么这个锁就是类名.class。(实际上也就是三个SellTicket对象,其实是这三个sellTicket在抢,那么锁必须是这三个对象都能访问到的,也就是说,这个锁也可以是自定义线程类里的静态对象)锁一般显式地写在同步代码块的()里
      package com.study.syn;public class SellTicket {public static void main(String[] args) {SellTicket03 s1 = new SellTicket03();SellTicket03 s2 = new SellTicket03();SellTicket03 s3 = new SellTicket03();s1.start();s2.start();s3.start();}
      }
      class SellTicket03 extends Thread{private static int ticketNum = 100;//三个线程同时售票,所以要共享它private static boolean loop = true;private static Object obj = new Object();public  static void sell(){  //同步方法,在同一时刻,只能有一个线程来执行run方法synchronized (obj){if (ticketNum <=0 ){System.out.println("售票结束...");loop =false;return;}//休眠50毫秒try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("窗口" + Thread.currentThread().getName() + "售出一张票,剩余票数=" + (--ticketNum));}}@Overridepublic void run() {while(loop){sell();}}
      }
  5. 个人理解:当我们用实现Runnable接口的方式来创建线程,其实操作的是同一个sellTicket,哪个thread抢到sellTicket,哪个thread就执行。当我们用继承Thread类的方式来创建线程,其实是创建了多个sellTicket,多个对象抢同一个资源,那么这个资源必须是SellTicket里静态的 

七、死锁

  1. 线程的死锁基本介绍:多个线程都占用了对方的资源,但不肯相让,导致了死锁
  2. 死锁的代码演示:
    package com.study.syn;public class DeadLock_ {public static void main(String[] args) {//模拟死锁现象DeadLockDemo A = new DeadLockDemo(true);DeadLockDemo B = new DeadLockDemo(false);A.setName("A线程");B.setName("B线程");A.start();B.start();}
    }
    class DeadLockDemo extends Thread{static Object o1 = new Object();//保证多个线程共享同一个对象static Object o2 = new Object();boolean flag;public DeadLockDemo(boolean flag){this.flag = flag;}@Overridepublic void run() {//1.如果flag为True,线程A就会先得到/持有o1对象锁,然后尝试去获取o2对象锁//2.如果线程A得不到o2对象锁,就会BLOCKED//3.如果flag为False,线程B就会先得到/持有o2对象锁,然后尝试去获取o1对象锁//4.如果线程B得不到o1对象锁。就会BLOCKEDif (flag){synchronized (o1){System.out.println(Thread.currentThread().getName() + "进入1");synchronized (o2){System.out.println(Thread.currentThread().getName() + "进入2");}}}else{synchronized (o2){System.out.println(Thread.currentThread().getName() + "进入3");synchronized (o1){System.out.println(Thread.currentThread().getName() + "进入4");}}}}
    }
  3.  下面的操作会释放锁:
    1. 当前线程的同步方法、同步代码块执行结束
    2. 当前线程在同步代码块、同步方法中遇到break、return
    3. 当前线程在同步1代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
    4. 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停、并释放锁
  4. 下面的操作不会释放锁:
    1. 线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法,暂停当前线程的执行,不会释放锁
    2. 线程执行同步代码块时,其它线程调用了该线程的suspend()方法,将该线程挂起,该线程不会释放锁(线程从Runnable状态出来,才叫释放锁,而Runnable状态又细分为两种:Ready和Running。但如果是从Runnable状态进入TIMED_WAITING状态,不会释放锁) 

八、线程作业

  1. 编程题1(1)在main方法中启动两个线程(2)第一个线程循环随机打印100以内的的整数(3)直到第二个线程从键盘读取了"Q"命令
    package HomeWork;import java.util.Scanner;public class Homework01 {public static void main(String[] args) {T1 t1 = new T1();T2 t2 = new T2(t1);t1.start();t2.start();}
    }
    class T1 extends Thread{private boolean loop = true;@Overridepublic void run() {//循环打印100以内的整数while(loop){//(int)(Math.random() * 100)生成了[0,99]之间的整数System.out.println((int)(Math.random() * 100) + 1);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}public void setLoop(boolean loop) {this.loop = loop;}
    }
    class T2 extends Thread{private T1 t;public T2(T1 t){this.t = t;}@Overridepublic void run() {//从键盘读取Scanner scanner = new Scanner(System.in);String next = scanner.next();if (next.equals("Q")){t.setLoop(false);}}
    }
  2. 编程题2(1)有2个用户分别从同一个卡上取钱(总额:10000)(2)每次都取1000,当余额不足时,就不能取款了(3)不能出现超取现象(线程同步问题)
    package HomeWork;public class Homework02 {public static void main(String[] args) {Account account = new Account();Thread thread1 = new Thread(account);Thread thread2 = new Thread(account);thread1.setName("第一个用户");thread2.setName("第二个用户");thread1.start();thread2.start();}
    }
    class Account implements Runnable{private int money = 10000;//余额为10000元public void withdraw(){//哪个用户抢到这把锁,就先判断卡里钱够不够,够的话就取钱,取完钱释放锁//如果钱不够,就退出这个循环while(true){synchronized (this){if (money < 1000){System.out.println("卡里余额不够,无法取钱...");break;}System.out.println(Thread.currentThread().getName() + "从卡中取走1000元,还剩" + (money -= 1000) + "元");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}public void run() {withdraw();}
    }

本笔记根据韩顺平零基础学Java课程学习整理而来 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/487447.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

在本地运行大语言模型

1&#xff0c;打开下面网站下载&#xff0c;软件 lm studio 2&#xff0c; 设置模型下载路径 3&#xff0c;没有魔法条件的人&#xff0c;去镜像网站下载模型的镜像文件 、 4&#xff0c;

JUC:Synchronized和锁升级

1. 面试题 谈谈你对Synchronized的理解Sychronized的锁升级你聊聊Synchronized实现原理&#xff0c;monitor对象什么时候生成的&#xff1f;知道monitor的monitorenter和monitorexit这两个是怎么保证同步的嘛&#xff1f;或者说这两个操作计算机底层是如何执行的偏向锁和轻量级…

网络知识:IP数据报知识详解

目录 一、IP数据报概念 二、IPV4数据报报头组成 三、IPV6数据报报头组成 今天给大家分享IP数据库相关的知识,希望对大家进一步了解IP协议提供一些帮助! 一、IP数据报概念 TCP/IP协议的网际层接收到传输层传递过来的数据单元,封装成向下(OSI模型的数据链路层、TCP/IP协…

消息中间件-Kafka2-3.9.0源码构建

消息中间件-Kafka2-3.9.0源码构建 1、软件环境 JDK Version 1.8Scala Version 2.12.0Kafka-3.9.0 源码包 下载地址&#xff1a;https://downloads.apache.org/kafka/3.9.0/kafka-3.9.0-src.tgzGradle Version > 8.8Apache Zookeeper 3.7.0 2、源码编译 打开源码根目录修改…

详解:HTTP/HTTPS协议

HTTP协议 一.HTTP是什么 HTTP&#xff0c;全称超文本传输协议&#xff0c;是一种用于分布式、协作式、超媒体信息系统的应用层协议。HTTP往往是基于传输层TCP协议实现的&#xff0c;采用的一问一答的模式&#xff0c;即发一个请求&#xff0c;返回一个响应。 Q&#xff1a;什…

vue中pdf.js的使用,包括pdf显示,跳转指定页面,高亮关键词

目录 一、下载pdf.js 二、引入到本地的项目中 三、实现预览pdf 四、跳转到指定页面 五、利用pdf里面的find查找关键词并可以监听updatefindcontrolstate统计个数 六、修改页面大小为实际大小 七、每次加载pdf都是在第一页 八、修改pdf滚动方式为横向 九、清除pdf缓存 十、pdf.j…

题海拾贝:力扣 231. 2 的幂

Hello大家好&#xff01;很高兴我们又见面啦&#xff01;给生活添点passion&#xff0c;开始今天的编程之路&#xff01; 我的博客&#xff1a;<但凡. 我的专栏&#xff1a;《编程之路》、《题海拾贝》、《数据结构与算法之美》 欢迎点赞&#xff0c;关注&#xff01; 目录 …

多级IIR滤波效果(BIQUAD),system verilog验证

MATLAB生成IIR系数 采用率1k&#xff0c;截止频率30hz&#xff0c;Matlab生成6阶对应的biquad3级系数 Verilog测试代码 // fs1khz,fc30hz initial beginreal Sig_Orig, Noise_white, Mix_sig;real fs 1000;Int T 1; //周期int N T*fs; //1s的采样点数// 数组声明…

【实战教程】使用YOLO和EasyOCR实现视频车牌检测与识别【附源码】

《------往期经典推荐------》 一、AI应用软件开发实战专栏【链接】 项目名称项目名称1.【人脸识别与管理系统开发】2.【车牌识别与自动收费管理系统开发】3.【手势识别系统开发】4.【人脸面部活体检测系统开发】5.【图片风格快速迁移软件开发】6.【人脸表表情识别系统】7.【…

word poi-tl 图表功能增强,插入图表折线图、柱状图、饼状图

目录 问题解决问题poi-tl介绍 功能实现引入依赖功能介绍 功能实例饼图模版代码效果图 雷达图&#xff08;模版同饼图&#xff09;代码效果图 柱状图&#xff08;模版同饼图&#xff09;代码效果图 附加CustomCharts 工具类CustomChartSingleSeriesRenderData 数据对象CustomCha…

树莓集团是如何链接政、产、企、校四个板块的?

树莓集团作为数字影像行业的积极探索者与推动者&#xff0c;我们通过多维度、深层次的战略举措&#xff0c;将政、产、企、校四个关键板块紧密链接在一起&#xff0c;实现了资源的高效整合与协同发展&#xff0c;共同为数字影像产业的繁荣贡献力量。 与政府的深度合作政府在产业…

SQL 计算字段:算术计算

计算字段的一种常见用途是对检索出的数据进行算术计算。举个例子&#xff0c;假设 Orders 表记录了所有订单信息&#xff0c;而 OrderItems 表则记录了每个订单中的物品详情。以下 SQL 语句查询订单号为 20008 的所有物品&#xff1a; SELECT prod_id, quantity, item_price …

Apache-HertzBeat 开源监控默认口令登录

0x01 产品描述: HertzBeat(赫兹跳动) 是一个开源实时监控系统,无需Agent,性能集群,兼容Prometheus,自定义监控和状态页构建能力。HertzBeat 的强大自定义,多类型支持,高性能,易扩展,希望能帮助用户快速构建自有监控系统。0x02 漏洞描述: HertzBeat(赫兹跳动) 开源实时…

反向代理-缓存篇

文章目录 强缓存一、Expires(http1.0 规范)二、cache-control(http1.1 出现的 header 信息)Cache-Control 的常用选项Cache-Control 常用选项的选择三、弊端协商缓存一、ETag二、If-None-Match三、Last-modified四、If-Modified-Since浏览器的三种刷新方式静态资源部署策略…

element Plus中 el-table表头宽度自适应,不换行

在工作中&#xff0c;使用el-table表格进行开发后&#xff0c;遇到了小屏幕显示器上显示表头文字会出现换行展示&#xff0c;比较影响美观&#xff0c;因此需要让表头的宽度变为不换行&#xff0c;且由内容自动撑开。 以下是作为工作记录&#xff0c;用于demo演示教程 先贴个…

从单体到微服务:如何借助 Spring Cloud 实现架构转型

一、Spring Cloud简介 Spring Cloud 是一套基于 Spring 框架的微服务架构解决方案&#xff0c;它提供了一系列的工具和组件&#xff0c;帮助开发者快速构建分布式系统&#xff0c;尤其是微服务架构。 Spring Cloud 提供了诸如服务发现、配置管理、负载均衡、断路器、消息总线…

PostgreSQL 安装部署系列:使用YUM 方式在Centos 7.9 安装指定 PostgreSQL -15版本数据库

一、前言 千里之行始于足下&#xff0c;想学习一门数据库&#xff0c;首先要从安装部署开始&#xff0c;先拥有一套属于自己的学习测试库。为了更好的学习该数据库&#xff0c;可以选择一个在企业界使用率比较普及的操作系统&#xff0c;选择稳定版本的操作系统&#xff1b;如果…

Mac上基于pyenv管理Python多版本的最佳实践

首先声明&#xff0c;你可以选择使用 Homebrew 来安装pyenv。我这里主要是想和我 Linux 设备上一致&#xff0c;所以选择使用脚本来安装pyenv。 准备安装脚本 这个安装的脚本来源于官方的的github仓库。 关于安装脚本的解读请看《pyenv 安装脚本解读》。 pyenv-installer.sh …

创建型设计模式

一、设计模式介绍 1.设计模式是什么 设计模式是指在软件开发中&#xff0c;经过验证的&#xff0c;用于解决在特定环境下&#xff0c;重复出现的&#xff0c;特定问题的解决方案&#xff1b; 2.设计模式怎么来的&#xff1f; 满足设计原则后&#xff0c;慢慢迭代出来的。 3.设…

Linux系统下常用资源查看

一、查看CPU使用率 top 命令 top命令可以看到总体的系统运行状态和cpu的使用率 。 %us&#xff1a;表示用户空间程序的cpu使用率&#xff08;没有通过nice调度&#xff09; %sy&#xff1a;表示系统空间的cpu使用率&#xff0c;主要是内核程序。 %ni&#xff1a;表示用户空间且…