美文网首页
Java线程基础知识

Java线程基础知识

作者: 稻田上的稻草人 | 来源:发表于2017-09-28 11:11 被阅读17次

    线程的状态

    • 新建状态:用new语句创建的线程对象处于新建状态,此时它和其它的java对象一样,仅仅在堆中被分配了内存
    • 就绪状态:当一个线程创建了以后,其他的线程调用了它的start()方法,该线程就进入了就绪状态。处于这个状态的线程位于可运行池中,等待获得CPU的使用权
    • 运行状态:处于这个状态的线程占用CPU,执行程序的代码
    • 阻塞状态:当线程处于阻塞状态时,java虚拟机不会给线程分配CPU,直到线程重新进入就绪状态,它才有机会转到运行状态。 可以细分为三种情况:
      • 位于对象等待池中的阻塞状态:当线程运行时,如果执行了某个对象的wait()方法,java虚拟机就回把线程放到这个对象的等待池中
      • 位于对象锁中的阻塞状态,当线程处于运行状态时,试图获得某个对象的同步锁时,如果该对象的同步锁已经被其他的线程占用,JVM就会把这个线程放到这个对象的琐池中。
      • 其它的阻塞状态:当前线程执行了sleep()方法,或者调用了其它线程的join()方法,或者发出了I/O请求时,就会进入这个状态中。
    shunxutu.png

    线程的优先级

    • 当线程的优先级没有指定时,所有线程都携带普通优先级。
    • 优先级可以用从1到10的范围指定。10表示最高优先级,1表示最低优先级,5是普通优先级。
    • 优先级最高的线程在执行时被给予优先。但是不能保证线程在启动时就进入运行状态。
    • 与在线程池中等待运行机会的线程相比,当前正在运行的线程可能总是拥有更高的优先级。
    • t.setPriority()用来设定线程的优先级。
    • 在线程开始方法被调用之前,线程的优先级应该被设定。
    • 你可以使用常量,如MIN_PRIORITY,MAX_PRIORITYNORM_PRIORITY来设定优先级
      /**
         * The minimum priority that a thread can have.
         */
        public final static int MIN_PRIORITY = 1;
    
       /**
         * The default priority that is assigned to a thread.
         */
        public final static int NORM_PRIORITY = 5;
    
        /**
         * The maximum priority that a thread can have.
         */
        public final static int MAX_PRIORITY = 10;
    

    线程的使用

    Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("t1 begin");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t1 end");
        }
    });
    
    t1.start();
    

    线程中特殊函数

    join()

    join方法是一个属于对象的方法,主要作用是是的调用join方法的这个线程对象先执行,调用方法所在的线程等执行完了,在执行。

    Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("t1 begin");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t1 end");
        }
    });
    
    t1.start();
    t1.join();
    
    Thread t2 = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("t2 begin");
            System.out.println("t2 end");
        }
    });
    t2.start();
    

    输出的结果:

    
    //注释t1.join()
    
    
    t1 begin
    
    t2 begin
    
    t2 end
    
    t1 end
    
    //没有注释t1.join()
    
    
    t1 begin
    
    t1 end
    
    t2 begin
    
    t2 end
    
    
    wait()

    表示等待获取某个锁执行了该方法的线程释放对象的锁,JVM会把该线程放到对象的等待池中。该线程等待其它线程唤醒 notify() 执行该方法的线程唤醒在对象的等待池中等待的一个线程,JVM从对象的等待池中随机选择一个线程,把它转到对象的锁池中。使线程由阻塞队列进入就绪状态(只能在同步代码块中使用)上面尤其要注意一点,一个线程被唤醒不代表立即获取了对象的monitor,只有monitor,只有等调用完notify()或者notifyAll()并退出synchronized块,释放对象锁后,其余线程才可获得锁执行

    sleep()

    是一个类的方法,让当前线程停止执行,让出cpu给其他的线程,但是不会释放对象锁资源以及监控的状态,当指定的时间到了之后又会自动恢复运行状态。有一个用法可以代替yield函数——sleep(0)

    yield()

    这方法与sleep()类似,可以使用sleep(0)来达到相同的效果,只是不能由用户指定暂停多长时间,并且yield()方法只能让同优先级或者高优先级的线程有执行的机会,注意这里并不是一定,有可能又会执行当前线程,执行完后,这个线程的状态从执行状态转到了就绪状态

    notify()

    执行该方法的线程唤醒在对象的等待池中等待的一个线程,JVM从对象的等待池中随机选择一个线程,把它转到对象的锁池中。使线程由阻塞队列进入就绪状态。注意:这里必须持有相同锁的线程

    interrupt()

    中断线程,被中断线程会抛InterruptedException

    线程的停止

    当线程启动时,我们怎么去停止启动的线程呢?一般来说,有

    run()和start()的区别

    我们从源码来学习,这两个方法的不同,Thread类的方法:

    
        /**
         * Package-scope method invoked by Dalvik VM to create "internal"
         * threads or attach threads created externally.
         *
         * Don't call Thread.currentThread(), since there may not be such
         * a thing (e.g. for Main).
         */
        Thread(ThreadGroup group, String name, int priority, boolean daemon) {
            synchronized (Thread.class) {
                id = ++Thread.count;
            }
            if (name == null) {
                this.name = "Thread-" + id;
            } else {
                this.name = name;
            }
            if (group == null) {
                throw new InternalError("group == null");
            }
            this.group = group;
            this.target = null;
            this.stackSize = 0;
            this.priority = priority;
            this.daemon = daemon;
            /* add ourselves to our ThreadGroup of choice */
            this.group.addThread(this);
        }
        /**
         * Initializes a new, existing Thread object with a runnable object,
         * the given name and belonging to the ThreadGroup passed as parameter.
         * This is the method that the several public constructors delegate their
         * work to.
         *
         * @param group ThreadGroup to which the new Thread will belong
         * @param runnable a java.lang.Runnable whose method <code>run</code> will
         *        be executed by the new Thread
         * @param threadName Name for the Thread being created
         * @param stackSize Platform dependent stack size
         * @throws IllegalThreadStateException if <code>group.destroy()</code> has
         *         already been done
         * @see java.lang.ThreadGroup
         * @see java.lang.Runnable
         */
    //带runnable参数的thread类的构造函数调用了这个方法
        private void create(ThreadGroup group, Runnable runnable, String threadName, long stackSize) {
            Thread currentThread = Thread.currentThread();
            if (group == null) {
                group = currentThread.getThreadGroup();
            }
            if (group.isDestroyed()) {
                throw new IllegalThreadStateException("Group already destroyed");
            }
            this.group = group;
            synchronized (Thread.class) {
                id = ++Thread.count;
            }
            if (threadName == null) {
                this.name = "Thread-" + id;
            } else {
                this.name = threadName;
            }
            //建立的runnable接口赋值给thread中的target
            this.target = runnable;
            this.stackSize = stackSize;
            this.priority = currentThread.getPriority();
            this.contextClassLoader = currentThread.contextClassLoader;
            // Transfer over InheritableThreadLocals.
            if (currentThread.inheritableValues != null) {
                inheritableValues = new ThreadLocal.Values(currentThread.inheritableValues);
            }
            // add ourselves to our ThreadGroup of choice
            this.group.addThread(this);
        }
    

    run方法的源代码:

    
     public void run() {
            if (target != null) {
                target.run();
            }
        }
    

    在run方法中,直接调用的是我们传入的target(Runnable对象)的run方法,并没有开启新的线程

    start方法的源代码:

    
     public synchronized void start() {
            checkNotStarted();
            hasBeenStarted = true;
            nativeCreate(this, stackSize, daemon);
        }
    

    start方法最后调用了nativeCreate的native方法,这个方法的主要作用是开启了一个新的线程。并且这个方法,会利用jni回调Thread的run方法。

    总结:

    1. 如果直接调用run方法,并没有开启新的线程,而是直接运行run方法里面的内容,
    2. 而start方法,则会调用native方法 nativeCreate 开启线程

    线程的停止

    实际开发中,我们使用线程的场景一般是执行耗时任务,如果我们开启了多个新的线程来执行新的任务,最后又不在对他进行关闭,这样有时候会浪费资源和内存的泄露。那我们怎么来管理我们的线程呢?目前有两种方法:

    • 我们自己手动开发,管理我们的线程,包括线程的启动,线程的回收, 线程的停止等
    • 使用JDK中自带的线程池技术

    今天我们不讲线程池,后面的文章会讲到。对于单个线程而言,上面我们将了他的启动,现在我们来讲他的关闭。

    线程的关闭的二种方式:

    1. 使用标志位

    我们定义一个标志位,在线程的run方法中,不断的循环检测标志位,从而确定是否退出

    public class ShutdownThread extends Thread {  
        public volatile boolean exit = false;   
            public void run() {   
            while (!exit){  
                //do something  
            }  
        }   
    }  
    
    2. 使用interrupt方法

    这里可以分为两种情况:

    • 线程处于阻塞状态,如使用了sleep,同步锁的wait,socket的receiver,accept等方法时,会使线程处于阻塞状态。当调用线程的interrupt()方法时,系统会抛出一个InterruptedException异常,代码中通过捕获异常,然后break跳出循环状态,使线程正常结束。通常很多人认为只要调用interrupt方法线程就会结束,实际上是错的,一定要先捕获InterruptedException异常之后通过break来跳出循环,才能正常结束run方法。
    public class ShutdownThread extends Thread {  
        public void run() {   
            while (true){  
                try{  
                        Thread.sleep(5*1000);阻塞5妙  
                    }catch(InterruptedException e){  
                        e.printStackTrace();  
                        break;//捕获到异常之后,执行break跳出循环。  
                    }  
            }  
        }   
    }   
    
    • 线程未进入阻塞状态,使用isInterrupted()判断线程的中断标志来退出循环,当使用interrupt()方法时,中断标志就会置true,和使用自定义的标志来控制循环是一样的道理。
    public class ShutdownThread extends Thread {  
        public void run() {   
            while (!isInterrupted()){  
                //do something, but no tthrow InterruptedException  
            }  
        }   
    }  
    

    为什么要区分进入阻塞状态和和非阻塞状态两种情况了,是因为当阻塞状态时,如果有interrupt()发生,系统除了会抛出InterruptedException异常外,还会调用interrupted()函数,调用时能获取到中断状态是true的状态,调用完之后会复位中断状态为false,所以异常抛出之后通过isInterrupted()是获取不到中断状态是true的状态,从而不能退出循环,因此在线程未进入阻塞的代码段时是可以通过isInterrupted()来判断中断是否发生来控制循环,在进入阻塞状态后要通过捕获异常来退出循环。

    因此使用interrupt()来退出线程的最好的方式应该是两种情况都要考虑:

    public class ThreadSafe extends Thread {  
        public void run() {   
            while (!isInterrupted()){ //非阻塞过程中通过判断中断标志来退出  
                try{  
                    Thread.sleep(5*1000);//阻塞过程捕获中断异常来退出  
                }catch(InterruptedException e){  
                    e.printStackTrace();  
                    break;//捕获到异常之后,执行break跳出循环。  
                }  
            }  
        }   
    }   
    

    相关文章

      网友评论

          本文标题:Java线程基础知识

          本文链接:https://www.haomeiwen.com/subject/lxusqxtx.html