Java多线程实例学习

1. Java多线程的就绪、运行和死亡状态

就绪状态转换为运行状态:当此线程得到处理器资源;

运行状态转换为就绪状态:当此线程主动调用yield()方法或在运行过程中失去处理器资源。

运行状态转换为死亡状态:当此线程线程执行体执行完毕或发生了异常。

此处需要特别注意的是:当调用线程的yield()方法时,线程从运行状态转换为就绪状态,但接下来CPU调度就绪状态中的哪个线程具有一定的随机性,因此,可能会出现A线程调用了

yield()方法后,接下来CPU仍然调度了A线程的情况。

由于实际的业务需要,常常会遇到需要在特定时机终止某一线程的运行,使其进入到死亡状态。目前最通用的做法是设置一boolean型的变量,当条件满足时,使线程执行体快速执行完毕。如:

1. 代码

package test;

public class TestStopThread {

    public static void main(String[] args) {
        MyThread myThread = new MyThread("MyThread");
        Thread thread = new Thread(myThread);
        for(int i=0; i<100; i++){
            System.out.println(Thread.currentThread().getName() + "-->" + i);
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(i == 10){
                thread.start();
            }
            if(i == 30){
                myThread.stopThread();
            }
        }
    }

}

class MyThread implements Runnable{

    boolean stop = false;

    private int i = 50;

    private String threadName;

    public MyThread(String threadName){
        this.threadName = threadName;
    }

    @Override
    public void run() {
        while (!stop && i>0){
            System.out.println(threadName + "-->" + i--);
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void stopThread(){
        stop = true;
    }
}

2. 运行结果:

main-->0
main-->1
main-->2
main-->3
main-->4
main-->5
main-->6
main-->7
main-->8
main-->9
main-->10
main-->11
MyThread-->50
MyThread-->49
main-->12
MyThread-->48
main-->13
MyThread-->47
main-->14
main-->15
MyThread-->46
main-->16
MyThread-->45
MyThread-->44
main-->17
MyThread-->43
main-->18
MyThread-->42
main-->19
MyThread-->41
main-->20
main-->21
MyThread-->40
main-->22
MyThread-->39
MyThread-->38
main-->23
main-->24
MyThread-->37
MyThread-->36
main-->25
main-->26
MyThread-->35
main-->27
MyThread-->34
main-->28
MyThread-->33
MyThread-->32
main-->29
main-->30
MyThread-->31
MyThread-->30
main-->31
main-->32
main-->33
main-->34
main-->35
...

2. Java多线程的阻塞状态与线程控制

1. join()

让一个线程等待另一个线程完成才继续执行。如A线程线程执行体中调用B线程的join()方法,则A线程被阻塞,知道B线程执行完为止,A才能得以继续执行。

(1)代码实例:

package test;

public class TestJoin {

    public static void main(String[] args) {
        MyJoin myJoin = new MyJoin();
        Thread thread = new Thread(myJoin, "MyJoin");
        for(int i=0; i<30; i++){
            System.out.println(Thread.currentThread().getName() + "-->" + i);
            if(i == 10){
                thread.start();
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            if(i == 20){
                try {
                    thread.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

class MyJoin implements Runnable{

    @Override
    public void run() {
        for(int i=0; i<20; i++){
            System.out.println(Thread.currentThread().getName() + "-->" + i);
        }
    }
}

(2)运行结果:

main-->0
main-->1
main-->2
main-->3
main-->4
main-->5
main-->6
main-->7
main-->8
main-->9
main-->10
MyJoin-->0
MyJoin-->1
MyJoin-->2
MyJoin-->3
MyJoin-->4
MyJoin-->5
MyJoin-->6
MyJoin-->7
MyJoin-->8
MyJoin-->9
MyJoin-->10
MyJoin-->11
MyJoin-->12
MyJoin-->13
MyJoin-->14
MyJoin-->15
MyJoin-->16
MyJoin-->17
MyJoin-->18
MyJoin-->19
main-->11
main-->12
main-->13
main-->14
main-->15
main-->16
main-->17
main-->18
main-->19
main-->20
main-->21
main-->22
main-->23
main-->24
main-->25
main-->26
main-->27
main-->28
main-->29

可以看到,当main线程中join 了MyJoin线程后,会暂停main线程,直到MyJoin线程执行完毕再执行main,这就是join方法,说简单点,就是A线程中调用B线程join方法,此时,A线程暂停执行(阻塞),而B线程执行,直到B线程执行完毕后A线程继续执行

2. sleep()

sleep —— 让当前的正在执行的线程暂停指定的时间,并进入阻塞状态。在其睡眠的时间段内,该线程由于不是处于就绪状态,因此不会得到执行的机会。即使此时系统中没有任何其他可执行的线程,出于sleep()中的线程也不会执行。因此sleep()方法常用来暂停线程执行。

前面有讲到,当调用了新建的线程的start()方法后,线程进入到就绪状态,可能会在接下来的某个时间获取CPU时间片得以执行,如果希望这个新线程尽快执行,直接调用原来线程的sleep(1)即可。

(1)代码实例:

package test;

public class TestSleep {

    public static void main(String[] args) {
        for(int i=0; i<20; i++){
            System.out.println(Thread.currentThread().getName() + "-->" + i);
            if(i == 10){
                new Thread(new MySleep(), "MySleep").start();
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

class MySleep implements Runnable{

    @Override
    public void run() {
        for(int i=0; i<20; i++){
            System.out.println(Thread.currentThread().getName() + "-->" + i);
        }
    }
}

(2)运行结果:

main-->0
main-->1
main-->2
main-->3
main-->4
main-->5
main-->6
main-->7
main-->8
main-->9
main-->10
MySleep-->0
MySleep-->1
MySleep-->2
MySleep-->3
MySleep-->4
MySleep-->5
MySleep-->6
MySleep-->7
MySleep-->8
MySleep-->9
MySleep-->10
MySleep-->11
MySleep-->12
MySleep-->13
MySleep-->14
MySleep-->15
MySleep-->16
MySleep-->17
MySleep-->18
MySleep-->19
main-->11
main-->12
main-->13
main-->14
main-->15
main-->16
main-->17
main-->18
main-->19

3. 后台线程(Daemon Thread)

后台线程主要是为其他线程(相对可以称之为前台线程)提供服务,或“守护线程”。如JVM中的垃圾回收线程。

生命周期:后台线程的生命周期与前台线程生命周期有一定关联。主要体现在:当所有的前台线程都进入死亡状态时,后台线程会自动死亡(其实这个也很好理解,因为后台线程存在的目的在于为前台线程服务的,既然所有的前台线程都死亡了,那它自己还留着有什么用)。

设置后台线程:调用Thread对象的setDaemon(true)方法可以将指定的线程设置为后台线程。

(1)代码实例:

package test;

public class TestDeamon {

    public static void main(String[] args) {

        MyDeamon myDeamon = new MyDeamon();
        Thread thread = new Thread(myDeamon, "MyDeamon");

        for(int i=0; i<20; i++){
            System.out.println(Thread.currentThread().getName() + "-->" + i);
            if(i == 5){
                thread.setDaemon(true);
                thread.start();
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            if(i >= 19){
                System.out.println("main线程执行完毕");
            }
        }
    }

}

class MyDeamon implements Runnable{

    @Override
    public void run() {
        System.out.println("MyDeamon线程开始执行");
        for(int i=0; i<100; i++){
            System.out.println(Thread.currentThread().getName() + "-->" + i);
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("MyDeamon线程执行完毕");
    }
}

(2)运行结果:

main-->0
main-->1
main-->2
main-->3
main-->4
main-->5
main-->6
MyDeamon线程开始执行
main-->7
main-->8
main-->9
main-->10
main-->11
main-->12
main-->13
main-->14
main-->15
main-->16
main-->17
main-->18
main-->19
main线程执行完毕
MyDeamon-->0

判断线程是否是后台线程:调用thread对象的isDeamon()方法。

注:main线程默认是前台线程,前台线程创建中创建的子线程默认是前台线程,后台线程中创建的线程默认是后台线程。调用setDeamon(true)方法将前台线程设置为后台线程时,需要在start()方法调用之前。前天线程都死亡后,JVM通知后台线程死亡,但从接收指令到作出响应,需要一定的时间,前台线程main执行完毕后,后台线程收到指令结束线程,由于有一定的响应时间,所以执行了一段代码MyDeamon-->0

4. 改变线程的优先级/setPriority()

每个线程在执行时都具有一定的优先级,优先级高的线程具有较多的执行机会。每个线程默认的优先级都与创建它的线程的优先级相同。main线程默认具有普通优先级。
设置线程优先级:setPriority(int priorityLevel)。参数priorityLevel范围在1-10之间,常用的有如下三个静态常量值:

  • MAX_PRIORITY:10

  • MIN_PRIORITY:1

  • NORM_PRIORITY:5

注:具有较高线程优先级的线程对象仅表示此线程具有较多的执行机会,而非优先执行。

(1)代码实例:

package test;

public class TestPriority {

    public static void main(String[] args) {
        MyPriority myPriority = new MyPriority();
        Thread thread = new Thread(myPriority, "MyPriority");
        thread.setPriority(Thread.MAX_PRIORITY);
        thread.start();
        for(int i=0; i<20; i++){
            System.out.println(Thread.currentThread().getName() + "-->" + i);
        }
    }

}

class MyPriority implements Runnable{

    @Override
    public void run() {
        for(int i=0; i<20; i++){
            System.out.println(Thread.currentThread().getName() + "-->" + i);
        }
    }
}

(2)运行结果1:

MyPriority-->0
MyPriority-->1
MyPriority-->2
MyPriority-->3
MyPriority-->4
MyPriority-->5
MyPriority-->6
MyPriority-->7
MyPriority-->8
MyPriority-->9
MyPriority-->10
MyPriority-->11
MyPriority-->12
MyPriority-->13
MyPriority-->14
MyPriority-->15
MyPriority-->16
MyPriority-->17
MyPriority-->18
MyPriority-->19
main-->0
main-->1
main-->2
main-->3
main-->4
main-->5
main-->6
main-->7
main-->8
main-->9
main-->10
main-->11
main-->12
main-->13
main-->14
main-->15
main-->16
main-->17
main-->18
main-->19

运行结果2:

main-->0
MyPriority-->0
MyPriority-->1
MyPriority-->2
MyPriority-->3
MyPriority-->4
MyPriority-->5
MyPriority-->6
MyPriority-->7
MyPriority-->8
MyPriority-->9
MyPriority-->10
MyPriority-->11
MyPriority-->12
MyPriority-->13
MyPriority-->14
MyPriority-->15
MyPriority-->16
MyPriority-->17
MyPriority-->18
MyPriority-->19
main-->1
main-->2
main-->3
main-->4
main-->5
main-->6
main-->7
main-->8
main-->9
main-->10
main-->11
main-->12
main-->13
main-->14
main-->15
main-->16
main-->17
main-->18
main-->19

可以看到,当设置了线程优先级后,优先级高的具有较多的执行机会,但是并不一定优先执行,运行结果1和2有些许不同,1中MyPriority线程先执行,2中main线程先执行了一段时间,后让给优先级高的MyPriority线程,可见,证实了"具有较高线程优先级的线程对象仅表示此线程具有较多的执行机会,而非优先执行。"

5. 线程让步:yield()

使当前线程从执行状态(运行状态)变为可执行态(就绪状态)。cpu会从众多的可执行态里选择,也就是说,当前也就是刚刚的那个线程还是有可能会被再次执行到的,并不是说一定会执行其他线程而该线程在下一次中不会执行到了。调用yield方法后,该线程就会把CPU时间让掉,让其他或者自己的线程执行(也就是谁先抢到谁执行)

同时,yield()方法还与线程优先级有关,当某个线程调用yiled()方法从运行状态转换到就绪状态后,CPU从就绪状态线程队列中只会选择与该线程优先级相同或优先级更高的线程去执行。

(1)代码实例:

package test;

public class TestYield {

    public static void main(String[] args) {

        MyYield1 myYield1 = new MyYield1();
        MyYield2 myYield2 = new MyYield2();
        Thread thread1 = new Thread(myYield1, "MyYield1");
        Thread thread2 = new Thread(myYield2, "MyYield2");
        thread1.setPriority(Thread.MAX_PRIORITY);
        thread2.setPriority(Thread.MIN_PRIORITY);
        thread1.start();
        thread2.start();

        for(int i=0; i<50; i++){
            System.out.println(Thread.currentThread().getName() + "-->" + i);
            if(i == 10){
                Thread.yield();
            }
        }

    }

}

class MyYield1 implements Runnable{

    @Override
    public void run() {
        for(int i=0; i<30; i++){
            System.out.println(Thread.currentThread().getName() + "-->" + i);
        }
    }
}

class MyYield2 implements Runnable{

    @Override
    public void run() {
        for(int i=0; i<30; i++){
            System.out.println(Thread.currentThread().getName() + "-->" + i);
        }
    }
}

(2)运行结果:

main-->0
main-->1
main-->2
MyYield1-->0
MyYield1-->1
MyYield1-->2
MyYield1-->3
MyYield1-->4
MyYield1-->5
MyYield1-->6
MyYield1-->7
MyYield1-->8
MyYield1-->9
MyYield1-->10
MyYield1-->11
MyYield1-->12
MyYield1-->13
MyYield1-->14
MyYield1-->15
MyYield1-->16
MyYield1-->17
MyYield1-->18
MyYield1-->19
MyYield1-->20
MyYield1-->21
MyYield1-->22
MyYield1-->23
MyYield1-->24
MyYield1-->25
MyYield1-->26
MyYield1-->27
MyYield1-->28
MyYield1-->29
main-->3
main-->4
main-->5
main-->6
main-->7
main-->8
main-->9
main-->10
main-->11
main-->12
main-->13
main-->14
main-->15
main-->16
main-->17
main-->18
main-->19
main-->20
main-->21
main-->22
main-->23
main-->24
main-->25
main-->26
main-->27
main-->28
main-->29
main-->30
main-->31
main-->32
main-->33
main-->34
main-->35
main-->36
main-->37
main-->38
main-->39
main-->40
main-->41
main-->42
main-->43
main-->44
main-->45
main-->46
main-->47
main-->48
main-->49
MyYield2-->0
MyYield2-->1
MyYield2-->2
MyYield2-->3
MyYield2-->4
MyYield2-->5
MyYield2-->6
MyYield2-->7
MyYield2-->8
MyYield2-->9
MyYield2-->10
MyYield2-->11
MyYield2-->12
MyYield2-->13
MyYield2-->14
MyYield2-->15
MyYield2-->16
MyYield2-->17
MyYield2-->18
MyYield2-->19
MyYield2-->20
MyYield2-->21
MyYield2-->22
MyYield2-->23
MyYield2-->24
MyYield2-->25
MyYield2-->26
MyYield2-->27
MyYield2-->28
MyYield2-->29
posted @ 2017-11-10 22:08  A旺仔A  阅读(490)  评论(0编辑  收藏  举报