美文网首页
并发编程(六):深入分析Thread

并发编程(六):深入分析Thread

作者: 北京的小毛驴 | 来源:发表于2017-02-22 20:54 被阅读0次

什么是线程安全性

如果一个类可以安全地被多个线程使用,它就是线程安全的。你无法对此论述提出任何争议,但也无法从中得到更多有意义的帮助。那么我们如何辨别线程安全与非线程安全的类?我们甚至又该如何理解“安全”呢?任何一个合理的“线程安全性”定义,其关键在于“正确性”的概念。在<<Java并发编程实践>>书中作者是这样定义的:

一个类是是线程安全的,是指在被多个线程访问时,类可以持续进行正确的行为。或当多个线程访问一个类时,如果不用考虑这些线程在运行时环境下的调度和交替执行,并且不需要额外的同步及在调用方代码不必作其他的协调,这个类的行为仍然是正确的,那么称这个类是线程安全的。

线程的六种状态

线程从创建到销毁期间有六种状态:

  • New: 至今尚未启动的线程的状态。
  • Runnable :可运行线程的线程状态。
  • Blocked :受阻塞并且正在等待监视器锁的某一线程的线程状态。
  • Waiting :某一等待线程的线程状态。
  • Timed_waiting:具有指定等待时间的某一等待线程的线程状态。
  • Terminated:已终止线程的线程状态。线程已经结束执行。

如下图所示:

image

源码解析

属性

//线程名字,通过构造参数来指定  
private char        name[];  
//表示线程的优先级,优先级越高,越优先被执行(最大值为10,最小值为1,默认值为5)  
private int         priority;  
  
private Thread      threadQ;  
private long        eetop;  
  
/* Whether or not to single_step this thread. */  
private boolean     single_step;  
  
//线程是否是守护线程:当所有非守护进程结束或死亡后,程序将停止   
private boolean     daemon = false;  
  
/* JVM state */  
private boolean     stillborn = false;  
  
//将要执行的任务  
private Runnable target;  
  
/* 线程组表示一个线程的集合。此外,线程组也可以包含其他线程组。线程组构成一棵树,在树中,除了初始线程组外,每个线程组都有一个父线程组。  */  
private ThreadGroup group;  
  
/* The context ClassLoader for this thread */  
private ClassLoader contextClassLoader;  
  
/* The inherited AccessControlContext of this thread */  
private AccessControlContext inheritedAccessControlContext;  
  
/*第几个线程,在init初始化线程的时候用来赋给thread.name */    
private static int threadInitNumber;  
private static synchronized int nextThreadNum() {  
    return threadInitNumber++;  
}  
  
/* ThreadLocal values pertaining to this thread. This map is maintained 
 * by the ThreadLocal class. */  
ThreadLocal.ThreadLocalMap threadLocals = null;  
  
/* 
 * InheritableThreadLocal values pertaining to this thread. This map is 
 * maintained by the InheritableThreadLocal class. 
 */  
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;  
  
/* 
 * The requested stack size for this thread, or 0 if the creator did 
 * not specify a stack size.  It is up to the VM to do whatever it 
 * likes with this number; some VMs will ignore it. 
 */  
private long stackSize;  
  
/* 
 * JVM-private state that persists after native thread termination. 
 */  
private long nativeParkEventPointer;  
  
/* 
 * Thread ID 
 */  
private long tid;  
  
/* For generating thread ID */  
private static long threadSeqNumber;  
  
/* 
 *线程从创建到最终的消亡,要经历若干个状态。 
 *一般来说,线程包括以下这几个状态:创建(new)、就绪(runnable)、运行(running)、阻塞(blocked)、time waiting、waiting、消亡(dead)。 
 *  
 */  
private volatile int threadStatus = 0;  

start()操作

//启动新创建的线程  
public synchronized void start() {  
    /** 
     * This method is not invoked for the main method thread or "system" 
     * group threads created/set up by the VM. Any new functionality added 
     * to this method in the future may have to also be added to the VM. 
     * 
     * A zero status value corresponds to state "NEW". 
     */  
    /*这个方法不会被主线程调用或通过虚拟机系统线程组创建起来。未来任何添加到该方法里的新功能可能需要加入到虚拟机中 
     *  
     * 状态new的值是0. 
     * */  
    if (threadStatus != 0)  
        throw new IllegalThreadStateException();  
  
    /* Notify the group that this thread is about to be started 
     * so that it can be added to the group's list of threads 
     * and the group's unstarted count can be decremented. */  
    /* 通知线程组新线程将要启动,以便它可以添加到线程组列表并且线程组没有开始计数*/  
    group.add(this);  
  
    boolean started = false;  
    try {  
        start0();  
        started = true;  
    } finally {  
        try {  
            if (!started) {  
                group.threadStartFailed(this);  
            }  
        } catch (Throwable ignore) {  
            /* do nothing. If start0 threw a Throwable then 
              it will be passed up the call stack */  
        }  
    }  
}  

run()操作

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

start()和run()之间有什么区别?

package com.game.thread;  
  
/** 
 *  
 * @author liulongling 
 * 
 */  
public class ThreadTest extends Thread{  
  
    public ThreadTest(String name) {  
        super.setName(name);  
    }  
  
    @Override  
    public void run() {  
        for(int i = 0; i < 5;i++)  
        {  
            System.out.println(super.getName()+":"+i);  
        }  
    }  
  
    public static void main(String[] args) {  
        ThreadTest test = new ThreadTest("A");  
        ThreadTest test1 = new ThreadTest("B");  
          
        test.run();  
        test1.run();  
          
        if(Thread.activeCount()>=1)  
        {  
            Thread.yield();  
        }  
    }  
}  

控制台:

A:0
A:1
A:2
A:3
A:4
B:0
B:1
B:2
B:3
B:4

代码示例:start()方法使用

package com.game.thread;  
  
/** 
 *  
 * @author liulongling 
 * 
 */  
public class ThreadTest extends Thread{  
  
    public ThreadTest(String name) {  
        super.setName(name);  
    }  
  
    @Override  
    public void run() {  
        for(int i = 0; i < 5;i++)  
        {  
            System.out.println(super.getName()+":"+i);  
        }  
    }  
  
    public static void main(String[] args) {  
        ThreadTest test = new ThreadTest("A");  
        ThreadTest test1 = new ThreadTest("B");  
          
        test.start();  
        test1.start();  
          
        if(Thread.activeCount()>=1)  
        {  
            Thread.yield();  
        }  
    }  
}  

控制台:

B:0
A:0
B:1
A:1
B:2
A:2
B:3
A:3
B:4
A:4

结果分析

以上结果可以发现run()和单线程一样每次只能执行一个线程,而start()不一样有多个线程交叉执行着。我们知道start()方法被用来启动新创建的线程,而且start()内部通过本地系统调用了run()方法,这和直接调用run()方法的效果不一样。当你调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启动,start()方法会启动新的线程,其中多个线程在CPU中是支持并发执行的。那么有没有什么方法可以让A线程优先执行呢?

setPriority()操作

public final void setPriority(int newPriority) {  
       ThreadGroup g;  
       checkAccess();  
       if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {  
           throw new IllegalArgumentException();  
       }  
       if((g = getThreadGroup()) != null) {  
           if (newPriority > g.getMaxPriority()) {  
               newPriority = g.getMaxPriority();  
           }  
           setPriority0(priority = newPriority);  
       }  
   }  
private native void setPriority0(int newPriority);  

setPriority使用了native关键字,在Java API中,一个native方法意味着这个方法没有使用Java语言实现,只能通过代码示例去分析它的原理。在代码中将线程A的执行优先级设置为最高,同时线程B的优先级设置为最低,那么预期的结果应该是线程A先执行完后再执行线程B,代码如下:

package com.game.thread;  
  
/** 
 *  
 * @author liulongling 
 * 
 */  
public class ThreadTest extends Thread{  
  
    public ThreadTest(String name) {  
        super.setName(name);  
    }  
  
    @Override  
    public void run() {  
        for(int i = 0; i < 5;i++)  
        {  
            System.out.println(super.getName()+":"+i);  
        }  
    }  
  
    public static void main(String[] args) {  
        ThreadTest test = new ThreadTest("A");  
        ThreadTest test1 = new ThreadTest("B");  
        //MAX_PRIORITY是最高优先级  
        test.setPriority(MAX_PRIORITY);  
          
        test.setPriority(MIN_PRIORITY);  
        test.start();  
        test1.start();  
          
        if(Thread.activeCount()>=1)  
        {  
            Thread.yield();  
        }  
    }  
}  

控制台:

A:0
A:1
A:2
A:3
A:4
B:0
B:1
B:2
B:3
B:4

sleep(long millis)操作

public static native void sleep(long millis) throws InterruptedException;  

sleep也是使用了native关键字,调用了底层方法。sleep是指线程被调用时,占着CPU不工作,形象地说明为“占着CPU睡觉”,此时,系统的CPU部分资源被占用,其他线程无法进入。多线程下使用时需要注意的是sleep方法不会释放锁。比如:线程A和线程B执行一段加锁代码,线程A先进去,线程B在外面等待,其中代码程序有sleep方法让线程休眠,休眠后锁并不会被释放,线程B也只能继续在外面等待直到休眠时间结束。代码如下:

package com.game.thread;  
  
import java.io.IOException;  
  
/** 
 *  
 * @author liulongling 
 * 
 */  
public class ThreadTest{  
  
    private int i = 10;  
    private Object object = new Object();  
      
    MyThread thread1 = new MyThread("A");  
    MyThread thread2 = new MyThread("B");  
       
    public static void main(String[] args) throws IOException  {  
        ThreadTest test = new ThreadTest();  
        test.thread1.start();  
        test.thread2.start();  
    }   
       
       
    class MyThread extends Thread{  
        public MyThread(String name) {  
            super.setName(name);  
        }  
        @Override  
        public void run() {  
            synchronized (object) {  
                System.out.println(Thread.currentThread().getName()+":"+i++);  
                try {  
                    System.out.println("线程"+Thread.currentThread().getName()+"进入睡眠状态");  
                    Thread.currentThread().sleep(1000);  
                } catch (InterruptedException e) {  
                    // TODO: handle exception  
                }  
                System.out.println("线程"+Thread.currentThread().getName()+"被唤醒");  
                System.out.println(Thread.currentThread().getName()+":"+i);  
            }  
        }  
    }  
}  

控制台:

A:10
线程A进入睡眠状态
线程A被唤醒
A:11
B:11
线程B进入睡眠状态
线程B被唤醒
B:12

yield()操作

调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程。它跟sleep方法类似,同样不会释放锁。但是yield不能控制具体的交出CPU的时间,另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。
 注意,调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。

isAlive()

/** 
 * Tests if this thread is alive. A thread is alive if it has 
 * been started and has not yet died. 
 * 
 * @return  <code>true</code> if this thread is alive; 
 *          <code>false</code> otherwise. 
 */  
public final native boolean isAlive();  

表示线程当前是否为可用状态,如果线程已经启动,并且当前没有任何异常的话,则返回true,否则为false

join()操作

join有3个重载方法:

1、 join()

//立即阻塞调用线程,直到该线程执行结束  
public final synchronized void join(long millis)  
        throws InterruptedException {  
    long base = System.currentTimeMillis();  
    long now = 0;  
  
    if (millis < 0) {  
        throw new IllegalArgumentException("timeout value is negative");  
    }  
  
    if (millis == 0) {  
        //线程状态正常  
        while (isAlive()) {  
            wait(0);  
        }  
    } else {  
        while (isAlive()) {  
            long delay = millis - now;  
            if (delay <= 0) {  
                break;  
            }  
            wait(delay);  
            now = System.currentTimeMillis() - base;  
        }  
    }  
}  
package com.game.thread;  
  
import java.io.IOException;  
  
/** 
 *  
 * @author liulongling 
 * 
 */  
public class ThreadTest{  
  
    private int i = 10;  
    private Object object = new Object();  
  
    MyThread thread1 = new MyThread("A");  
    MyThread thread2 = new MyThread("B");  
  
    public static void main(String[] args) throws IOException  {  
        ThreadTest test = new ThreadTest();  
        test.thread1.start();  
        System.out.println("线程"+Thread.currentThread().getName()+"等待");  
        try {  
            test.thread1.join();  
        } catch (InterruptedException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  
        System.out.println("线程"+Thread.currentThread().getName()+"执行");  
        for (int j = 0; j < 5; j++)  
        {  
            System.out.println(Thread.currentThread().getName() + ":" + j);  
        }  
    }   
  
  
    class MyThread extends Thread{  
        public MyThread(String name) {  
            super.setName(name);  
        }  
        @Override  
        public void run() {  
            synchronized (object) {  
                System.out.println(Thread.currentThread().getName()+":"+i++);  
                try {  
                    System.out.println("线程"+Thread.currentThread().getName()+"进入睡眠状态");  
                    Thread.currentThread().sleep(1000);  
                } catch (InterruptedException e) {  
                    // TODO: handle exception  
                }  
                System.out.println("线程"+Thread.currentThread().getName()+"被唤醒");  
                System.out.println(Thread.currentThread().getName()+":"+i);  
            }  
        }  
    }  
}  

控制台:

线程main等待
A:10
线程A进入睡眠状态
线程A被唤醒
A:11
线程main执行
main:0
main:1
main:2
main:3
main:4

MSDN上解释join无参方法其作用为:阻塞 “调用线程” 直到某个线程结束。从上面结果可以分析出,A线程其实在main线程上运行,我们可以说main线程调用了A线程或称main线程为“调用线程”,A线程调用join()方法后将调用线程阻塞直到A线程结束后控制台才输出来main:0...。我们去掉join方法看下控制台输出结果。

控制台:

main:0
main:1
A:10
main:2
线程A进入睡眠状态
main:3
main:4
线程A被唤醒
A:11

2、join(long millis)

package com.game.thread;  
  
import java.io.IOException;  
  
/** 
 *  
 * @author liulongling 
 * 
 */  
public class ThreadTest{  
  
    private int i = 10;  
    private Object object = new Object();  
  
    MyThread thread1 = new MyThread("A");  
    MyThread thread2 = new MyThread("B");  
  
    public static void main(String[] args) throws IOException  {  
        ThreadTest test = new ThreadTest();  
        test.thread1.start();  
        System.out.println("线程"+Thread.currentThread().getName()+"等待");  
        try {  
            test.thread1.join(500);  
        } catch (InterruptedException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  
        System.out.println("线程"+Thread.currentThread().getName()+"执行");  
        for (int j = 0; j < 5; j++)  
        {  
            System.out.println(Thread.currentThread().getName() + ":" + j);  
        }  
    }   
  
  
    class MyThread extends Thread{  
        public MyThread(String name) {  
            super.setName(name);  
        }  
        @Override  
        public void run() {  
            synchronized (object) {  
                System.out.println(Thread.currentThread().getName()+":"+i++);  
                try {  
                    System.out.println("线程"+Thread.currentThread().getName()+"进入睡眠状态");  
                    Thread.currentThread().sleep(1000);  
                } catch (InterruptedException e) {  
                    // TODO: handle exception  
                }  
                System.out.println("线程"+Thread.currentThread().getName()+"被唤醒");  
                System.out.println(Thread.currentThread().getName()+":"+i);  
            }  
        }  
    }  
}  

控制台:

线程main等待
A:10
线程A进入睡眠状态
线程main执行
main:0
main:1
main:2
main:3
main:4
线程A被唤醒
A:11

从上面结果可以分析出,在A线程还未结束,主线程已经开始执行。原因是我们给主线程设置的阻塞时间是500ms,小于A线程run()方法里的1000ms休眠时间。

3、join(long millis, int nanos)

阻塞调用线程的时间最长为 millis 毫秒 + nanos 纳秒...这里就不举例说明了,原理和上一个方法一样。

interrupt()操作

interrupt的作用是中断正被阻塞的线程,比如我给某一线程休眠了10s时间,如果我在这个线程上调用了interrupt方法,看看会有什么效果。代码如下:

package com.game.thread;  
  
import java.io.IOException;  
  
/** 
 *  
 * @author liulongling 
 * 
 */  
public class ThreadTest{  
  
    private int i = 10;  
    private Object object = new Object();  
  
    MyThread thread1 = new MyThread("A");  
    MyThread thread2 = new MyThread("B");  
  
    public static void main(String[] args) throws IOException  {  
        ThreadTest test = new ThreadTest();  
        test.thread1.start();  
        test.thread1.interrupt();  
    }   
  
  
    class MyThread extends Thread{  
        public MyThread(String name) {  
            super.setName(name);  
        }  
        @Override  
        public void run() {  
            synchronized (object) {  
                System.out.println(Thread.currentThread().getName()+":"+i++);  
                try {  
                    System.out.println("线程"+Thread.currentThread().getName()+"进入睡眠状态");  
                    Thread.currentThread().sleep(10000);  
                } catch (InterruptedException e) {  
                    System.out.println("你被中断了");  
                }  
                System.out.println("线程"+Thread.currentThread().getName()+"被唤醒");  
                System.out.println(Thread.currentThread().getName()+":"+i);  
            }  
        }  
    }  
}  

控制台:

A:10
线程A进入睡眠状态
你被中断了
线程A被唤醒
A:11

使用了 interrupt的结果是在休眠代码处抛出一个异常,并且阻塞马上停止了。

setDaemon()操作

用来设置线程是否成为守护线程和判断线程是否是守护线程。
守护线程和用户线程的区别在于:守护线程依赖于创建它的线程,而用户线程则不依赖。举个简单的例子:如果在main线程中创建了一个守护线程,当main方法运行完毕之后,守护线程也会随着消亡。而用户线程则不会,用户线程会一直运行直到其运行完毕。在JVM中,像垃圾收集器线程就是守护线程。Thread类有一个比较常用的静态方法currentThread()用来获取当前线程。

作者:小毛驴,一个Java游戏服务器开发者 原文地址:https://liulongling.github.io/

相关文章

网友评论

      本文标题:并发编程(六):深入分析Thread

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