线程对象
每一个线程都是和类Thread的实例相关联的。在Java中,有两种基本的使用Thread对象的方式,可用来创建并发性程序。
  1.在应用程序需要发起异步任务的时候,只要生成一个Thread对象即可(继承Thread类和实现runnable接口),这样可以直接控制线程的创建并对其进行管理。
  2.把应用程序的任务交给执行器(executor),这样可以将对线程的管理从程序中抽离出来。

Thread类中的静态方法

Thread类中的静态方法表示操作的线程是"正在执行静态方法所在的代码块的线程"。为什么Thread类中要有静态方法,这样就能对CPU当前正在运行的线程进行操作。下面来看一下Thread类中的静态方法:

1、currentThread()

currentThread()方法返回的是对当前正在执行线程对象的引用。看一个重要的例子,然后得出结论:

public class MyThread04 extends Thread
{
    static
    {
        System.out.println("静态块的打印:" + 
                Thread.currentThread().getName());    
    }
    
    public MyThread04()
    {
        System.out.println("构造方法的打印:" + 
                Thread.currentThread().getName());    
    }
    
    public void run()
    {
        System.out.println("run()方法的打印:" + 
                Thread.currentThread().getName());
    }
}

public static void main(String[] args)
{
    MyThread04 mt = new MyThread04();
    mt.start();
}

看一下运行结果:

静态块的打印:main
构造方法的打印:main
run()方法的打印:Thread-0

这个例子说明了,线程类的构造方法、静态块是被main线程调用的,而线程类的run()方法才是应用线程自己调用的

1、睡眠  (public static native void sleep(long millis) throws InterruptedException;)(是Thread类的静态方法)
Thread.sleep(long millis)和Thread.sleep(long millis, int nanos)静态方法强制当前正在执行的线程休眠(暂停执行),以“减慢线程”。该方法的作用是在指定的毫秒内让当前"正在执行的线程"休眠(暂停执行)。这个"正在执行的线程"是关键,指的是Thread.currentThread()返回的线程。根据JDK API的说法,"该线程不丢失任何监视器的所属权",简单说就是sleep代码上下文如果被加锁了,锁依然在,但是CPU资源会让出给其他线程。
线程睡眠的原因:线程执行太快,或者需要强制进入下一轮,因为Java规范不保证合理的轮换。
睡眠的实现:调用静态方法。

try {
   Thread.sleep(123);
 } catch (InterruptedException e) {
   e.printStackTrace(); 
 }

睡眠的位置:为了让其他线程有机会执行,可以将Thread.sleep()的调用放线程run()之内。这样才能保证该线程执行过程中会睡眠。

注意:

  1、线程睡眠是帮助所有线程获得运行机会的最好方法。

  2、线程睡眠到期自动苏醒,并返回到可运行状态,不是运行状态。sleep()中指定的时间是线程不会运行的最短时间。因此,sleep()方法不能保证该线程睡眠到期后就开始执行。

  3、sleep()是静态方法,只能控制当前正在运行的线程。

2、线程的优先级和线程让步yield()  (是Thread类的静态方法)

线程的让步是通过Thread.yield()来实现的。yield()方法的作用是:暂停当前正在执行的线程对象,并执行其他线程。

要理解yield(),必须了解线程的优先级的概念。线程总是存在优先级,优先级范围在1~10之间。JVM线程调度程序是基于优先级的抢先调度机制。在大多数情况下,当前运行的线程优先级将大于或等于线程池中任何线程的优先级。但这仅仅是大多数情况。

注意:当设计多线程应用程序的时候,一定不要依赖于线程的优先级。因为线程调度优先级操作是没有保障的,只能把线程优先级作用作为一种提高程序效率的方法,但是要保证程序不依赖这种操作。

当线程池中线程都具有相同的优先级,调度程序的JVM实现自由选择它喜欢的线程。这时候调度程序的操作有两种可能:一是选择一个线程运行,直到它阻塞或者运行完成为止。二是时间分片,为池内的每个线程提供均等的运行机会。

设置线程的优先级:线程默认的优先级是创建它的执行线程的优先级。可以通过setPriority(int newPriority)更改线程的优先级。例如:

Thread t = new MyThread();
 t.setPriority(8);
 t.start();

线程优先级为1~10之间的正整数,JVM从不会改变一个线程的优先级。然而,1~10之间的值是没有保证的。一些JVM可能不能识别10个不同的值,而将这些优先级进行每两个或多个合并,变成少于10个的优先级,则两个或多个优先级的线程可能被映射为一个优先级。

线程默认优先级是5,Thread类中有三个常量,定义线程优先级范围:

static int MAX_PRIORITY 
 线程可以具有的最高优先级。
static int MIN_PRIORITY 
 线程可以具有的最低优先级。
static int NORM_PRIORITY 
 分配给线程的默认优先级。

3、Thread.yield()方法

Thread.yield()方法作用是:暂停当前正在执行的线程对象,并执行其他线程。

yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。

结论:yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。

看一下例子:

public class MyThread08 extends Thread
{
    public void run()
    {
        long beginTime = System.currentTimeMillis();
        int count = 0;
        for (int i = 0; i < 50000000; i++)
        {
            Thread.yield();
            count = count + i + 1;
        }
        long endTime = System.currentTimeMillis();
        System.out.println("用时:" + (endTime - beginTime) + "毫秒!");
    }
}

public static void main(String[] args)
{
    MyThread08 mt = new MyThread08();
    mt.start();
}
——————————————————————————————————————————————————————————————————————————————————————————————————
结果:
用时:3264毫秒!
用时:3299毫秒!
用时:3232毫秒!
用时:3256毫秒!
用时:3283毫秒!
用时:3504毫秒!
用时:3378毫秒!

看到,每次执行的用时都不一样,证明了yield()方法放弃CPU的时间并不确定。

4. isDaeMon、setDaemon(boolean on) (是Thread的实例方法)

讲解两个方法前,首先要知道理解一个概念。Java中有两种线程,一种是用户线程,一种是守护线程。守护线程是一种特殊的线程,它的作用是为其他线程的运行提供便利的服务,最典型的应用便是GC线程。如果进程中不存在非守护线程了,那么守护线程自动销毁,因为没有存在的必要,为别人服务,结果服务的对象都没了,当然就销毁了。理解了这个概念后,看一下例子:

public class MyThread1 extends Thread
{
    private int i = 0;
    
    public void run()
    {
        try
        {
            while (true)
            {
                i++;
                System.out.println("i = " + i);
                Thread.sleep(1000);
            }
        } 
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }
}
public static void main(String[] args)
    {
        try
        {
            MyThread1 mt = new MyThread1();
            mt.setDaemon(true);
            mt.start();
            Thread.sleep(5000);
            System.out.println("我离开thread对象再也不打印了,我停止了!");
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }

————————————————————————————————
结果:
i = 1
i = 2
i = 3
i = 4
i = 5
我离开thread对象再也不打印了,我停止了!
i = 6

要解释一下。我们将MyThread1线程设置为守护线程,看到第6行的那句话,而i停在6不会再运行了。这说明,main线程运行了5秒多结束,而i每隔1秒累加一次,5秒后main线程执行完结束了,MyThread1作为守护线程,main函数都运行完了,自然也没有存在的必要了,就自动销毁了,因此也就没有再往下打印数字。

关于守护线程,有一个细节注意下,setDaemon(true)必须在线程start()之前

5. holdsLock()    public static native boolean holdsLock(Object obj);

判断某个线程是否持有对象监视器:Thread类提供了一个holdsLock(Object obj)方法,当且仅当对象obj的监视器被某条线程持有的时候才会返回true,注意这是一个static方法,这意味着"某条线程"指的是当前线程。

Object o = new Object();
@Test
public void test1() throws Exception {
    new Thread(new Runnable() {
        @Override
        public void run() {
            synchronized(o) {
                System.out.println("child thread: holdLock: " + 
                    Thread.holdsLock(o));
            }
        }
    }).start();
    System.out.println("main thread: holdLock: " + Thread.holdsLock(o));
    Thread.sleep(2000);
}

main thread: holdLock: false
child thread: holdLock: true

 6. 线程实例对象的 join()方法    public final void join() throws InterruptedException

一、作用

  Thread类中的join方法的主要作用就是同步,它可以使得线程之间的并行执行变为串行执行。具体看代码:

public class JoinTest {
    public static void main(String [] args) throws InterruptedException {
        ThreadJoinTest t1 = new ThreadJoinTest("小明");
        ThreadJoinTest t2 = new ThreadJoinTest("小东");
        t1.start();
        /**join的意思是使得放弃当前线程的执行,并返回对应的线程,例如下面代码的意思就是:
         程序在main线程中调用t1线程的join方法,则main线程放弃cpu控制权,并返回t1线程继续执行直到线程t1执行完毕
         所以结果是t1线程执行完后,才到主线程执行,相当于在main线程中同步t1线程,t1执行完了,main线程才有执行的机会
         */
        t1.join();
        t2.start();
    }

}
class ThreadJoinTest extends Thread{
    public ThreadJoinTest(String name){
        super(name);
    }
    @Override
    public void run(){
        for(int i=0;i<1000;i++){
            System.out.println(this.getName() + ":" + i);
        }
    }
}

上面程序结果是先打印完小明线程,在打印小东线程;  

上面注释也大概说明了join方法的作用:在A线程中调用了B线程的join()方法时,表示只有当B线程执行完毕时,A线程才能继续执行。注意,这里调用的join方法是没有传参的,join方法其实也可以传递一个参数给它的,具体看下面的简单例子:

public class JoinTest {
    public static void main(String [] args) throws InterruptedException {
        ThreadJoinTest t1 = new ThreadJoinTest("小明");
        ThreadJoinTest t2 = new ThreadJoinTest("小东");
        t1.start();
        /**join方法可以传递参数,join(10)表示main线程会等待t1线程10毫秒,10毫秒过去后,
         * main线程和t1线程之间执行顺序由串行执行变为普通的并行执行
         */
        t1.join(10);
        t2.start();
    }

}
class ThreadJoinTest extends Thread{
    public ThreadJoinTest(String name){
        super(name);
    }
    @Override
    public void run(){
        for(int i=0;i<1000;i++){
            System.out.println(this.getName() + ":" + i);
        }
    }
}

上面代码结果是:程序执行前面10毫秒内打印的都是小明线程,10毫秒后,小明和小东程序交替打印。

所以,join方法中如果传入参数,则表示这样的意思:如果A线程中掉用B线程的join(10),则表示A线程会等待B线程执行10毫秒,10毫秒过后,A、B线程并行执行。需要注意的是,jdk规定,join(0)的意思不是A线程等待B线程0秒,而是A线程等待B线程无限时间,直到B线程执行完毕,即join(0)等价于join()。

二、join与start调用顺序问题

  上面的讨论大概知道了join的作用了,那么,入股 join在start前调用,会出现什么后果呢?先看下面的测试结果

public class JoinTest {
    public static void main(String [] args) throws InterruptedException {
        ThreadJoinTest t1 = new ThreadJoinTest("小明");
        ThreadJoinTest t2 = new ThreadJoinTest("小东");
        /**join方法可以在start方法前调用时,并不能起到同步的作用
         */
        t1.join();
        t1.start();
        //Thread.yield();
        t2.start();
    }

}
class ThreadJoinTest extends Thread{
    public ThreadJoinTest(String name){
        super(name);
    }
    @Override
    public void run(){
        for(int i=0;i<1000;i++){
            System.out.println(this.getName() + ":" + i);
        }
    }
}

上面代码执行结果是:小明和小东线程交替打印。

所以得到以下结论:join方法必须在线程start方法调用之后调用才有意义。这个也很容易理解:如果一个线程都没有start,那它也就无法同步了。

  三、join方法实现原理

  有了上面的例子,我们大概知道join方法的作用了,那么,join方法实现的原理是什么呢?

  其实,join方法是通过调用线程的wait方法来达到同步的目的的。例如,A线程中调用了B线程的join方法,则相当于A线程调用了B线程的wait方法,在调用了B线程的wait方法后,A线程就会进入阻塞状态,具体看下面的源码:

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;
            }
        }
    }

从源码中可以看到:join方法的原理就是调用相应线程的wait方法进行等待操作的,例如A线程中调用了B线程的join方法,则相当于在A线程中调用了B线程的wait方法,当B线程执行完(或者到达等待时间),B线程会自动调用自身的notifyAll方法唤醒A线程,从而达到同步的目的。