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


    一、线程的两种启动方法

    分别是继承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()。

  • 相关阅读:
    剑指offer系列——41.和为S的连续正数序列
    剑指offer系列——40.数组中只出现一次的数字i-ii
    指针初始化
    剑指offer系列——39.平衡二叉树
    剑指offer系列——38.二叉树的深度
    剑指offer系列——37.数字在排序数组中出现的次数/在排序数组中查找元素的第一个和最后一个位置
    剑指offer系列——36.两个链表的第一个公共结点?
    剑指offer系列——35.数组中的逆序对**
    查看机器上GPU情况
    Linux下fork()、vfork()、clone()和exec()的区别
  • 原文地址:https://www.cnblogs.com/lbrs/p/10591109.html
Copyright © 2020-2023  润新知