Java多线程(1)

Java多线程(1)

线程的世界

何为线程

通俗来说计算机中的每一个任务就是一个进程,而在每一个进程内部至少有一个线程,线程是进程中的一个实体,进程中的多个线程共享进程的资源。

线程是程序执行的一个路径,每一个线程都有自己的局部变量表、程序计数器以及自己的生命周期

创建线程

Java有三种创建线程的方式

  • 实现Runnable接口

  • 继承Thread类

  • 使用FutureTask

第一种:

public class ThreadTest {


//    MyThread继承了Thread类,重写run方法
    public static class MyThread extends Thread{

        @Override
        public void run() {
            System.out.println("Thread run");
        }
    }

    public static void main(String[] args) {

        MyThread myThread = new MyThread();


//        当创建完MyThread实例后线程并没有启动,除非你调用start方法
        myThread.start();

    }
}


第二种:


public class RunnableTask implements Runnable{

    @Override
    public void run() {
        System.out.println("thread run");
    }

    public static void main(String[] args) {

        RunnableTask task = new RunnableTask();

        new Thread(task).start();

    }
}

第三种:

public class CallerTask implements Callable<String> {

    @Override
    public String call() throws Exception {
        return "world";
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        FutureTask<String> futureTask = new FutureTask<>(new CallerTask());

        new Thread(futureTask).start();

        String res = futureTask.get();

        System.out.println(res);
    }
}

线程生命周期

大体可以分为5个阶段:

  • NEW

  • RUNNABLE

  • RUNNING

  • BLOCKED

  • TERMINATED

NEW

当用new创建一个人Thread对象时,它并不处于执行状态,因为没有调用start(),此时为new状态,也就是Thread对象的状态

RUNNABLE

线程对象进入RUNNABLE必须调用start(),此时才真正在JVM进程中创建了一个线程,但是线程启动后不一定立即得到执行。线程的运行与否要听令于CPU调度,此时的线程具备执行资格,在等待CPU调度后才能执行

RUNNING

一旦CPU选中了线程,那么它才能真正开始执行自己的逻辑代码,

在该状态下,可能发生以下变化:

  • 直接进入TERMINATED,调用了stop()

  • 进入BLOCKED,如使用了sleep或者wait

  • 进行某个阻塞IO操作,进入BLOCKED

  • 获取某个锁资源,进入BLOCKED

  • 线程主动使用yield(),放弃CPU执行权,进入RUNNABLE

BLOCKED

进入BLOCKED的原因上面说了

在该状态下,可能发生以下变化:

  • 直接进入TERMINATED,调用了stop()

  • 线程阻塞结束,进入RUNNABLE

  • 线程完成了休眠,进入RUNNABLE

  • Wait中的线程被唤醒,进入RUNNABLE

  • 线程获取到了锁资源,进入RUNNABLE

  • 线程阻塞被打断,进入RUNNABLE

TERMINATED

TERMINATED是最终状态,该状态中线程不会切换到其他状态

Thread构造函数

线程命名

默认命名

如果没有为线程显式指定名字,那么线程就会以"Thread-"作为前缀与一个自增数字进行组合,这个自增数字在整个JVM进程中会不断自增

比如执行以下代码:

public class T1 {

    public static void main(String[] args) {

        IntStream.range(0,5).boxed().map(
                i->new Thread(
                        ()-> System.out.println(Thread.currentThread().getName())
                )
        ).forEach(Thread::start);

        
    }
}

输出:

Thread-0
Thread-4
Thread-2
Thread-1
Thread-3

命名线程

以下这些构造函数都可以传入参数对线程进行命名

Thread(Runnable target, String name)
分配一个新的 Thread对象。


Thread(String name)
分配一个新的 Thread对象。


Thread(ThreadGroup group, Runnable target, String name)
分配一个新的 Thread对象,使其具有 target作为其运行对象,具有指定的 name作为其名称,属于 group引用的线程组。


Thread(ThreadGroup group, Runnable target, String name, long stackSize)
分配一个新的 Thread对象,以便它具有 target作为其运行对象,将指定的 name正如其名,以及属于该线程组由称作 group ,并具有指定的 堆栈大小 。


Thread(ThreadGroup group, String name)
分配一个新的 Thread对象。

实例:

public class T2 {

    private final static String STRING = "APPLE-";

    private static Thread create(final int intName){
        return new Thread(
                ()-> System.out.println(Thread.currentThread().getName()),STRING + intName
        );
    }

    public static void main(String[] args) {

        IntStream.range(0,5).mapToObj(T2::create).forEach(Thread::start);
        

    }
}

Thread API

sleep方法

sleep会使当前线程进入指定毫秒数的休眠,暂停执行,休眠的一个重要特性就是不会放弃monitor锁的所有权

举个例子说明线程睡眠时拥有的监视器资源不会释放

public class SleepTest {

  private static final Lock LOCK = new ReentrantLock();

    public static void main(String[] args) {

        Thread a = new Thread(new Runnable() {
            @Override
            public void run() {
                    LOCK.lock();
                System.out.println("A sleep");

                try {
                    Thread.sleep(10000);
                    System.out.println("A awaked");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    LOCK.unlock();
                }
            }
        });


        Thread b = new Thread(new Runnable() {
            @Override
            public void run() {

                LOCK.lock();
                System.out.println("B sleep");

                try {
                    Thread.sleep(10000);
                    System.out.println("B asw");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    LOCK.unlock();
                }
            }
        });


        a.start();
        b.start();
    }
}


看结果发现:

线程A先获取锁,输出一段文字,然后sleep方法使得A休眠,在休眠这段时间内锁lock还是a持有的

yield方法

yield方法会提醒调度器我自愿放弃当前CPU的资源

public class YieldTest implements Runnable{


    public YieldTest() {
        Thread t = new Thread(this);
        t.start();
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            if ( (i % 5) == 0){
                System.out.println(Thread.currentThread().getName()+"yirld");
                Thread.yield();
            }
        }
        System.out.println(Thread.currentThread().getName()+"over");
    }

    public static void main(String[] args) {
        new YieldTest();

        new YieldTest();
    }

}


线程优先级

优先级较高的线程获得cpu资源较多,也就是cpu优先执行优先级别高的线程

例子:

public class PriorityTest {


    public static void main(String[] args) {

        Thread t1 = new Thread(
                ()->
                {
                    while (true){
                        System.out.println("1");
                    }
                }
        );

        t1.setPriority(3);

        Thread t2 = new Thread(
                ()->
                {
                    while (true){
                        System.out.println("2");
                    }
                }
        );

        t2.setPriority(10);

        t1.start();
        t2.start();

    }
}


发现线程2出现的频率高很多

public class PriorityTest_1 {


    static class T1 extends Thread{

        @Override
        public void run() {
            System.out.println(this.getPriority());

        }
    }

    public static void main(String[] args) {

        T1 t1 = new T1();
        t1.start();

    }
}

发现默认优先级设置为5

线程interrupt

每个线程都有一个与之相关联的 Boolean 属性,用于表示线程的中断状态(interrupted status)。中断状态初始时为 false;

当另一个线程通过调用Thread.interrupt()中断一个线程时,会出现以下两种情况之一。

  • (1)如果那个线程在执行一个低级可中断阻塞方法,例如Thread.sleep(), Thread.join()或 Object.wait(),那么它将取消阻塞并抛出InterruptedException。

  • (2)否则,interrupt() 只是设置线程的中断状态。在被中断线程中运行的代码以后可以轮询中断状态,看看它是否被请求停止正在做的事情。中断状态可以通过
    Thread.isInterrupted()来读取,并且可以通过一个名为Thread.interrupted() 的操作读取和清除。

interrupt中断机制中有如下方法:

  • Thread.interrupt():(1)将线程的中断状态设置为true(2)让被阻塞的线程抛出InterruptedException异常(同时中断状态为false)

  • Thread.isInterrupted(),检测调用该方法的线程是否被中断,中断状态不会被清除。线程一旦被中断,该方法返回true,而一旦sleep等方法抛出异常,它将清除中断状态,此时方法将返回false。

  • Thread.interrupted(),检测当前线程是否被中断,并且中断状态会被清除(即重置为false);由于它是静态方法,因此不能在特定的线程上使用,只能报告调用它的线程的中断状态;如果该方法被调用两次,则第二次一般是返回false,如果线程不存活,则返回false。

public class T1 {


//    检测调用该方法的线程是否被中断,中断状态不会被清除。线程一旦被中断,该方法返回true,
//    而一旦sleep等方法抛出异常,它将清除中断状态,此时方法将返回false。


    public static void main(String[] args) {
        Thread t = Thread.currentThread();
        System.out.println("1: " + t.isInterrupted());
        t.interrupt();
        System.out.println("2: " + t.isInterrupted());
        System.out.println("3: " + t.isInterrupted());
        try {
            Thread.sleep(2000);
            System.out.println("not interrted...");
        } catch (Exception e) {
            System.out.println("interrupted...");
            System.out.println("4: " + t.isInterrupted());
        }
        System.out.println("5: " + t.isInterrupted());
    }


}


public class T2 {

    public static void main(String[] args) {

//        检测当前线程是否被中断,并且中断状态会被清除(即重置为false);由于它是静态方法,因此不能在特定的线程上使用,只能报告调用它的线程的中断状态;如果该方法被调用两次,则第二次一般是返回false,如果线程不存活,则返回false。



        System.out.println("1: " + Thread.interrupted());
        Thread.currentThread().interrupt();
        System.out.println("2: " + Thread.interrupted());
        System.out.println("3: " + Thread.interrupted());
    }
}


一个例子理解

public class T7 extends Thread{

    volatile Boolean stop = false;

    public static void main(String[] args) throws InterruptedException {
        T7 t = new T7();
        System.out.println("starting thread...");
        t.start();

        Thread.sleep(10000);

        System.out.println("asking thread to stop...");
        // 必须要在interrupt之前设置
        // 如果线程阻塞,将不会检查此变量,调用interrupt之后,线程就可以尽早的终结被阻塞状态,能够检查这一变量
        t.stop = true;
        // 如果线程没有被阻塞,这时调用interrupt将不起作用。
        // 这一方法实际上完成的是:在线程受到阻塞时抛出一个中断信号,这样线程就可以退出阻塞状态
        t.interrupt();

        Thread.sleep(3000);
        System.out.println("stopping app...");
    }

    @Override
    public void run() {
        while (!stop) {
            System.out.println("running...");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                System.out.println("interrupted...");
            }
        }
        System.out.println("thread exit...");
    }

//    把握几个重点:stop变量、run方法中的sleep()、interrupt()、InterruptedException。串接起来就是这个意思:
//
//当我们在run方法中调用sleep(或其他阻塞线程的方法)时,如果线程阻塞的时间过长,比如10s,那在这10s内,线程阻塞,run方法不被执行;
//但是如果在这10s内,stop被设置成true,表明要终止这个线程;但是,现在线程是阻塞的,它的run方法不能执行,自然也就不能检查stop,所以线程不能终止;
//这个时候,我们就可以用interrupt()方法了:我们在thread.stop = true;语句后调用thread.interrupt()方法, 该方法将在线程阻塞时抛出一个中断信号,
// 该信号将被catch语句捕获到,一旦捕获到这个信号,线程就提前终结自己的阻塞状态;
//这样,它就能够 再次运行run 方法了,然后检查到stop = true,while循环就不会再被执行,在执行了while后面的清理工作之后,run方法执行完 毕,线程终止。
//当代码调用中需要抛出一个InterruptedException,可以选择吧中断状态复位,也可以选在向外抛出InterruptedException,由外层的调用者来决定。
}


线程join()方法

假设你需要等待某几件事情完成后才继续往下执行,比如多个线程加载资源,需要等待多个线程全部加载完毕再汇总处理,这个时候Thread类提供的join方法就可以完成这个任务

Thread的join()方法和sleep()方法一样也是一个可中断的方法,就是如果有其他线程执行了对当前线程的interrupt操作,它也会捕获到中断信号,并且擦除线程的interrupt标识

例子:

将原本并行执行的多线程方法变成串行执行的

public class T1 {

//    并行执行的


    //private static final Long count = 10000L;
    public static void main(String[] args){
        long base = System.currentTimeMillis();
        try {
            ThreadJoin t1 = new ThreadJoin("线程1");
            ThreadJoin t2 = new ThreadJoin("线程2");
            t1.start();
            t2.start();

        } catch (Exception e) {
            e.printStackTrace();
        }
        long time = System.currentTimeMillis() - base;
        System.out.println("执行时间:"+time);
    }

}


class ThreadJoin extends Thread{
    private static final Long count = 10L;

    public ThreadJoin(String name){
        super(name);
    }

    @Override
    public void run() {
        //super.run();
        for(int i = 1; i <= count; i ++){
            System.out.println(this.getName()+":"+i);
        }
    }
}


public class T2 {


//    要实现串行执行,可以加上join方法,实现线程1执行完成后才开始执行线程2,也就是串行执行


    //private static final Long count = 10000L;
    public static void main(String[] args){
        long base = System.currentTimeMillis();
        try {
            ThreadJoin_1 t1 = new ThreadJoin_1("线程1");
            ThreadJoin_1 t2 = new ThreadJoin_1("线程2");
            t1.start();
            t1.join();
            t2.start();

        } catch (Exception e) {
            e.printStackTrace();
        }
        long time = System.currentTimeMillis() - base;
        System.out.println("执行时间:"+time);
    }

}


class ThreadJoin_1 extends Thread{
    private static final Long count = 10L;

    public ThreadJoin_1(String name){
        super(name);
    }

    @Override
    public void run() {
        //super.run();
        for(int i = 1; i <= count; i ++){
            System.out.println(this.getName()+":"+i);
        }
    }
}


例子:

join方法可以使得所属线程X对象正常执行任务,使得当前其他线程进行无限期阻塞直到线程X销毁后再继续执行

public class T3 {

    private static class Thread1 extends Thread{
        @Override
        public void run() {

            try {
                int val = (int) (Math.random()*10000);
                System.out.println(val);
                Thread.sleep(val);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {


        Thread1 thread1 = new Thread1();
        thread1.start();
        try {
            thread1.join();
            System.out.println("我要等Thread1执行完毕后再执行");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}


模拟案例:

假设一个系统用于查询航班信息,当用户发起请求时,需要到各大航空公司获取数据,最后统一返回给客户端

思路:

将每一个航空公司的查询都交给一个线程去工作,然后在它们结束工作之后统一对数据进行整理

/**
 * 查询接口
 */
public interface FightQuery {

    List<String> get();
}

public class FightQueryTask extends Thread implements FightQuery{

//    获取航空公司数据

    private final String origin;

    private final String destination;

    private final List<String> flightList = new ArrayList<>();

    public FightQueryTask(String airline,String origin,String destination){
        super("[" + airline + "]");
        this.origin = origin;
        this.destination = destination;
    }

    @Override
    public void run() {
        System.out.printf("%s-查询从%s到%s\n",getName(),origin,destination);
        int val = ThreadLocalRandom.current().nextInt(10);
        try {

//            模拟查询的花费的时间
            TimeUnit.SECONDS.sleep(val);
            this.flightList.add(getName() + "---" + val);
            System.out.printf("航班: %s 查询成功\n",getName());
        } catch (InterruptedException e){
            e.printStackTrace();
        }
    }

    @Override
    public List<String> get() {
        return this.flightList;
    }
}



public class Example {

//    各大航空公司
    private static List<String> fightCompany =
            Arrays.asList(
              "CSA","CEA","HNA"
            );

    public static void main(String[] args) {

        List<String> res = search("上海","北京");
        System.out.println("=======================res=========================");

        res.forEach(System.out::println);


    }

    private static List<String> search(String original,String dest){

        final List<String> res = new ArrayList<>();


//        创建查询航班信息的线程列表
        List<FightQueryTask> tasks = fightCompany.stream()
                .map(f->createTask(f,original,dest))
                .collect(Collectors.toList());


//        分别启动这几个线程
        tasks.forEach(Thread::start);


//        分别调用每一个线程的join方法 阻塞当前线程
        tasks.forEach(t->
                {
                    try {
                        t.join();
                    } catch (InterruptedException e){
                        e.printStackTrace();
                    }
                }
                );
//      在此之前  当前线程被阻塞,获取每一个查询线程的结果  加入到res

        tasks.stream().map(FightQuery::get).forEach(res::addAll);

        return res;
    }

    private static FightQueryTask createTask(String fight,String original,String dest){
        return new FightQueryTask(fight,original,dest);
    }
}


停止线程

Java中有三种方法可以终止正在运行的线程:

  • 使用退出标志,使得线程正常退出

  • 使用stop方法,但是不推荐,而且它和suspend一样属于作废过期的方法

  • 使用interrupt方法中断线程

interr方法效果不像for+break那样,马上停止循环,interr方法仅仅是在当前线程中打了一个停止的标记,并不是真正停止线程

例子:

public class T1 {

    private static class T_a extends Thread{
        @Override
        public void run() {
            super.run();
            for (int i = 0; i < 30; i++) {
                System.out.println("i=  " + (i+1));
            }
        }
    }


    public static void main(String[] args) {

        try {
            T_a a = new T_a();
            a.start();
            Thread.sleep(3000);
            a.interrupt();
        } catch (InterruptedException e){
            e.printStackTrace();
        }

    }

}


输出:

i=  1
i=  2
i=  3
i=  4
i=  5
i=  6
i=  7
i=  8
i=  9
i=  10
i=  11
i=  12
i=  13
i=  14
i=  15
i=  16
i=  17
i=  18
i=  19
i=  20
i=  21
i=  22
i=  23
i=  24
i=  25
i=  26
i=  27
i=  28
i=  29
i=  30


public class T4 extends Thread{


//    interrupt()方法仅仅将线程设置为中断状态,但是并不会去停止线程,
//    返回true说明线程的中断状态已经被设置了。


    @Override
    synchronized public void run() {
        super.run();
        for (int i = 0; i < 100000; i++) {
            //判断线程是否停止
            if (this.interrupted()){
                System.out.println("已经停止了");
                break;
            }
            System.out.println(i);
        }
        System.out.println("虽然线程已经停止了,但是还是会跳出for循环继续向下执行的");
    }

    public static void main(String[] args) {
        T4 test = new T4();
        try {
            test.start();
            test.sleep(50);
            //线程停止
            test.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println();

    }


}




public class T5 extends Thread{

//    使用异常捕获法停止多线程:
//
//当interrupted()检测到线程状态为停止的时候,会抛出异常,继而捕获这个异常来停止多线程


    @Override
    synchronized public void run() {
        super.run();
        try {
            for (int i = 0; i < 100000; i++) {
                //判断线程是否停止
                if (this.interrupted()) {
                    System.out.println("已经停止了");
                    //抛出异常
                    throw new InterruptedException();
                }
                System.out.println(i);
            }
        } catch (InterruptedException e) {
            System.out.println("线程结束...");
        }
    }

    public static void main(String[] args) {
        T5 test = new T5();
        try {
            test.start();
            test.sleep(100);
            test.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println();

    }

}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值