多线程(一)两种线程的实现方法、后台线程和线程组、线程的生命周期

一、线程的两种启动方法

分别是继承java.lang.Thread类和实现java.lang.Runnable接口两种方法。

 举个例子:

package base;

public class ThreadTest extends Thread{
    private int count=10;
    public void run()
    {
        for(int i=0;i<10;i++)
            if(count>0)
            System.out.println(this.getName()+":count"+--count);
    }

public static void main(String []args)
{
    ThreadTest t1=new ThreadTest();
    ThreadTest t2=new ThreadTest();
    ThreadTest t3=new ThreadTest();
    t1.start();
    t2.start();
    t3.start();

}
}

结果显示3个线程各记了10个数,但是并非按照顺序来排列:

Thread-0:count9
Thread-2:count9
Thread-1:count9
Thread-2:count8
Thread-1:count8
Thread-2:count7
Thread-1:count7
Thread-2:count6
Thread-1:count6
Thread-1:count5
Thread-1:count4
Thread-1:count3
Thread-1:count2
Thread-1:count1
Thread-1:count0
Thread-2:count5
Thread-2:count4
Thread-2:count3
Thread-2:count2
Thread-2:count1
Thread-2:count0
Thread-0:count8
Thread-0:count7
Thread-0:count6
Thread-0:count5
Thread-0:count4
Thread-0:count3
Thread-0:count2
Thread-0:count1
Thread-0:count0

 接着是Runable接口的展示,但是与Thread类有所不同,因为接口的特性可以实现多个,而不能继承多个父类

package base;

public class RunnableTest implements Runnable{
    private int count=10;
public void run()
{
    for(int i=0;i<10;i++)
        if(count>0)
        System.out.println(Thread.currentThread().getName()+":count"+--count);

}
public static void main(String []args)
{
    RunnableTest rt=new RunnableTest();
    Thread th1=new Thread(rt);
    Thread th2=new Thread(rt);
    Thread th3=new Thread(rt);
    th1.start();
    th2.start();
    th3.start();
;
}
}

结果:

Thread-1:count9
Thread-2:count8
Thread-0:count7
Thread-2:count5
Thread-1:count6
Thread-2:count3
Thread-0:count4
Thread-2:count1
Thread-1:count2
Thread-0:count0

可以看出,接口可以共用资源,3个线程一共计了10个数。

总结一下:一个类继承Thread()类之后,实现run()方法之后。然后只需在主函数(或其他合适的地方)实例化并且调用start()方法即可。

而使用接口的情况,需要一个类实现该接口,然后实现run()方法。在主函数中实例化该类,然后将该实例化对象作为参数创建一个新的Thread()对象,最终调用start()方法。

其实所谓多线程就是指多个Thread()对象之间不一定按照顺序执行了!

二、后台线程和线程组

这两个概念似乎不经常出现,但是有必要在线程生命周期之前熟悉它们。

后台线程daemon thread 是相对于用户线程user thread来说的。有些称为守护线程或者精灵线程,当一个程序中只有后台线程时,程序会立刻退出。如果还存在其他用户线程那么程序不会中止。而我们可以通过isDaemon()方法判断一个线程是否是后台线程。两者之间可以相互切换,通过setDaemon(boolean on)如果参数是true,该线程将会被设置为后台线程,false则会被设置为用户线程。不过该方法的调用只能在start()之前,一旦线程启动,那么它的属性就无法再进行切换。

接下来是后台线程的一个简单是示例:

package base;

public class DaemonThread extends Thread{
public void run()
{
    for(int i=0;i<10;i++)
    {
        System.out.println("线程"+i);
        try 
        {
            sleep(3000);
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
    }
}
public static void main(String []args)
{
    Thread a=new DaemonThread();
    a.setDaemon(true);
    a.start();
    if(a.isDaemon())
    {
        System.out.println("IsDaemon");
    }
    else
    {
        System.out.println("IsNotDaemon");
    }
    System.out.println("XXX");
    
}
}

结果:

IsDaemon
XXX
线程0

结果代表当主线程运行完毕后,即“XXX”打印完毕后,就只剩下后台线程了,仍然再暂停中,因此程序直接结束,至于打印出线程0是因为那是在程序结束之前已经运行完毕。

可以想象的,当我们把setDaemon()参数设置为false或者删掉这句代码,那么程序将会在上面结果的基础上每隔3000ms打印出一个线程i,直到结束。而如果把sleep代码去掉但是保留setDaemon的部分,那么结果也会打印出所有的线程的结果。猜想是后台线程运行较快,在主线程运行结束前就已经结束,只有使用挂起才能观察的效果,这也是为什么示例都会采取挂起的动作。

于是我就想到把10次循环改为无限循环,看在主线城结束时能打印多少?

经过实测,打印到了线程17,基本大致上验证了咱们之前的猜想(虽然每次结果并不相同)。

 

线程组ThreadGroup好像比较相对的不重要,不过倒是学到了一些方法的使用:

    {
        Thread t=Thread.currentThread();
        System.out.println(t.getName());
        ThreadGroup tg=t.getThreadGroup();
        System.out.println(tg.getName());
        System.out.println("当前线程组含有"+tg.activeCount()+"个线程");
        int n=tg.activeCount();
        Thread [] th=new Thread [n];
        tg.enumerate(th);
        for (Thread a:th)
            System.out.println(a.getName());

        tg=tg.getParent();
        System.out.println(tg.getName());
        System.out.println("当前线程组含有"+tg.activeCount()+"个线程");

        int m=tg.activeCount();
        Thread [] thr=new Thread [m];
        tg.enumerate(thr);
        for (Thread b:thr)
            System.out.println(b.getName());


    }

例如currentThread()获得当前线程,是静态方法,Thread类调用。getThreadGroup()获得当前线程组,不是静态方法,Thread对象调用。activeCount()表示线程组中的线程数量。还有enumerate(a)将线程组里的线程赋给线程数组a。getName()获得线程或者线程组的名称。

对了,贴上结果:

三、线程的生命周期和Java中的线程方法

当我们new一个新的Thread的对象时,线程就进入到了新生态,而调用start()方法就进入了就绪态(或可执行状态),JVM通过一定的调度规则使就绪态的线程变成运行态,运行态执行run()方法,执行结束后转变为死亡态。运行态的线程调用静态方法sleep(long millis)进入睡眠态,过了指定的睡眠时间(millis/1000)秒,线程重新变为运行态,接着运行。调用成员方法wait()方法进入等待态,需要别的线程调用Object的成员方法notify()或者notifyAll(),又或者时间到了,有可能(?)唤醒重新进入就绪态,重新等待JVM的调度。

 

介绍阻塞态,需要先介绍一个新的概念——线程的优先级。每个线程都有优先级,在Java中,线程的最小优先级为常量Thread.MIN_PRIORITY,其值为1。最大的优先级为Thread.MAX_PRIORITY,值为10。如果不设置优先级,那么线程的默认优先级为Thread.NORM_PRIORITY,值为5。通过getPriority()方法可以得到线程的优先级,而setPriority(int newPriority)则是设置优先级,当设置的参数大于所允许的最大优先级时,线程的优先级就变为所允许的最大优先级。

为什么会有最大优先级呢?因为这里有一个设置最大优先级的方法:setMaxPriority(int maxP)。同样的,也能查到最大优先级getMaxPriority()。但是线程的最大优先级不能超过父线程组的最大优先级,因此会在maxP和父线程最大优先级选择较小的那一个。注:maxP不能超过正常的优先级范围,否则不起作用。

当有多个处于就绪态线程时,优先级高的线程会优先执行,进入运行态。优先级一样则随机选择。如果线程共享资源,但是资源只能被有限个线程占用,JVM就会优先选取优先级高的线程进入运行态,而其余的就绪态线程由于资源短缺就会转换为阻塞态。当资源准备就绪,则阻塞态线程会自动重新进入就绪态。

小笔记:

mythread.start()会启动一个新线程,并在新线程中运行run()方法。
而mythread.run()则会直接在当前线程中运行run()方法,并不会启动一个新线程来运行run()。

 

posted @ 2019-03-24 22:43  LeftBody  阅读(293)  评论(0编辑  收藏  举报