Java多线程全知识(除线程池外)

多线程

进程与线程

  • 一个进程有多个线程
  • 进程之间相互隔离,线程之间可以相互通信
  • cpu一个时间点只能执行一个线程,但多个线程之间的切换比较快,给人一种多个线程同时执行的错觉

实现多线程的3种方法

  1. 继承Thread类(不推荐)

    • 创建类,继承Thread类
    • 重写run()方法
    • 创建对象,使用start()方法启动线程

    Java有单继承的局限性,尽量不使用这种方法

  2. 实现Runnable接口(推荐使用)

    • 创建类,实现Runnable接口
    • 重写run()方法
    • 创建对象,并将其作为Thread类的构造参数,创建Thread类,调用start()方法启动线程

    这种方法避免使用继承,缓解了Java单继承的局限性,并且使用静态代理,可以节约资源

    > 使用到了静态代理,一个资源(对象),多次代理(Thread类代理、多个线程使用一个对象)

  3. 实现Callable接口(投入工作后会使用到,前期不使用)

线程的状态以及操作

状态

![线程状态图]()

  • 准备状态:当new一个线程(new Thread())时,进入准备状态
  • 就绪状态

    • 事件1:线程调用start()方法,启动线程,进入就绪状态,开始和其他线程抢占cpu资源
  • 运行状态

    • 事件2:通过调度算法,线程被cpu调度,获取到cpu资源,进程线程内的逻辑操作,进入运行状态
  • 阻塞状态

    • 事件3:线程调用sleep、wait方法或者同步锁定时,当前线程进入阻塞状态
    • 事件4:阻塞时间结束后,则重新进入准备就绪状态,开始抢占cpu资源
  • 终止(死亡)状态

    • 事件5:线程被停止、线程逻辑操作执行完毕等事件发生后,线程进入终止状态
    • 一旦进入死亡状态的线程将不能被再次启动

操作

  • 线程停止

    • 不建议使用stop()、destory()等方法,容易造成为止的错误
    • 建议设置一个Boolean值进行控制,要停止线程时,就将其切换为false
  • 线程礼让

    • yield(),线程调用该方法后进入就绪状态,重新和其他线程抢占cpu
  • 线程休眠

    • sleep(),线程调用该方法后进入阻塞状态,过了设定的时间后,进入继续状态,和其他线程抢占cpu
  • 线程强制执行

    • join(),线程调用该方法后立即进入运行状态,之前正在运行的线程进入阻塞状态

线程状态观测

  • 使用Thread.getState()来获取该线程的状态
  • 线程状态有几个常量

    • NEW:尚未启动的线程处于此状态[刚new出来的线程]
    • RUNNABLE:在Java虚拟机中执行的线程处于此状态[调用start()方法后的线程]
    • BLOCKED:被阻塞等待监视器锁定的线程处于此状态
    • WAITING:正在等待另一个线程执行特定动作的线程处于此状态
    • TIME_WAITING:正在等待另一个线程执行动作达到指定等待时间的线程处于此状态[调用了sleep的线程]
    • TERMINATED:已退出的线程处于此状态

线程优先级

  • getPriority、setPriority分别用来获得和设置线程的优先级
  • 线程优先级的范围在1-10,MIN_PRIORITY=1, MAX_PRIORITY=10, (默认优先级)NORM_PRIORITY=5
  • 设置优先级要在线程start之前设置,不然无效
  • 优先级高的不一定先执行,只是被执行的概率更高,同时这会引发一个性能倒置的问题

守护线程

  • Java虚拟机只需要保证执行完用户线程(非守护线程)
  • setDaemon(true),即可使该线程成为守护线程,默认为false

线程同步

多个线程操作同一个资源,会导致一些不安全的事件发生。例如,银行取钱,两个人同时取一个银行卡的钱,两人取钱的总额大于银行卡余额,结果余额就会出现负数。这就是一种严重的错误。

所以我们要用到锁和队列

当一个线程操作一个资源的时候,给他一个锁,这样来防止同一时间有其他线程来操作这个资源。在使用过资源后,释放锁,交由下一个线程。也就形成了一个队列。

加锁

  1. 使用synchronized关键字

    > 每个对象都有一把锁

    1. 对方法加锁

      public synchronized void method(){

      逻辑代码

      }

      这样就实现对方法加锁,在线程调用这个方法的时候,就会默认使用this对象(线程对象)来加锁

    2. 代码块加锁

      @Override

      public void run(){

      synchronized (一个对象,一般使用被操作的资源){

      逻辑代码

      }

      }

      当运行到代码块时,括号里的对象将会被锁上,逻辑代码执行完之前,其他线程不能访问

  2. 使用Lock(接口)锁

    > ReentrantLock类实现了Lock接口

    1. 使用方法

      class testLock implements Runnable{

      private final ReentrantLock lock =new ReentrantLock();

      @Override

      public void run() {

      while(true){

      try {

      lock.lock();//加锁

      逻辑代码

      }finally {

      lock.unlock();//解锁

      }

      }

    > synchronized与Lock的区别,前者是隐性的上锁开锁,在进入被其修饰的代码时自动上锁,在跳出代码块后自动解锁
    >
    > 而Lock则是需要手动的上锁解锁,是显性的

死锁

两个线程在分别已经拥有一个对象锁并且未解锁的情况下,互相请求获取对方的对象锁,就会造成死锁

例如:小明拿着玩具1,小红拿着玩具2,两人想要对方的玩具的同时,还不想放弃自己的玩具。两人就会僵持住(死锁)

生产者和消费者

wait()方法:this.wait,会释放锁,所以一般都是使用的同一个对象

notifyAll():通知所有wait的线程启动

线程池

放在其他地方

声明:该文章系转载,转载该文章的目的在于更广泛的传递信息,并不代表本网站赞同其观点,文章内容仅供参考。

本站是一个个人学习和交流平台,网站上部分文章为网站管理员和网友从相关媒体转载而来,并不用于任何商业目的,内容为作者个人观点, 并不代表本网站赞同其观点和对其真实性负责。

我们已经尽可能的对作者和来源进行了通告,但是可能由于能力有限或疏忽,导致作者和来源有误,亦可能您并不期望您的作品在我们的网站上发布。我们为这些问题向您致歉,如果您在我站上发现此类问题,请及时联系我们,我们将根据您的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。