您的位置:首页 > 编程语言 > Java开发

64.JAVA编程思想——优先级

2016-05-08 21:19 411 查看
64.JAVA编程思想——优先级
线程的优先级(Priority)告诉调试程序该线程的重要程度有多大。如果有大量线程都被堵塞,都在等候运行,调试程序会首先运行具有最高优先级的那个线程。然而,这并不表示优先级较低的线程不会运行(换言之,不会因为存在优先级而导致死锁)。若线程的优先级较低,只不过表示它被准许运行的机会小一些而已。

可用getPriority()方法读取一个线程的优先级,并用setPriority()改变它。在下面程序中,大家会发现计数器的计数速度慢了下来,因为它们关联的线程分配了较低的优先级:

1     代码

import java.awt.*;

import java.awt.event.*;

import java.applet.*;

class Ticker2
extends Thread {

    private Button
b = new Button("Toggle"),incPriority=
newButton("up"),decPriority=
newButton("down");

    private TextField
t= newTextField(10),
pr= newTextField(3);
// Display

                                                                   
// priority

    private
int
count= 0;

    private
boolean
runFlag=
true;

 

    public Ticker2(Container
c) {

        b.addActionListener(new ToggleL());

        incPriority.addActionListener(new UpL());

        decPriority.addActionListener(new DownL());

        Panel p =
new Panel();

        p.add(t);

        p.add(pr);

        p.add(b);

        p.add(incPriority);

        p.add(decPriority);

        c.add(p);

    }

    class ToggleL
implements ActionListener {

        public
void
actionPerformed(ActionEvent
e) {

            runFlag = !runFlag;

        }

    }

    class UpL
implementsActionListener {

        public
void
actionPerformed(ActionEvent
e) {

            int
newPriority = getPriority() + 1;

            if (newPriority > Thread.MAX_PRIORITY)

                newPriority = Thread.MAX_PRIORITY;

            setPriority(newPriority);

        }

    }

    class DownL
implements ActionListener {

        public
void
actionPerformed(ActionEvent
e) {

            int
newPriority = getPriority() - 1;

            if (newPriority < Thread.MIN_PRIORITY)

                newPriority = Thread.MIN_PRIORITY;

            setPriority(newPriority);

        }

    }

    public
void
run() {

        while (true) {

            if (runFlag) {

                t.setText(Integer.toString(count++));

                pr.setText(Integer.toString(getPriority()));

            }

            yield();

        }

    }

}

public
class
Counter5 extends Applet {

    private Button
start= newButton("Start"),upMax=
newButton("Inc Max Priority"),

            downMax =
new Button("Dec Max Priority");

    private
boolean
started=
false;

    private
staticfinalintSIZE
= 10;

    private Ticker2[]
s= newTicker2[SIZE];

    private TextField
mp= newTextField(3);

 

    public
void
init() {

        for (int
i = 0; i <
s.length; i++)

            s[i] =
newTicker2(this);

        add(new Label("MAX_PRIORITY = " + Thread.MAX_PRIORITY));

        add(new Label("MIN_PRIORITY = " + Thread.MIN_PRIORITY));

        add(new Label("Group Max Priority = "));

        add(mp);

        add(start);

        add(upMax);

        add(downMax);

        start.addActionListener(new StartL());

        upMax.addActionListener(new UpMaxL());

        downMax.addActionListener(new DownMaxL());

        showMaxPriority();

        // Recursively display parent thread groups:

        ThreadGroup parent =
s[0].getThreadGroup().getParent();

        while (parent !=
null) {

            add(new Label("Parent threadgroup max priority = "+
parent.getMaxPriority()));

            parent =
parent.getParent();

        }

    }

    public
void
showMaxPriority() {

        mp.setText(Integer.toString(s[0].getThreadGroup().getMaxPriority()));

    }

    class StartL
implements ActionListener {

        public
void
actionPerformed(ActionEvent
e) {

            if (!started) {

                started =
true;

                for (int
i = 0; i <
s.length; i++)

                    s[i].start();

            }

        }

    }

    class UpMaxL
implements ActionListener {

        public
void
actionPerformed(ActionEvent
e) {

            int
maxp = s[0].getThreadGroup().getMaxPriority();

            if (++maxp > Thread.MAX_PRIORITY)

                maxp = Thread.MAX_PRIORITY;

            s[0].getThreadGroup().setMaxPriority(maxp);

            showMaxPriority();

        }

    }

    class DownMaxL
implements ActionListener {

        public
void
actionPerformed(ActionEvent
e) {

            int
maxp = s[0].getThreadGroup().getMaxPriority();

            if (--maxp < Thread.MIN_PRIORITY)

                maxp = Thread.MIN_PRIORITY;

            s[0].getThreadGroup().setMaxPriority(maxp);

            showMaxPriority();

        }

    }

    public
staticvoid
main(String[]
args){

        Counter5 applet =
new Counter5();

        Frame aFrame =
new Frame("Counter5");

        aFrame.addWindowListener(new WindowAdapter() {

            public
void
windowClosing(WindowEvent
e) {

                System.exit(0);

            }

        });

        aFrame.add(applet, BorderLayout.CENTER);

        aFrame.setSize(300, 600);

        applet.init();

        applet.start();

        aFrame.setVisible(true);

    }

} /// :~

Ticker 采用前面构造好的形式,但有一个额外的TextField(文本字段),用于显示线程的优先级;以及两个额外的按钮,用于人为提高及降低优先级。

也要注意yield()的用法,它将控制权自动返回给调试程序(机制)。若不进行这样的处理,多线程机制仍会工作,但我们会发现它的运行速度慢了下来(试试删去对yield()的调用)。亦可调用sleep(),但假若那样做,计数频率就会改由sleep()的持续时间控制,而不是优先级。

Counter5 中的init()创建了由10 个Ticker2构成的一个数组;它们的按钮以及输入字段(文本字段)由Ticker2 构建器置入窗体。Counter5 增加了新的按钮,用于启动一切,以及用于提高和降低线程组的最大优先级。除此以外,还有一些标签用于显示一个线程可以采用的最大及最小优先级;以及一个特殊的文本字段,用于显示线程组的最大优先级。最后,父线程组的优先级也作为标签显示出来。

按下“up”(上)或“down”(下)按钮的时候,会先取得Ticker2 当前的优先级,然后相应地提高或者降低。

运行该程序时,我们可注意到几件事情。首先,线程组的默认优先级是5。即使在启动线程之前(或者在创建线程之前,这要求对代码进行适当的修改)将最大优先级降到5 以下,每个线程都会有一个5 的默认优先级。最简单的测试是获取一个计数器,将它的优先级降低至1,此时应观察到它的计数频率显著放慢。现在试着再次提高优先级,可以升高回线程组的优先级,但不能再高了。现在将线程组的优先级降低两次。线程的优先级不会改变,但假若试图提高或者降低它,就会发现这个优先级自动变成线程组的优先级。此外,新线程仍然具有一个默认优先级,即使它比组的优先级还要高(换句话说,不要指望利用组优先级来防止新线程拥有比现有的更高的优先级)。

最后,试着提高组的最大优先级。可以发现,这样做是没有效果的。我们只能减少线程组的最大优先级,而不能增大它。

2     线程组

所有线程都隶属于一个线程组。那可以是一个默认线程组,亦可是一个创建线程时明确指定的组。在创建之初,线程被限制到一个组里,而且不能改变到一个不同的组。每个应用都至少有一个线程从属于系统线程组。若创建多个线程而不指定一个组,它们就会自动归属于系统线程组。

线程组也必须从属于其他线程组。必须在构建器里指定新线程组从属于哪个线程组。若在创建一个线程组的时候没有指定它的归属,则同样会自动成为系统线程组的一名属下。因此,一个应用程序中的所有线程组最终都会将系统线程组作为自己的“父”。

之所以要提出“线程组”的概念,很难从字面上找到原因。这多少为我们讨论的主题带来了一些混乱。一般地说,我们认为是由于“安全”或者“保密”方面的理由才使用线程组的。根据Arnold 和Gosling 的说法:

“线程组中的线程可以修改组内的其他线程,包括那些位于分层结构最深处的。一个线程不能修改位于自己所在组或者下属组之外的任何线程”。然而,我们很难判断“修改”在这儿的具体含义是什么。

下面例子展示了位于一个“叶子组”内的线程能修改它所在线程组树的所有线程的优先级,同时还能为这个“树”内的所有线程都调用一个方法。

2.1     代码

public
class
TestAccess {

    public
staticvoid
main(String[]
args){

        ThreadGroup x =
new ThreadGroup("x"),
y = new ThreadGroup(x,
"y"), z =
new
ThreadGroup(y,
"z");

        Thread one =
new TestThread1(x,
"one"), two=
newTestThread2(z,
"two");

    }

}

class TestThread1
extends Thread {

    private
int
i;

    TestThread1(ThreadGroup
g, String name) {

        super(g,
name);

    }

    void f() {

        i++;
// modify this thread

        System.out.println(getName() +
" f()");

    }

}

class TestThread2
extends TestThread1 {

    TestThread2(ThreadGroup
g, String name) {

        super(g,
name);

        start();

    }

    public
void
run() {

        ThreadGroup g = getThreadGroup().getParent().getParent();

        g.list();

        Thread[] gAll =
new Thread[g.activeCount()];

        g.enumerate(gAll);

        for (int
i = 0; i <
gAll.length;
i++) {

            gAll[i].setPriority(Thread.MIN_PRIORITY);

            ((TestThread1)
gAll[i]).f();

        }

        g.list();

    }

} /// :~

在main()中,创建了几个ThreadGroup(线程组),每个都位于不同的“叶”上:x 没有参数,只有它的名字(一个String),所以会自动进入“system”(系统)线程组;y 位于x下方,而z 位于y 下方。注意初始化是按照文字顺序进行的,所以代码合法。

有两个线程创建之后进入了不同的线程组。其中,TestThread1 没有一个run()方法,但有一个f(),用于通知线程以及打印出一些东西,以便我们知道它已被调用。而TestThread2 属于TestThread1 的一个子类,它的run()非常详尽,要做许多事情。首先,它获得当前线程所在的线程组,然后利用getParent()在继承树中向上移动两级(这样做是有道理的,因为我想把TestThread2 在分级结构中向下移动两级)。随后,我们调用方法activeCount(),查询这个线程组以及所有子线程组内有多少个线程,从而创建由指向Thread
的句柄构成的一个数组。enumerate()方法将指向所有这些线程的句柄置入数组gAll 里。然后在整个数组里遍历,为每个线程都调用f()方法,同时修改优先级。这样一来,位于一个“叶子”线程组里的线程就修改了位于父线程组的线程。

调试方法list()打印出与一个线程组有关的所有信息,把它们作为标准输出。在我们对线程组的行为进行调查的时候,这样做是相当有好处的。

list()不仅打印出ThreadGroup 或者Thread 的类名,也打印出了线程组的名字以及它的最高优先级。对于线程,则打印出它们的名字,并接上线程优先级以及所属的线程组。注意list()会对线程和线程组进行缩排处理,指出它们是未缩排的线程组的“子”。

大家可看到f()是由TestThread2 的run()方法调用的,所以很明显,组内的所有线程都是相当脆弱的。然而,我们只能访问那些从自己的system 线程组树分支出来的线程,而且或许这就是所谓“安全”的意思。我们不能访问其他任何人的系统线程树。

2.2     线程组的控制

抛开安全问题不谈,线程组最有用的一个地方就是控制:只需用单个命令即可完成对整个线程组的操作。下面这个例子演示了这一点,并对线程组内优先级的限制进行了说明。括号内的注释数字便于大家比较输出结果:

2.2.1       代码

public
class
ThreadGroup1 {

    public
staticvoid
main(String[]
args){

        // Get the system thread & print its Info:

        ThreadGroup sys = Thread.currentThread().getThreadGroup();

        sys.list();
// (1)

        // Reduce the system thread group priority:

        sys.setMaxPriority(Thread.MAX_PRIORITY- 1);

        // Increase the main thread priority:

        Thread curr = Thread.currentThread();

        curr.setPriority(curr.getPriority() + 1);

        sys.list();
// (2)

        // Attempt to set a new group to the max:

        ThreadGroup g1 =
new ThreadGroup("g1");

        g1.setMaxPriority(Thread.MAX_PRIORITY);

        // Attempt to set a new thread to the max:

        Threadt =
new Thread(g1,
"A");

        t.setPriority(Thread.MAX_PRIORITY);

        g1.list();
// (3)

        // Reduce g1's max priority, then attempt

        // to increase it:

        g1.setMaxPriority(Thread.MAX_PRIORITY- 2);

        g1.setMaxPriority(Thread.MAX_PRIORITY);

        g1.list();
// (4)

        // Attempt to set a new thread to the max:

        t =
new
Thread(g1,
"B");

        t.setPriority(Thread.MAX_PRIORITY);

        g1.list();
// (5)

        // Lower the max priority below the default

        // thread priority:

        g1.setMaxPriority(Thread.MIN_PRIORITY+ 2);

        // Look at a new thread's priority before

        // and after changing it:

        t =
new
Thread(g1,
"C");

        g1.list();
// (6)

        t.setPriority(t.getPriority() - 1);

        g1.list();
// (7)

        // Make g2 a child
Threadgroup of g1 and

        // try to increase its priority:

        ThreadGroup
g2= newThreadGroup(g1,"g2");

       
g2.list();// (8)

       
g2.setMaxPriority(Thread.MAX_PRIORITY);

       
g2.list();// (9)

        // Add a bunch of new threads to g2:

        for (int
i = 0; i < 5;
i++)

            new Thread(g2,Integer.toString(i));

        // Show information about all
threadgroups

        // and threads:

        sys.list();
// (10)

        System.out.println("Starting all threads:");

        Thread[] all =
new Thread[sys.activeCount()];

        sys.enumerate(all);

        for (int
i = 0; i <
all.length;
i++)

            if (!all[i].isAlive())

                all[i].start();

        // Suspends & Stops all threads in

        // this group and its subgroups:

        System.out.println("All threads started");

        sys.suspend();
// Deprecated in Java 1.2

        // Never gets here...

        System.out.println("All threads suspended");

        sys.stop();
// Deprecated in Java 1.2

        System.out.println("All threads stopped");

    }

} /// :~

下面的输出结果已进行了适当的编辑,以便用一页能够装下(java.lang.已被删去),而且添加了适当的数字,与前面程序列表中括号里的数字对应:

java.lang.ThreadGroup[name=main,maxpri=10]

    Thread[main,5,main]

java.lang.ThreadGroup[name=main,maxpri=9]

    Thread[main,6,main]

java.lang.ThreadGroup[name=g1,maxpri=9]

java.lang.ThreadGroup[name=g1,maxpri=9]

java.lang.ThreadGroup[name=g1,maxpri=9]

java.lang.ThreadGroup[name=g1,maxpri=3]

java.lang.ThreadGroup[name=g1,maxpri=3]

java.lang.ThreadGroup[name=g2,maxpri=3]

java.lang.ThreadGroup[name=g2,maxpri=3]

java.lang.ThreadGroup[name=main,maxpri=9]

    Thread[main,6,main]

    java.lang.ThreadGroup[name=g1,maxpri=3]

        java.lang.ThreadGroup[name=g2,maxpri=3]

Startingall threads:

Allthreads started

所有程序都至少有一个线程在运行,而且main()采取的第一项行动便是调用Thread 的一个static(静态)方法,名为currentThread()。从这个线程开始,线程组将被创建,而且会为结果调用list()。

由线程组的规则所限,一个子组的最大优先级在任何时候都只能低于或等于它的父组的最大优先级。

本程序的最后一个部分演示了用于整组线程的方法。程序首先遍历整个线程树,并启动每一个尚未启动的线程。例如,system 组随后会被挂起(暂停),最后被中止(尽管用suspend()和stop()对整个线程组进行操作看起来似乎很有趣,但应注意这些方法在Java 1.2 里都是被“反对”的)。但在挂起system 组的同时,也挂起了main 线程,而且整个程序都会关闭。所以永远不会达到让线程中止的那一步。实际上,假如真的中止了main 线程,它会“掷”出一个ThreadDeath
违例,所以我们通常不这样做。由于ThreadGroup 是从Object 继承的,其中包含了wait()方法,所以也能调用wait(秒数×1000),令程序暂停运行任意秒数的时间。当然,事前必须在一个同步块里取得对象锁。

ThreadGroup 类也提供了suspend()和resume()方法,所以能中止和启动整个线程组和它的所有线程,也能中止和启动它的子组,所有这些只需一个命令即可(再次提醒,suspend()和resume()都是Java1.2 所“反对”的)。

从表面看,线程组似乎有些让人摸不着头脑,但请注意我们很少需要直接使用它们。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: