• Java-多线程第三篇3种创建的线程方式、线程的生命周期、线程控制、线程同步、线程通信


    1、Java使用Thread类代表线程。

        所有的线程对象必须是Thread类或其子类的实例。

        当线程继承Thread类时,直接使用this即可获取当前线程,Thread对象的getName()方法返回当前线程的名字,因此可以直接调用getName()方法返回当前线程的名字。

        Thread.currentThread():该方法总是返回当前正在执行的线程对象。

    2、创建线程方式1:继承Thread类创建线程类

        这种方式创建线程的步骤一般为:

        1》定义Thread类的子类,并重写该类的run()方法,该方法作为线程的线程执行体

        2》创建Thread子类的实例,即线程对象。

        3》调用线程对象的start()方法启动线程。

        举个例子:

    public class extendsThread extends Thread{
    
      ...
      @Override
      public void run(){
      
          ...
          //do something
    System.out.println(this.getName()); } public static void main(String[] agrs){ //创建并启动第一个线程 new extendsThread().start(); //创建并启动第二个线程 new extendsThread().start(); } }

    3、创建线程方式2:实现Runnable接口

        这种方式创建线程的步骤一般为:

          1》定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体

          2》创建Runnable接口实现类的实例,并将该实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。本质是Thread对象负责执行实现类对象的run()方法体。

          3》调用线程对象的start()方法来启动该线程。

         举个例子:

    public class ImplRunnable implements Runnable{
    
       ...
       @Override
       public void run(){
    
           ...
           //do something 
    System.out.println(Thread.currentThread().getName());
       }
    
       public static void main(String agrs){
    
          ...
          System.out.println(Thread.currentThread().getName());
    
          ImplRunnable target=new ImplRunnable();
          //创建并启动第一个线程
          new Thread(target,"Thread1").start();
          //创建并启动第二个线程
          new Thread(target,"Thread2").start();
      }
    }

    4、使用Callable和Future创建线程:创建有返回值的线程

        这种方式创建线程的步骤一般为:

           1》创建Callable接口的实现类,并实现call()方法,该方法作为线程执行体,且该方法有返回值。然后创建该实现类的实例。

           2》使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。

           3》使用FutureTask对象作为Thread对象的target创建并启动新线程。

           4》调用FutureTask对象的get()方法获取子线程执行结束后的返回值。

           举个例子:

    public class ImplCallable implements Callable<Integer>{
    
       ...
       @Override
       public Integer call(){
     
          ...
          //do something
          System.out.println(Thread.currentThread().getName());
          //return a value.
          return 1;
       }
    
        public static void main(String[] agrs) throws InterruptedException, ExecutionException{
    
          ...
          //创建并启动一个线程
          FutureTask<Integer> target=new FutureTask<Integer>(new ImplCallable());
          new Thread(target,"Thread3 with value.").start();
          //获取返回值
          System.out.println(target.get());
        }
    }

    5、线程的生命周期

       

      线程进入阻塞状态:

      

       线程的死亡:

       

       当主线程结束时,其他线程不受任何影响,并不会随之结束。一旦子线程启动起来之后,它就拥有和主线程相同的地位,它不会受主线程的影响。可以调用线程对象的isAlive()方法,当线程处于就绪、运行、阻塞三种状态时,该方法返回true;当线程处于新建、死亡两种状态时,该方法返回false。不要试图对一个已经死亡的线程调用start()方法使它重新启动,死亡的线程不能再次作为线程执行。

    6、控制线程

      1》join():线程。Thread的静态方法。

        A线程调用B线程的join()方法,A线程将被阻塞,直至B线程执行结束。join()方法有如下三种重载方式:

          1>join():等待被join的线程执行完毕。

          2>join(long millis):等待被join的线程的事时间最长为millis毫秒,如果millis毫秒被join的线程依然没有结束,则不再等待。

          3>join(long millis,int nanos):等待被join的线程的时间最长为millis毫秒加nanos毫秒。(一般计算机硬件、操作系统本身无法做到这么精确)。

       2》setDaemon(true):后台线程、守护线程、精灵线程

        如果所有的前台线程都死亡了,后台线程也会自动死亡。调用Thread对象的setDaemon(true)方法可以将指定线程设置成后台线程。

        Thread还提供了isDaemon()方法,用于判断指定线程是否为后台线程。

        前台线程创建的子线程默认是前台线程,后台线程创建的子线程默认是后台线程。

      3》sleep():让线程睡眠。Thread的静态方法。

       在指定的睡眠时间内,即使系统中没有其他线程可执行,处于sleep()中的线程也不会执行。sleep()方法通常用来暂停线程。

       4》yield():线程让步。Thread的静态方法。可以让线程暂停,但不会阻塞线程,线程会进入就绪状态,有可能立即又得到执行。当线程执行yield()方法后,优先级与之相等或者比它高的线程才会得到执行的机会。

       比较:

       

       5》改变线程的优先级

         每个线程默认的优先级与创建它的父线程的优先级相同。默认情况下,main线程具有普通优先级。

         Thread提供了setPriority(int newPriority)、getPriority()方法来设置和返回指定线程的优先级,其中setPriority(int newPriority)方法的参数可以是一个整数,范围1~10,值越大优先级越高。Thread也提供了三个静态常量设置的值:

         

    7、线程同步:方式(1)synchronized的同步代码块、同步方法;方式(2)同步锁(Lock或ReadWriteLock接口的实现类)。

       1》同步代码块:

    //其中的obj就是同步监视器。
    //线程在执行到下面的代码块的时候,必须先获得对同步监视器的锁定。
    //任何时刻只能有一个线程可以获得同步监视器的锁定,同步代码块执
    //行结束,同步监视器自动被释放锁定。 sychronized(obj){ ... }

         2》同步方法:

                 使用sychronized修饰的某个方法(可能是实例方法,也可能是静态方法)。

                  1>修饰实例方法:无需显式指定同步监视器,同步方法的同步监视器就是this,即调用该方法的实例对象。

                  2>修饰静态方法:。。。

         

         ==》注意:sychronized可以修饰代码块、成员方法。但是不能修饰构造器、成员变量等。

         ==》为了减少线程安全问题带来的性能影响:

            1>不要对线程安全类的所有方法都进行同步,只对那些会改变竞争资源的方法进行同步(可以参考哪些资源会被改变)。

            2>如果可变类有两种环境:单线程、多线程环境。可以考虑对该类提供两个版本,在不同的环境中使用不同的版本。

           (典型单线程、多线程线程安全版本:StringBuilder(非线程安全版本)是单线程环境下使用;StringBuffer(线程安全版本)在多线程环境下使用)

          释放同步监视器的锁定,这个不能显式地控制,一般的时机为:

            可大概总结为线程要从同步代码块、同步方法的代码块中退出(被迫或正常执行结束),这时候就会自动释放同步监视器。

          

         3》同步锁(Lock):

           Lock对象充当同步对象。

           1)Java 5提供了Lock、ReadWriteLock两个根接口,并为:

                1>Lock==>ReetrantLock(可重入锁)实现类:

                2>ReadWriteLock==>ReetrantReadWriteLock(可重入读写锁)实现类:提供了Writing、ReadingOptimistic、Reading三种锁模式。

                    可重入锁,指的是一个线程可以对已被加锁的ReetrantLock / ReetrantReadWriteLock对象再次加锁,ReetrantLock / ReetrantReadWriteLock对象会维持一个计数器来追踪lock()方法的嵌套调用。线程在每次调用lock()加锁后,必须显式调用unlock()来释放锁,所以一段被锁保护的代码可以调用另一个被相同锁保护的方法。

           2)Java 8新增StampedLock类,在大多数场景中可以替代ReetrantReadWriteLock(可重入读写锁)。

           常用的锁是ReetrantLock(可重入锁),其一般使用格式:

    public class A{
        //定义锁对象
        private final ReentrantLock lock=new ReentrantLock();
        ...
        public void method(){
            //加锁对象
            lock.lock();
            try{
               //需要保证线程同步的代码
               ...
            }
            finally{
               //释放锁
               lock.unlock();
            }
        }
    }

        总结:同步代码块、同步方法使用的是与资源竞争相关的、隐式的同步监视器,且强制要求加锁和锁释放要出现在一个块结构中,而且获得了多个锁时,它们必须以相反的顺序释放,且必须在与所有锁被获取时相同的范围内释放所有锁。

        ==》死锁:

          两个线程相互等待对方释放同步监视器时就会发生死锁,简单的例子A等待B的筷子吃面然后才能空出盘子,但B在等待A的盘子盛面吃了才可以给A筷子,相互之间等待需要的资源,就形成了死锁。出现了死锁,所有线程处于阻塞态,无法继续。

    8、线程通信

       1》传统的线程通信

           Object类提供了wait()、notify()、notifyAll()三个方法,这三个方法并不属于Thread类,而是属于Object类。且三个方法必须由同步监视器对象来调用,可分为两种情况:

          

           关于上面的wait()、notify()、notifyAll()三个方法:

           

          使用举例:

    public class A{
       ...
       public sychronized void getMoney(){
          
          try{
       
              if(!flag){
                
                  wait();
              }
              else{
                 
                 ...
                 flag=false;
                 notifyAll();
              }
          }
          catch{
             ...
          }
       }
    
       public sychronized void setMoney(){
       
          try{
       
              if(!flag){
                
                  wait();
              }
              else{
                 
                 ...
                 flag=true;
                 notifyAll();
              }
          }
          catch{
             ...
          }
       }
    }

       2》使用Condition控制线程通信

          这种情况针对不使用sychronized关键字来保证同步(即不是同步代码块或者同步方法的方式),而是使用Lock对象来保证同步,则系统中不存在隐式的同步监视器,也就不能使用上面的三个方法(wait()、notify()、notifyAll())进行线程间通信了。

          针对使用Lock对象保证同步的情况,Java提供了Condition类来保持协调。

          使用Condition可以让那些已经得到Lock对象却无法继续执行的线程释放Lock对象,也可以唤醒其他处于等待的线程。

         使用举例:

    public class A{
       //定义锁对象
       private final Lock lock=new ReentrantLock();
       //指定Lock对象的对应的Condition
       private final Condition cond=lock.new Condition();
       ...
       public void getMoney(){
          //加锁
          lock.lock();
          try{
       
              if(!flag){
                
                  cond.await();
              }
              else{
                 
                 ...
    flag=false; cond.signalAll(); } }
    catch{ ... } finally{ //释放锁 lock.unlock(); } } public void setMoney(){ //加锁 lock.lock(); try{ if(!flag){ cond.await(); } else{ ...
    flag=true; cond.signalAll(); } }
    catch{ ... } finally{ //释放锁 lock.unlock(); } } }

      3》使用阻塞队列(BlockingQueue)控制线程通信

         Java 5提供了一个BlockingQueue接口,Queue的子接口,主要用作线程同步工具。特征:当生产者试图往BlockingQueue放入元素时,如果队列已满,则线程被阻塞;如果消费者线程试图从BlockingQueue取元素时,如果队列已空,则线程被阻塞。

         。。。详细使用参考相关的API。

    9、线程组合未处理的异常

  • 相关阅读:
    VS Code安装以及工作区的创建
    var let const的使用和区别
    springboot 配置mysql日期返回格式
    vue安装Node和NPM配置,路由安装。
    分组查询语句(group by函数)
    ORA-00918:未明确定义列
    内连接(inner join)
    右外连接(right join)
    左外连接(left join)
    比较oracle两表中date类型数据是否一致语句查询
  • 原文地址:https://www.cnblogs.com/ZeroMZ/p/11361193.html
Copyright © 2020-2023  润新知