美文网首页
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多线程高级特性(JDK8)

    [TOC] 一、Java多线程 1.Java多线程基础知识 Java 给多线程编程提供了内置的支持。一条线程指的是...

  • Android面试题4

    1 Java基础知识。线程,java虚拟机,内存模型等。2 Android基础知识。官方API,常用控件源码,自定...

  • 多线程--同步与锁

    同步与锁 上一篇中,笔者介绍了Java多线程的基础知识,主要讲解了进程/线程的区别、Java多线程的创建、Java...

  • java的守护线程与非守护线程

    最近重新研究Java基础知识,发现以前太多知识知识略略带过了,比较说Java的线程机制,在Java中有两类线程:U...

  • 读 GitChat 线程三部曲 笔记

    GitChat线程三部曲 Java 编程之美 - 线程相关的基础知识 Java 编程之美:并发编程高级篇之一 Ja...

  • Java多线程:生命周期,实现与调度

    前面几篇文章为Java多线程做足了铺垫,这篇终于到了正题,一起学习一下Java多线程的基础知识。 1. Java线...

  • Java synchronized 关键字原理学习

    在上一篇Java 线程 和 锁 基础知识已经介绍了Java中的线程和锁的一些基本概念,现在就来学习和了解下Java...

  • Java学习

    Java 基础知识点 基础语法 多线程 并发 IO、NIO 集合框架 网络 RMI SQL 上图来自Java 征途...

  • 并发编程艺术-4

    本文主要介绍的Java 并发编程的基础知识, 其中包括了:线程是什么? 为什么需要多线程? 线程的状态和相关操作。...

  • Android开发 Java线程基础

    简介 本篇文章是带大家了解 Java多线程的基础知识.主要内容: 介绍多线程的概念, 了解多线程的优点, 状态, ...

网友评论

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

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