• JAVA线程


    线程:
            一. 从进程到线程
                进程是指运行中的应用程序,每一个进程都有自己独立的内存空间。一个应用程序可以同时启动多个进程。
                线程是指进程中的一个执行流程。一个进程可以由多个线程组成,在一个进程中可以同时运行多个不同的线程,它们分别执行不同的任务。
                当进程内的多个线程同时运行时,这种运行方式称为并发运行。


                线程与进程的区别:每个进程都需要操作系统为其分配独立的内存地址空间,而同一进程中的所有线程在同一块地址空间中工作,这些线程可以共享同一块内存和系统资源。


            二. java中的线程
                在java虚拟机进程中,执行程序代码的任务是由线程来完成的。每当用java命令启动一个Java虚拟机进程时,Java虚拟机都 会创建一个主线程。该线程从程序入口main()方法开始执行。


                java中可以把线程分为前台线程(执行线程)、后台线程(守护线程)。


            三. 线程的创建和启动
                Java虚拟机的主线程从启动类的main()方法开始运行。此外,用户还可以创建自己的线程,它将和主线程并发运行。创建线程有两种方式:


                . 继承java.lang.Thread类; //extends
                . 实现Runnable接口;//implements


                1.  继承java.lang.Thread类
                    1)重写Thread类的run()方法;//public void run(){}——包含线程运行时所执行的代码;
                    2)在main()方法中调用start()方法启动线程,一个线程只能被启动一次。
    ------------------------------------------------------------------------------------------
    例:
    public class ThreadTest extends Thread {
       public void run(){
      System.out.println("thread");
       }
       public static void main(String[] args) {
    ThreadTest t=new ThreadTest();
    t.start();
       }
    }
    ------------------------------------------------------------------------------------------


                2.  实现Runnable接口
                    Java只能继承一个类,如果继承了Thread类,就不能继承其他类了。因此可以通过实现java.lang.Runnable接口避免此问题。
    1)实现Runnable接口
    2)创建Thread对象,传入要启动的线程类
    3)调用Thread对象的start()方法  //接口中没有start()方法,不能直接启动
    ------------------------------------------------------------------------------------------
    例:
    public class ThreadTest implements Runnable {
       public void run(){
      System.out.println("thread");
       }
       public static void main(String[] args) {
    ThreadTest tt=new ThreadTest();
    Thread td=new Thread(tt);
    td.start();
       }
    }
    ------------------------------------------------------------------------------------------




            四. 线程状态


                线程的五种状态;


                1. 新建(New)——用new刚创建出的线程对象,只被分配了内存
                2. 就绪(Runnable)——调用start()方法后,位于可运行池,等待CPU的使用权。
                3. 运行(Running)——占用CPU,执行程序代码。
                4. 阻塞(Blocked)——线程因为某些原因放弃CPU,暂时停止运行。

                   阻塞状态可分为三种:
                   1)位于对象等待池中的阻塞状态(Blocked in objects' wait pool): 运行状态时,执行某个对象的wait()方法;
                   2)位于对象锁池中的阻塞状态(Blocked in object's lock pool): 当线程处于运行状态,试图获得某个对象的同步锁时,如该对象的同步锁已经被其他线程占用,Java虚拟机就会把这个线程放到这个对象的锁池中;
                   3)其他阻塞状态(Otherwise Blocked): 当前线程执行了sleep()方法,或者调用了其他线程的join()方法,或者发出了I/O
                     请求时,就会进入这个状态。
                   
      线程从阻塞状态只能进入就绪状态,然后才有机会转到运行状态。


                5. 死亡(Dead)——线程执行完run()方法或执行期间遇到异常退出run()方法。


            五. 线程调度

       按照特定的机制为多个线程分配CPU的使用权。
       
    有两种调度模型:
         . 分时调度模型:让所有线程轮流获得CPU的使用权,并且平均分配每个线程占用CPU的时间片。
                 . 抢占式调度模型:优先让可运行池中优先级高的线程较多可能占用CPU(概率高),如果可运行池中线程的优先级相同,那么就随机选择一个线程,使其占用CPU。处于可运行状态的线程会一直运行,直至它不得不放弃CPU。Java虚拟机采用这种。


                一个线程会因为以下原因而放弃CPU: 
                . Java虚拟机让当前线程暂时放弃CPU,转到就绪状态;(interrupt)
                . 当前线程因为某些原因而进入阻塞状态; (sleep   join)
                . 线程运行结束;


                线程的调度还依赖于操作系统。在某些操作系统中,只要运行中的线程没有阻塞, 就不会放弃CPU;在某些操作系统中,即使运行中的线程没有遇到阻塞,也会在运行一段时间后放弃CPU,给其他线程运行机会。


                常用方法:
                

                1.  stop();

                     可以强制终止一个线程,但从JDK1.2开始废弃了stop()方法。在实际编程中,一般是在受控制的线程中定义一个标志变量,其他线程通过改变标志变量的值,来控制线程的自然终止、暂停及恢复运行。



                2. isAlive(); 

                    判定某个线程是否是活着的(该线程如果处于可运行状态、运行状态和阻塞状态、对象等待队列和对象的锁池中返回true)



                3. Thread.sleep(5000);//参数单位为毫秒 

                    放弃CPU, 转到阻塞状态。当结束睡眠后,首先转到就绪状态,如有其它线程在运行,不一定运行,而是在可运行池中等待获得CPU。

                    线程在睡眠时如果被中断,就会收到一个InterrupedException异常,线程跳到异常处理代码块。


                4. void sleepingThread.interrupt():
                    中断某个线程


                5. boolean otherThread.isInterrupted():
                    测试某个线程是否被中断,与static boolean  interrupted()不同,对它的调用不会改变该线程的“中断”状态。


       
                6. public void join();
                    public void join(long timeout);
                    挂起当前线程,直至它所调用的线程终止才被运行。
                    线程A中调用线程B.join(),是使A线程阻塞,B线程开始执行。
                    谁调用谁阻塞。


            六. 线程的同步


          多个线程在操纵共享资源——实例变量时,有可能引起共享资源的况争。为了保证每个线程能正常执行操作,保证共享资源能正常访问和修改。Java引入了同步进制,具体做法是在有可能引起共享资源竞争的代码前加上synchronized标记。这样的代码被称为同步代码块。


           每个Java对象都有且只有一个同步锁,在任何时刻,最多只允许一个线程拥有这把锁。当一个线程试图执行带有synchronized标记的代码块时,该线程必须首先获得this关键字引用的对象的锁。
                   如果这个锁已经被其他线程占用,Java虚拟机就会把这个线程放到this指定对象的锁池中,线程进入阻塞状态。在对象的锁池中可能会有许多等待锁的线程。等到其他线程释放了锁,Java虚拟机会从锁池中随机取出一个线程,使这个线程拥有锁,并且转到就绪状态。

                   假如这个锁没有被其他线程占用,线程就会获得这把锁,开始执行同步代码块。在一般情况下,线程只有执行完同步代码块,才会释放锁,使得其他线程能够获得锁。


                    如果一个方法中的所有代码都属于同步代码,则可以直接在方法前用synchronized修饰。


               public synchronized String pop(){...}
          等价于
                public String pop(){
                       synchronized(this){...}
                }


                线程同步的特征:


                1. 如果一个同步代码块和非同步代码块同时操纵共享资源,仍然会造成对共享资源的竞争。
                    因为当一个线程执行一个对象的同步代码块时,其他线程仍然可以执行对象的非同步代码块。
                2. 每个对象都有唯一的同步锁。
                3. 在静态方法前面也可以使用synchronized修饰符。此时该同步锁的对象为类对象(类的Class对象)。
        4. 当一个线程开始执行同步代码块时,并不意味着必须以不中断的方式运行。进入同步代码块的线程也可以执行
                   Thread.sleep()或者执行Thread.yield()方法,此时它并没有释放锁,只是把运行机会(即CPU)让给了其他的线程。
                5. synchnozied声明不会被继承。


        同步是解决共享资源竞争的有效手段。当一个线程已经在操纵共享资源时,其他共享线程只能等待。为了提升并发性能,应该使同步代码块中包含尽可能少的操作,使得一个线程能尽快释放锁,减少其他线程等待锁的时间。




            七. 线程的通信



                锁对象.wait(): 执行该方法的线程释放对象的锁,Java虚拟机把该线程放到该对象的     等待池中。该线程等待其它线程将它唤醒;
                锁对象.notify(): 执行该方法的线程唤醒在对象的等待池中等待的一个线程。Java虚拟机从对象的等待池中随机选择一个线程,把它转到对象的锁池中。如果对象的等待池中没有任何线程,那么notify()方法什么也不做。
                锁对象.notifyAll():会把对象的等待池中的所有线程都转到对象的锁池中。
       注意:notify notifyAll只会唤醒等待池中等待同一个锁对象的线程,因为同一个时刻在等到池中可能会有多个线程,而这多个线程可能是在等待不同的锁对象。

                假如t1线程和t2线程共同操纵一个s对象,这两个线程可以通过s对象的wait()和notify()方法来进行通信。通信流程如下:


                1. 当t1线程执行对象s的一个同步代码块时,t1线程持有对象s的锁,t2线程在对象s的锁池中等待;
                2. t1线程在同步代码块中执行s.wait()方法, t1释放对象s的锁,进入对象s的等待池;
                3. 在对象s的锁池中等待锁的t2线程获得了对象s的锁,执行对象s的另一个同步代码块;
                4. t2线程在同步代码块中执行s.notify()方法,Java虚拟机把t1线程从对象s的等待池移到对象s的锁池中,在那里等待获得锁。
                5. t2线程执行完同步代码块,释放锁。t1线程获得锁,继续执行同步代码块。




            八. 线程的死锁


                A线程等待B线程持有的锁,而B线程正在等待A持有的锁;
    --------------------------------------------------------------------------------------------------
    死锁的例子:
    package t;


    public class deadlock extends Thread {
      public static String x="x";
      public static String y="y";
      int a;
      public deadlock(int a){
     this.a=a;
      }
      public void run(){
     if(a==1){
     synchronized(x){
     System.out.println("1 in x");
     //sleep(1000);
     synchronized(y){
     System.out.println("1 in y");
     }
     }
     }
     if(a==2){
     synchronized(y){
     System.out.println("2 in y");
     //sleep(1000);
     synchronized(x){
     System.out.println("2 in x");
     }
     }
     }
      }
      public static void main(String[] args) {
    deadlock d1=new  deadlock(1);
    deadlock d2=new  deadlock(2);
    d1.start();
    d2.start();
      }
    }
    //大概率会死锁,也可能不会锁。加上sleep后则一定会死锁,但需要捕获异常InterruptedException e。
    --------------------------------------------------------------------------------------------------


            九. 线程让步


                Thread.yield()静态方法,如果此时具有相同优先级的其他线程处于就绪状态,那么yield()方法将把当前运行的线程放到可运行池中并使另一个线程运行。如果没有相同优先级的可运行线程,则yield()方法什么也不做。


                sleep()和yield()方法都是Thread类的静态方法,都会使当前处于运行状态的线程放弃CPU,把运行机会让给别的线程。区别:


                . sleep()不考虑其他线程优先级;
                  yield()只会给相同优先级或者更高优先级的线程一个运行的机会。
                . sleep()转到阻塞状态;
        yield()转到就绪状态;
                . sleep()会抛出InterruptedException异常,
                  yield()不抛任何异常
                . sleep()比yield方法具有更好的可移植性,yield()只在测试时用。


            十. 调整线程优先级
       
    注意:优先级高的线程只能获得较多运行的概率,但是实际中不一定真的有效果 
            Thread类的setPriority(int)和getPriority()方法分别用来设置优先级和读取优先级。优先级用整数来表示,取值范围是1-10,Thread类有以下3个静态常量。


                . MAX_PRIORITY: 10, 最高;
                . MIN_PRIORITY: 1, 最低;
                . NORM_PRIORITY: 5, 默认优先级;


       其它:stop():         中止线程运行;            已过时
                  resume():    使暂停线程恢复运行; 已过时
                  suspend():  暂停线程,不释放锁; 已过时


                释放对象的锁:


                . 执行完同步代码块;
                . 执行同步代码块过程中,遇到异常而导致线程终止,释放锁;
                . 执行同步代码块过程中,执行了锁所属对象的wait()方法,释放锁进入对象的等待池;


                线程不释放锁:


                . Thread.sleep()方法,放弃CPU,进入阻塞状态;
                . Thread.yield()方法,放弃CPU,进入就绪状态;
                . suspend()方法,暂停当前线程,已过时;

    -------------------------------------------------------------------------------------------------

    总结:

     *线程Thread调用的常用方法:isAlive,sleep,interrupt,isInterrupted,join,yield。(stop,suspend,resume)

     *锁对象synchronized调用的方法:wait,notify,notifyAll。
    -------------------------------------------------------------------------------------------------
  • 相关阅读:
    Android Studio精彩案例(四)《DrawerLayout使用详解仿网易新闻客户端侧边栏 》
    Android简易实战教程--第四十七话《使用OKhttp回调方式获取网络信息》
    Android Studio突然不显示logcat日志
    Xcode 调试技巧 --常用命令和断点
    (译)Objective-C 类属性
    Android简易实战教程--第四十六话《RecyclerView竖向和横向滚动》
    Android Studio 中设置代码块自动补齐
    RunLoop总结:RunLoop的应用场景(三)
    Android Studio精彩案例(三)《模仿微信ViewPage+Fragment实现方式二》
    .net学习视屏
  • 原文地址:https://www.cnblogs.com/codeToSuccess/p/13906270.html
Copyright © 2020-2023  润新知