Thread线程

龟兔赛跑:

package thread;

public class Tortoise implements Runnable{
    private int totalStep;
    private int step;
    public Tortoise(int totalStep) {
        this.totalStep=totalStep;
    }
    @Override
    public void run() {
        while(step<totalStep) {
            step++;
            System.out.printf("乌龟走了%d步。%n", step);
        }
    }

}
package thread;

public class Hara implements Runnable {
    private boolean[] flags = { true, false };
    private int totalStep;
    private int step;

    public Hara(int totalStep) {
        this.totalStep = totalStep;
    }

    @Override
    public void run() {
        while (step<totalStep) {
            boolean isHareSleep=flags[((int)(Math.random()*10))%2];//随机

            if(isHareSleep) {
                System.out.println("兔子睡着了");
            }else {
                step+=2;
                System.out.printf("兔子走了%d步。%n",step);
            }

        }

    }

}
package thread;

public class ThreadDemo {
    public static void main(String[] args) {
        new Thread(new Tortoise(10)).start();
        new Thread(new Hara(10)).start();
    }
}

从抽象观点与开发者的角度来看,JVM是台虚拟计算机,只安装一颗称为主线程的CPU,可执行main()定义的执行流程。如果想要为JVM加装CPU,就是创建 Thread实例,要启动额外CPU就是调用 Thread实例的 start()方法。额外CPU执行流程的进入点,可以定义在 Runnable接口的run()方法中。实际上JVM启动后,不只有一个主线程,还会有垃圾收集、内存管理等线程,不过这是底层机制,就撰写程序的角度来看JVM,确实只有一个主线程。
除了将流程定义在 Runnable的run()方法中之外,另一个撰写多线程程序的方式,就是继承Thead类,重新定义run()方法。单就撰写程序的角度来看,前面的龟兔赛跑也可以改写为以下:

public class Tortoise extends Thread{
    ....
    @Override
    public void run(){
    ....
    }
}

public class Hare extends Thread{
    ....
    @Override
    public void run(){
    ....
    }
}

调用:

new Tortoise(10).start();
new Hare(10).start();

Thread本身也操作了Runnable接口:

@Override
public void run(){
    if(target!=null){
    target.run();
    }
}

调用 Thread实例的 start()方法后,会执行以上定义的run()方法,如果创建 Thread时指定 Runnable实例,就会由 target参考。所以,如果直接继承 Thread并重新定义run()方法,当然也可以执行其中流程。操作 Runnable接口的好处就是较有弹性,你的类还有机会继承其他类。若继承了 Thread,,那该类就是一种 Thread,通常是为了直接利用 Thread中定义的一些方法,才会继承 Thread来操作。
在JDK8中,由于可以使用 Lambda表达,而建构 Thread时可以接受 Runnable实例,在某些必须以匿名类语法建构 Thread的场合,可以考虑用 Lambda表达式来操作 Runnable,然后再用以建立 Thread:

new Thread(()->{//方法操作内容}).start();
  1. Daemon线程
    主线程会从main()方法开始执行,直到main()方法结束后停止JVM。如果主线程中启动了额外线程,默认会等待被启动的所有线程都执行完run()方法才中止JVM。如果一个 Thread被标示为 Daemon线程,在所有的非 Daemon线程都结束时,JVM自动就会终止 setDaemon ()方法来设定一个从main()方法开始的就是一个非 Daemon线程,可以使用
    线程是否为 Daemon线程:
package thread;

public class DaemonDemo {
    public static void main(String[] args) {
        Thread th = new Thread(() -> {
            while (true) {
                System.out.println("ggg...");
            }
        });
        th.setDaemon(true);
        th.start();
    }
}

如果没有使用setDaemon()设定为true,则程序会不断地输出而不终止,非Deamon线程main()结束后Deamon线程结束;使用 isDaemon()方法可以判断线程是否为 Daemon线程,默认所有Daemon线程产生的线程也是 Daemon线程,因为基本上由一个背景服务线程衍生出来的线程,也应该是为了在背景服务而产生的,所以在产生它的线程停止时也应该一并跟着停止。
2. Thread基本状态图
在调用 Thread实例 start()方法后,基本状态为执行( Runnable)、被阻断( Blocked))执行中( unning)。实例化 Thread并执行 start()之后,线程进入 Runnable状态,此时线程尚未真正开始执行run()方法,必须等待排班器( Scheduler)入CPU执行,线程才会执行run()方法,进入 Running状态。线程看起来像是同时执行,但事实上同一时间点上,一个CPU还是只能执行一个线程,只是CPU会不断切换线程,且换动作很快,所以看来像是同时执行线程有其优先权,可使用 Thread的 setpriority()方法设定优先权,可设定值为 1(Thread. MIN_PRIORITY)到10( Thread. MAX_PRIORITY),默认是5( Thread. NORM_PRIORLT),1到10外设定值抛错:IllegalArgumentException。数字越大优先权越高。
有几种状况会让线程进入 Blocked状态,例如前面调用 Thread. sleep()方法,就会让线程进入 Blocked(其他还有进入 synchronized前竞争对象锁定的阻断、调用wait()的阻断等);等待输入输出完成也会进入 Blocked,等待用户输入时就是实际的例子。运用多线程,当某线程进入 Blocked时,让另一线程排入CPU执行(成为 Running状态),避免CPU空闲下来,经常是改进效能的方式之一。
例如以下这个程序可以指定网址下载网页,来看看不使用线程时花费的时间:

package thread;

import java.io.*;
import java.net.URL;

public class Download {
    public static void main(String[] args) throws Exception {
        URL[] urls = { new URL("https://blog.csdn.net/zkd758/article/details/80210270"),
                new URL("https://blog.csdn.net/zkd758/article/details/80143295"),
                new URL("https://blog.csdn.net/zkd758/article/details/80151669"),
                new URL("https://blog.csdn.net/zkd758/article/details/80037368"),
                new URL("https://blog.csdn.net/zkd758/article/details/80143295"),
                new URL("https://blog.csdn.net/zkd758/article/details/80152033") };
        String[] filenames = { "one.html", "two.html", "three.html", "four.html", "five.html", "six.html", };

        for (int i = 0; i <= urls.length; i++) {
            dump(urls[i].openStream(), new FileOutputStream(filenames[i]));
        }
    }

    public static void dump(InputStream src, OutputStream dest) throws IOException {
        try (InputStream input = new BufferedInputStream(src); OutputStream output = new BufferedOutputStream(dest)) {// 自动关闭
            byte[] data = new byte[1024];
            int length;
            while ((length = input.read(data)) != -1) {
                output.write(data, 0, length);
            }
        }
    }
}

多线程:

package thread;

import java.io.*;
import java.net.URL;

public class Download {
    public static void main(String[] args) throws Exception {
        URL[] urls = { new URL("https://blog.csdn.net/zkd758/article/details/80210270"),
                new URL("https://blog.csdn.net/zkd758/article/details/80143295"),
                new URL("https://blog.csdn.net/zkd758/article/details/80151669"),
                new URL("https://blog.csdn.net/zkd758/article/details/80037368"),
                new URL("https://blog.csdn.net/zkd758/article/details/80143295"),
                new URL("https://blog.csdn.net/zkd758/article/details/80152033") };
        String[] filenames = { "one.html", "two.html", "three.html", "four.html", "five.html", "six.html", };

        for (int i = 0; i <= urls.length; i++) {
            int index = i;
            new Thread(() -> {
                try {
                    dump(urls[index].openStream(), new FileOutputStream(filenames[index]));
                } catch (IOException e) {
                    throw new RuntimeException();
                }
            }).start();
        }

    }

    public static void dump(InputStream src, OutputStream dest) throws IOException {
        try (InputStream input = new BufferedInputStream(src); OutputStream output = new BufferedOutputStream(dest)) {// 自动关闭
            byte[] data = new byte[1024];
            int length;
            while ((length = input.read(data)) != -1) {
                output.write(data, 0, length);
            }
        }
    }
}
new FileOutputStream(filenames[index],ture)//ture后写入文件从文档原有内容后开始写入

这次的范例在for循环时,会建立新的 Thread并启动,以进行网页下载。这个花费的时间明显会少很多。
线程因输入/输出进入 Blocked状态,在完成输入输出后,会回到 Runnable状态,等待排班器排入执行( Running状态)。一个进入 Blocked状态的线程,可以由另一个线程调用该线程的 interrupt()方法,让离开 Blocked状态。
举个例子来说,使用 Thread. sleep()会让线程进入 Blocked状态,若此时有其他线程调用该线程的 interrupt()方法,会抛出
InterruptedException异常对象,这可以让线程“醒过来”:

package thread;

public class ThreadDemo {
    public static void main(String[] args) {
        Thread th=new Thread(){
            @Override
            public void run(){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    System.out.println("唤醒成功");
                }
            }
        };
        th.start();
        th.interrupt();
    }
}

join()安插线程:当线程使用join()加入另一线程后,该线程必须等加入的线程执行完毕然后继续:

package thread;

import static java.lang.System.out;

public class JoinDemo {
    public static void main(String[] args) throws InterruptedException {
        out.println("Main线程开始...");
        Thread th = new Thread(() -> {
            out.println("th线程开始...");
            for (int i = 0; i < 2; i++) {
                out.println("th线程执行...");
            }
            out.println("th将结束...");
        });
        th.start();
        //th.join();
        out.println("main将结束...");
    }
}

如果程序中 thread没有使用join()将之入主线程流程中,则最后一行显示“ main执行”的描述会先执行完毕,因为 thread使用了 sleep(),这让主线程有机会取得时间执行)。
有时候加入的线程可能处理太久,你不想无止境等待这个线程工作完毕,则可以在join()时指定时间,如join(10000),这表示加入成为流程的线程至多可处理10000毫秒,也就是10秒,如果加入的线程还没执行完则目前线程可继续执行原本工作流程。
4.停止线程
线程完成run()方法后,就会进入Dead,进入Dead(或已经调用过 start()方法)的线程不可以再次调用 start()方法,否则会抛出
IllegalThreadstateException o
thread类上定义有stop()方法,不过被标示为 Deprecated.被标示为 Deprecated的API表示过去确实定义过,后来因为会引发某问题,为了确保向前兼容性,这些API没有直接剔除,但不建议新撰写的程序再使用它。
包括Thread的resume()、suspend()、destroy()等均不建议使用,可能会造成无法预期的结果。若要停止线程,需自行操作:

public class Some implements Runnable{
    private volatile boolean isContinue=true;
    ...
    public void stop(){
    isContinue=false;
    }
    public void run(){
    while(isContinue){
            ...
        }
    }
}

停止线程只需调用some的stop方法。标志为volatile,isContinue的改变可以被run()方法看的,不会因为线程导致调用过stop还会继续执行。
ThreadGroup
每个线程都属于某个线程群组( ThreadGroup)。若在main()主流程中产生一个线程,该线程会属于main线程群组。可以使用以下程序片段取得目前线程所属线程群组名: Thread.currentThread().getThreadGroup(). getName();
每个线程产生时,都会归入某个线程群组,这视线程是在哪个群组中产生。如果没有指定,则归入产生该子线程的线程群组。也可以自行指定线程群组,线程一旦归入某个群组,就无法再更换。
java.lang. ThreadGroup类可以管理群组中的线程。可以使用以下方式产生群组,并在产生线程时指定所属群组:

ThreadGroup g1=new ThreadGroup("group1");
ThreadGroup g2=new ThreadGroup("group2");
Thread t1=new thread (g1,"group1的成员");
Thread t2=new thread (g2,"group2的成员");

ThreadGroup 的某些方法,可以对群组中所有线程产生作用。例如, interrupt()方法可以中断群组中所有线程, setMaxPriority)方可以设定群组中所有线程最大优先权。
如果想要一次取得群组中所有线程,可以使用 numerate()方法。例如:

Thread[] threadsnew Thread[threadGroupl.activeCount];
threadGroup1. enumeratethreads);

activeCount()方法取得群组的线程数量, enumerate()方法要传入 Thread数组,这会将线程对象设定至每个数组索引。 ThreadGroup中有个 uncaughtException()方法,群组中某个线程发生异常而未捕捉时,JVM会调用此方法进行处理。如果 ThreadGroup有父 ThreadGroup,就会调用父 ThreadGroup的uncaughtException ()方法,否则看看异常是否ThreadDeath实例。若是则什么都不做,若不是则调用异常的printstrackTrace()。如果必须定义ThreadGroup中线程的异常处理行为,可以重新定义此方法。例如:

package thread;

public class ThreadGroupDemo {
    public static void main(String[] args) {
        ThreadGroup tg = new ThreadGroup("group") {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                System.out.printf("%s:%s%n", t.getName(), e.getMessage());
            }
        };
        Thread t = new Thread(tg, () -> {
            throw new RuntimeException("ggg");
        });
        t.start();
    }
}

uncaughtException()方法第一个参数可取得发生异常的线程实例,第二个参数可取得异常对象,范例中显示了线程的名称及异常信息。
如果 ThreadGroup发生异常 uncaughtException处理顺序是
如果 ohreadGroup有父 ThreadGroup
,就会调用父
方法
否则,看看 Thread是否使用
setUncaughtExceptionHandler
方法设定 Thread.ncaught实例,有的话就会调用其
uncaughtException ()
方法。
否则,看看异常是否为 ThreadDeath
实例,若“是”则什么都不做,若“否”则调用异常的
printStrackTrace
未捕捉异常会由线程实例
setUncaughtExcept ion Handler
()设定的 Thread. UncaughtExceptlon Handler实例处理,之后是线程的 ThreadGroup,然后是默认的
Thread. UncaughtExceptionHandler o所以对于线程本身未捕捉的异常,可以自行指定处理方式:

package thread;

public class ThreadGroupDemo {
    public static void main(String[] args) {
        ThreadGroup tg = new ThreadGroup("group") {

        Thread t = new Thread(tg, () -> {
            throw new RuntimeException("ggg");
        });
        t.start();

        Thread t2 = new Thread(tg, () -> {
            throw new RuntimeException("ggggggg");
        });
        t2.setUncaughtExceptionHandler((thread, throwable) -> {
            System.out.printf("%s:%s%n", thread.getName(), throwable.getMessage());
        });
    }
}

猜你喜欢

转载自blog.csdn.net/zkd758/article/details/80216186