• 线程的基本解析


    线程对象是可以产生线程的对象。比如在Java平台中Thread对象,Runnable对象。线程,是指正在执行的一个指点令序列。在java平台上是指从一个线程对象的start()开始,运行run方法体中的那一段相对独立的过程。相比于多进程,多线程的优势有:

        (1)进程之间不能共享数据,线程可以;

        (2)系统创建进程需要为该进程重新分配系统资源,故创建线程代价比较小;

        (3)Java语言内置了多线程功能支持,简化了java多线程编程。

    一、创建线程和启动

      (1)继承Thread类创建线程类

    通过继承Thread类创建线程类的具体步骤和具体代码如下:

       • 定义一个继承Thread类的子类,并重写该类的run()方法;

       • 创建Thread子类的实例,即创建了线程对象;

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

    class SomeThead extends Thraad   { 
        public void run()   { 
         //所要重写的方法
        }  
     } 
     
    public static void main(String[] args){
     SomeThread oneThread = new SomeThread();   
      步骤3:启动线程:   
     oneThread.start(); 
    }

     

    (2)实现Runnable接口创建线程类

    通过实现Runnable接口创建线程类的具体步骤和具体代码如下:

       • 定义Runnable接口的实现类,并重写该接口的run()方法;

       • 创建Runnable实现类的实例,并以此实例作为Thread的target对象,即该Thread对象才是真正的线程对象。

    class SomeRunnable implements Runnable   { 
      public void run()   { 
      //所要重写的方法 
      }  
    } 
    Runnable oneRunnable = new SomeRunnable();   
    Thread oneThread = new Thread(oneRunnable);   
    oneThread.start();

    以上的两种方法均可实现创建线程类

    然而,本魔王一开始乍一看的时候感觉大体上好像并没有什么区别,但事实显然并没有这么简单,试问一下,当使用Thread时候如有需求要求继承其他类的时候,是可以用extend继承父类的域,那万一要继承的不只是一个父类呢,Thread毕竟只是一个类,不是接口,诶!!对了,这时候用Runnable就会方便很多,下面为大家总结的是Runnable与Thread的相关性。

     

    ****Runnable和Thread的区别和联系?****

    1) Thread是一个类,Runnable是一个接口;

    2) Thread类实现了Runnable接口,重写了run方法.

    3) Runnable是一个接口,定义一个类实现接口的同时还可以继承其他的类 ; Runnable 支持多继承的写法;

    4) Runable可以简单的实现数据的共享 ;Thread不太好实现;其实都可以实现

    5) Runnable适合多个相同的程序代码的线程去处理同一个资源,避免Java中的单继承的限制,增加程序的健壮性,代码可以被多个线程共享,代码和数据独立。线程池只能放入实现Runnable 类线程,不能直接放入继承Thread的类.

     

    在面试的时候,很多面试官都喜欢问一些关于线程状态的问题,可能不多问的特别细,但起码道理我们得说的出来,下图是线程的生命周期,就挑几个常见的给大家解释的,其实有一些是我自己归纳的,也有一些是网上dd下来的,懂的是什么道理,自己整理一套白话最好。

    • 新线程:
      •  用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新生状态。处于新生状态的线程有自己的内存空间,通过调用start方法进入就绪状态(runnable),它仅仅作为一个对象实例存在, JVM没有为其分配CPU时间片和其他线程运行资源;。

        注意:不能对已经启动的线程再次调用start()方法,否则会出现Java.lang.IllegalThreadStateException异常。

           

    • 就绪状态:
      • 在处于创建状态的线程中调用start方法将线程的状态转换为就绪状态(尽管是采用队列形式,事实上,把它称为可运行池而不是可运行队列。因为cpu的调度不一定是按照先进先出的顺序来调度的)。等待系统为其分配CPU。等待状态并不是执行状态,当系统选定一个等待执行的Thread对象后,它就会从等待执行状态进入执行状态,系统挑选的动作称之为“cpu调度”。一旦获得CPU,线程就进入运行状态并自动调用自己的run方法
      • 提示:如果希望子线程调用start()方法后立即执行,可以使用Thread.sleep()方式使主线程睡眠一伙儿,转去执行子线程。(sleep()方法在下面会解释)

           

    • 运行状态:
      • 处于运行状态的线程是最复杂的,有阻塞状态,就绪状态和死亡状态
      • 处于就绪状态的线程,如果获得了cpu的调度,就会从就绪状态变为运行状态,执行run()方法中的任务。如果该线程失去了cpu资源,就会又从运行状态变为就绪状态。重新等待系统分配资源。也可以对在运行状态的线程调用yield()方法,它就会让出cpu资源,再次变为就绪状态。(**当某个线程调用了yield()方法暂停之后,优先级与当前线程相同,或者优先级比当前线程更高的就绪状态的线程更有可能获得执行的机会,当然,只是有可能,因为我们不可能精确的干涉cpu调度线程。)
    • 等待/阻塞:
      • 阻塞状态在这里我不便多说,因为我自己都还没看透(喷血),我只能理解的是当一个线程占用CPU运行时被其他线程或者语句剥夺或者说暂停了其了使用权,从而进入阻塞状态。
      • 在阻塞状态的线程不能进入就绪队列。只有当引起阻塞的原因消除时,如睡眠时间已到,或等待的I/O设备空闲下来,线程便转入就绪状态,重新到就绪队列中排队等待,被系统选中后从原来停止的位置开始继续运行。

     

     

     

    Java提供了一些便捷的方法用于线程状态的控制,就举几个常用的例子,多了我也不会。具体如下:

    1、线程睡眠——sleep

          如果我们需要让当前正在执行的线程暂停一段时间,并进入阻塞状态,则可以通过调用Thread的sleep方法。

    注:

       (1)sleep是静态方法,最好不要用Thread的实例对象调用它,因为它睡眠的始终是当前正在运行的线程,而不是调用它的线程对象,它只对正在运行状态的线程对象有效。如下面的例子:

     1 package day7_3HomeWork;
     2 
     3 public class Thread_text {
     4 
     5     public static void main(String[] args) {
     6         Thread_input ti = new Thread_input();
     7         ti.start();
     8 
     9     }
    10 }
    11 class Thread_input extends Thread{
    12     String[] str = {"我","我爱","我爱福","我爱福建","我爱福建工","我爱福建工程","我爱福建工程学","我爱福建工程学院"};
    13     @Override
    14     public void run() {
    15         // TODO Auto-generated method stub
    16         for(int i=0;i<str.length;i++)
    17         {
    18             System.out.println(str[i]);
    19             try {
    20                 sleep(2000);
    21             } catch (InterruptedException e) {
    22                 // TODO Auto-generated catch block
    23                 e.printStackTrace();
    24             }
    25         }    
    26     }
    27 }

    相当于只是暂停当前的进程运行,线程处于阻塞状态,在2000mills的睡眠时间结束的时候才能重新变回就绪状态,而就绪状态进入到运行状态,是由系统控制的,我们不可能精准的去干涉它,所以如果调用Thread.sleep(1000)使得线程睡眠1秒,可能结果会大于1秒。下图是sleep的方法,需要传入的是一个long mills的参数,这是毫秒的单位。

    (2)Java线程调度是Java多线程的核心,只有良好的调度,才能充分发挥系统的性能,提高程序的执行效率。但是不管程序员怎么编写调度,只能最大限度的影响线程执行的次序,而不能做到精准控制。

    2、线程让步——yield

      yield()方法只是让当前线程暂停一下,重新进入就绪的线程池中,让系统的线程调度器重新调度器重新调度一次,完全可能出现这样的情况:当某个线程调用yield()方法之后,线程调度器又将其调度出来重新进入到运行状态执行。

      这个东西就有点小意思了,本王一开始用的时候感觉跟sleep好像没啥不同的,都是可以暂停当前的线程让CPU释放出资源空间并让给其他线程。

      它也是Thread类提供的一个静态的方法,当某个线程调用了yield()方法暂停之后,优先级与当前线程相同,或者优先级比当前线程更高的就绪状态的线程更有可能获得执行的机会,当然,只是有可能,因为我们不可能精确的干涉cpu调度线程。以下的示范代码是我从一个博客里面找出来的,与我的代码相比,比较经典一点。

     1 public class Test1 {  
     2     public static void main(String[] args) throws InterruptedException {  
     3         new MyThread("低级", 1).start();  
     4         new MyThread("中级", 5).start();  
     5         new MyThread("高级", 10).start();  
     6     }  
     7 }  
     8   
     9 class MyThread extends Thread {  
    10     public MyThread(String name, int pro) {  
    11         super(name);// 设置线程的名称  
    12         this.setPriority(pro);// 设置优先级  
    13     }  
    14   
    15     @Override  
    16     public void run() {  
    17         for (int i = 0; i < 30; i++) {  
    18             System.out.println(this.getName() + "线程第" + i + "次执行!");  
    19             if (i % 5 == 0)  
    20                 Thread.yield();  
    21         }  
    22     }  
    23 }

    3、设置线程的优先级

      然后很尴尬的是,我原先一开始也是半吊子一个,在写博客的时候一个不会查一个,然后自己在试一试,发现还挺好玩,就像上述的代码中就发现了另一个很好玩的东西,就是设置优先级,一开始我还以为既然yield()和sleep()真的这么不靠谱的话,那实际在做项目的过程中,总不会存在这么的一个不稳定性因素存在吧,然后就发现了这个——setPriority(int newPriority)和getPriority()方法。

      

      每个线程执行时都有一个优先级的属性,优先级高的线程可以获得较多的执行机会,而优先级低的线程则获得较少的执行机会。与线程休眠类似,线程的优先级仍然无法保障线程的执行次序。只不过,优先级高的线程获取CPU资源的概率较大,优先级低的也并非没机会执行。

      每个线程默认的优先级都与创建它的父线程具有相同的优先级,在默认情况下,main线程具有普通优先级。代码就和上面的一样就行了。

     1 public class Test1 {  
     2         public static void main(String[] args) throws InterruptedException {  
     3             new MyThread("高级", 10).start();  
     4             new MyThread("低级", 1).start();  
     5         }  
     6     }  
     7       
     8     class MyThread extends Thread {  
     9         public MyThread(String name,int pro) {  
    10             super(name);//设置线程的名称  
    11             setPriority(pro);//设置线程的优先级  
    12         }  
    13         @Override  
    14         public void run() {  
    15             for (int i = 0; i < 100; i++) {  
    16                 System.out.println(this.getName() + "线程第" + i + "次执行!");  
    17             }  
    18         }  
    19     }

    注:Thread类提供了setPriority(int newPriority)和getPriority()方法来设置和返回一个指定线程的优先级,其中setPriority方法的参数是一个整数,范围是1~·0之间,也可以使用Thread类提供的三个静态常量:

    MAX_PRIORITY   =10
    
    MIN_PRIORITY   =1
    
    NORM_PRIORITY   =5

     

    当然了,线程还有很多有意思的方法和可以实现的东西,线程同步啦,线程通信啦,一步一步来嘛,我也不是特别懂,哈哈哈哈哈哈,在卡不懂我写什么的时候赶紧再去百度一波,要不我写错了坑到你们就完球儿了

    在了解了线程大致是一个什么样的情况了之后,大家就可以来找一些东西来玩玩了,比较我刚学的时候就做了一个多人聊天室玩着,听着名字是挺高大上的是吧,其实就是一个小型的数据传输的一个java文件,把服务端和客户端都搞完之后,将sql脚本和客户端文件在同一个局域网内的电脑跑一下,就可以实现聊天啦,当然啦,ip得改成跑着服务端的那台电脑的IP地址,我把我写的那个界面改一下再传吧,要不着实有点小丑,网上也有很多博主有放出来,你们可以先看看。

    Emmmm,,,,思考了一下,感觉还有两个东西也得说一下,就是线程池和死锁。

    怎么说呢,线程池这个概念很重要,因为要理解。死锁这个概念也很重要,因为我们可以遇到,面试官也有可能问。

    线程池

      线程池主要用来解决线程生命周期开销问题和资源不足问题。通过对多个任务重复使用线程,线程创建的开销就被分摊到了多个任务上了,而且由于在请求到达时线程已经存在,所以消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使用应用程序响应更快.

       ***合理利用线程池能够带来三个好处。***(知道一下就好)

      1.降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

      2.提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。

      3.提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

    死锁

     原本我是知道但不是知道的很彻底的,后面被本宿舍的那个拿到了腾讯offer的大佬gank了一波,被气的呀,就去看了一下。其实他原本也是不怎么清楚,后来也很搞笑,是隔壁宿舍那个小佬在面雷火的时候被问到的时候答不上来来问他他也才去查了一波。

      难吗,不难,复杂吗,真不复杂。就一句话就能代过的那种。

      []个线程,在等待其他的线程释放资源,而对方也等待其他的线程释放资源;

      就是这么简单的一个概念,但涉及到的却有很多,根据不同场景下的不同情况都有不用的说法吧,这个以后如果我真的碰到难道有一些好的具体事例我再补充。

    (1)死锁的四个必要条件

    互斥条件:资源不能被共享,只能被同一个进程使用

    请求与保持条件:已经得到资源的进程可以申请新的资源

    非剥夺条件:已经分配的资源不能从相应的进程中被强制剥夺

    循环等待条件:系统中若干进程组成环路,该环路中每个进程都在等待相邻进程占用的资源

     

    举个常见的死锁例子:进程A中包含资源A,进程B中包含资源B,A的下一步需要资源B,B的下一步需要资源A,所以它们就互相等待对方占有的资源释放,所以也就产生了一个循环等待死锁。

     

    (2)处理死锁的方法

    忽略该问题,也即鸵鸟算法。当发生了什么问题时,不管他,直接跳过,无视它;

    检测死锁并恢复;

    资源进行动态分配;

    破除上面的四种死锁条件之一。

  • 相关阅读:
    linux 第五天
    linux 第四天
    二进制 位运算
    二进制
    java 方法的调用过程
    Linux 第三天
    Linux 第二天
    Linux
    学习了半个多月的TankGame
    学习第一天(spring)
  • 原文地址:https://www.cnblogs.com/wudidamowang666/p/11130012.html
Copyright © 2020-2023  润新知