多线程编程

本文详细介绍了Java中多线程的创建方式,包括继承Thread、实现Runnable接口、匿名内部类、lambda表达式以及线程池。还讨论了线程的中断、等待、休眠等机制,并探讨了Thread类的常用方法如isInterrupted和join。同时,解释了sleep方法的本质。最后,提到了线程同步关键字synchronized和volatile的重要性。
摘要由CSDN通过智能技术生成

        多线程指的是一个程序中包含两个或者两个以上的线程,多线程的提出是为提高代码的执行效率,这就好比工厂中的流水线,只有一条称为单线程,有多条流水线就称为多线程。多线程提高效率的同时由于并发执行的不确定性,导致出现的结果很多是我们不想要的,所以为了得到我们想要的结果就会在编写多线程的时候加入各种锁,其中最重要的就是synchronized和volatile。在认识它们之前先从多线程最基本的开始。

目录

一:线程的创建

1.基于类继承Thread创建线程 

2.基于实现Runnable接口的方式创建线程

3.基于匿名内部类继承Thread创建线程

4.基于匿名内部类实现Runnable接口创建线程

5.基于lambda表达式创建Runnable子类对象创建线程 

6.基于线程池的方式创建多线程 

7.基于实现Callable接口的方式创建线程 

多线程中Thread的常见方法

多线程的特点

二:多线程的中断

中断一个线程有两种方式

通过共享的boolean类型的变量来中断线程

通过调用Thread里面的interrupt()方法来中断线程 

interrupt()的两个行为

三:多线程的等待

join方法

join的行为

join(long millis)方法

四:多线程的休眠

sleep的本质

五:获取多线程实例


一:线程的创建

这里有一个面试题:线程的创建方式有几种,答案一共是7中。下面讲现阶段能接触到的创建方式

1.基于类继承Thread创建线程 

public class Thread01 {
    public static void main(String[] args) {
        Mythod mythod = new Mythod();
        Thread thread = new Thread(mythod);
        thread.start();
    }
}
class Mythod extends Thread{
    @Override
    public void run() {
        while(true){
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("你好");
        }
    }
}

2.基于实现Runnable接口的方式创建线程

public class Thread02 {
    public static void main(String[] args) {
        t t = new t();
        Thread thread = new Thread(t);
        thread.start();
    }
}
class t implements Runnable{
    @Override
    public void run() {
        while(true){
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("再将");
        }
    }
}

3.基于匿名内部类继承Thread创建线程

public class Thread03 {
    public static void main(String[] args) {
        Thread thread = new Thread() {
            @Override
            public void run() {
                while(true){
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("背景");
                }
            }
        };
        thread.start();
    }
}

4.基于匿名内部类实现Runnable接口创建线程

public class Thread04 {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while(true){
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("李华");
                }
            }
        }).start();
    }
}

5.基于lambda表达式创建Runnable子类对象创建线程 

public class Thread05 {
    public static void main(String[] args) {
        new Thread(()->{
            while(true){
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("韩梅梅");
            }
        }).start();
    }
}

6.基于线程池的方式创建多线程 

public class Thread28 {
    public static void main(String[] args) {
        //使用以下标准库中的线程池
        //先创建一个线程池的实例
        ExecutorService service = Executors.newFixedThreadPool(10);
        //给实例里面加入一些任务
        for (int i = 0; i < 10; i++) {
            service.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("你好");
                }
            });
        }
    }
}

7.基于实现Callable接口的方式创建线程 

public class Callable2 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable<Integer> callable = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int sum = 0;
                for (int i = 0; i < 5000; i++) {
                    sum++;
                }
                return sum;
            }
        };
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        Thread t = new Thread(futureTask);
        t.start();
        int ret = futureTask.get();
        System.out.println(ret);
    }
}

多线程中Thread的常见方法

getName()                        获得线程的名字(在创建线程的时候可以给线程起名字)
isDaemon()                       判断线程是否为守护线程(判断是否为后台线程)
isInterrupted                    判断线程是否被中断
public class Thread06 {
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                    
            }
        }, "我的线程");
        thread.start();
        System.out.println(thread.getName());
        System.out.println(thread.isDaemon());
        System.out.println(thread.isInterrupted());
    }
}

setDaemon()                      将线程设置为后台线程

一个线程被创建出来默认就是前台线程,前台线程会阻止进程的结束,而后台线程则不会,后台线程一结束进程就结束了,因此可以将前台线程设置为后台线程,从而提高程序的执行速度。

public class Thread06 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while(true){
                    System.out.println("你好");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        thread.start();
        System.out.println("程序已经结束");
    }
}

这里先出的“程序已经结束”再输出的“你好”体现出来多线程并发执行的不确定性,这个不确定性和操作系统的实现相关。可以看到进程并没有结束,原因是应为thread是一个前台线程,进程只有等待所有前台线程都结束以后才会结束。为了结束进程,只需要在开启thread线程之前将它设置为后台线程即可。

        thread.setDaemon(true);

 

多线程的特点

1.每个线程都是一个单独的执行流

2.每个线程之间是“并发”执行的,每个线程之间的执行顺序和操作系统的具体实现有关

3.多个线程执行的时间不是一个线程的1/n的原因是因为:创建线程自身也是有开销的,多个线程cpu上不一定是纯并行,也可能并发执行 

面试题:请分别从方法及运行结果说明Thread类中run和start的区别 

  1. 方法的区别:直接调用run方法,并没有创建新的线程,而只是之前的线程中(主线程),执行了run里面的内容;使用start则是创建了新的线程,新的线程里面调用的run方法。
  2. 运行结果的区别:如果直接运行run方法,程序会等待run方法(方法里面写了个死循环)执行完毕以后才会执行其他线程,但是运行start则是线程之间的并发执行,输出的结果也是交替出现的。
  3. run方法可以多次调用,而start方法只能被调用一次
class MyThread extends Thread{
    @Override
    public void run() {
        while(true){
            System.out.println("再见");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class Thread01 {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
        while(true){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("你好");
        }

    }
}

 运行的结果:

class MyThread extends Thread{
    @Override
    public void run() {
        while(true){
            System.out.println("再见");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class Thread01 {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.run();
        while(true){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("你好");
        }

    }
}

 运行结果:

二:多线程的中断

中断一个线程有两种方式

通过共享的boolean类型的变量来中断线程

中断线程就是让线程尽快将入口方法执行结束(入口方法:继承Thread重写run、实现Runnable接口重写run) 

public class Thread07 {
    private static boolean flag = true;
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(){
            @Override
            public void run() {
                while(flag){
                    try {
                        System.out.println("你好");
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        thread.start();
        Thread.sleep(3000);
        flag = false;
        System.out.println("线程已经被中断");
    }
}

通过调用Thread里面的interrupt()方法来中断线程 

public class Thread08 {
    public static void main(String args[]) throws InterruptedException {
        Thread thread = new Thread(){
            @Override
            public void run(){
                while(!Thread.currentThread().isInterrupted()){
                    try {
                        System.out.println("你好");
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        thread.start();
        Thread.sleep(3000);
        thread.interrupt();
        System.out.println("线程被中断");
    }
}

 

在分析结果之前,先明白下面d的方法:

Thread.currentThread().isInterrupted()方法

上面的这个方法是Thread类里面的静态方法,Thread.currentThread()表示获取当前线程的实例也就是线程对象。isInterrupted()方法为修改线程内置的标志位,它默认的结果是false,因此在while里面使用时注意取反。

interrupt()的两个行为

1. 如果thread线程没有处于阻塞状态,此时的interrupt就会修改内置的标志位

2.如果thread线程正处于阻塞状态,此时的interrupt就让线程内部产生阻塞的方法,在这个例子中就是sleep方法抛出异常

因此我们看到的结果首先是thread里面执行了三秒,然后调用interrupt方法,此时thread线程刚好处于阻塞状态,因此就让sleep方法抛出异常。但是由于catch语句里面并没有退出的逻辑,所以thread还会一直执行

为了看到interrupt()方法的打断效果,有两种方式:

第一种就是不要interrupt()方法前面的休眠操作,因为多线程是抢占式的执行,一旦让interrupt()方法成功将while里面的标志位变为false则thread就退出了。

public class Thread {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while(!Thread.currentThread().isInterrupted()){//isInterrupted()本身是false
                    System.out.println("线程运行中....");
                }
            }
        });
        thread.start();
        System.out.println("线程中断");
        thread.interrupt();
    }
}

第二种就是在catch代码块中写退出的逻辑

第二种方式是我们主要使用的,因为我们可以在catch代码块中写相应的逻辑语句,可以让thread线程立即退出,也可以让它待会退出或者是永远不退出。 

三:多线程的等待

因为多线程的执行顺序是不确定的,为了让某一个线程有明确的的执行顺序,这里可以使用线程等待机制。

join方法

join方法主要用来等待线程结束

现在要求t1线程先执行,然后t2线程在执行:看如下代码

public class Thread09 {
    public static void main(String[] args) {
        System.out.println("main_beg");
        Thread t1 = new Thread(()->{
            System.out.println("t1_beg");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t1_end");
        });

        Thread t2 = new Thread(()->{
            System.out.println("t2_beg");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t2_end");
        });
        t1.start();
        t2.start();
        System.out.println("main_end");
    }
}

可以看出代码的执行顺序是乱的,这样的结果往往是我们在编程中不想要的,我们作为编程者,需要的就是确定的结果。

为了获得准确的结果就要引入join,方法就是分别在t1.start()和t2.start()下面加入t1.join()、t2.join()

join的行为

1.如果被等待的线程还没执行完就阻塞等待

2.如果被等待的线程已经执行完了,直接就返回

3.main的阻塞等待时间是所有线程执行时间的总和

join(long millis)方法

和join一样,只不过这是有时间的等待,等不到就直接走了。

四:多线程的休眠

这个也已经在前面多次看到了,方法就就不讲了,主要讲一下sleep的本质。

sleep的本质

和sleep一样的让线程等待的方法还有wait/join/等待锁...它们的本质是把线程在PCB中的队列从就绪状态移到阻塞状态。时间到了或者出发了中断就会有回到就绪队列。

五:获取多线程实例

Thread.currentThread获得当前线程的实例

可以应用到下面三种形式的线程中:

public class Thread10 {
    public static void main(String[] args) {
        Thread thread = new Thread(){
            @Override
            public void run(){
                System.out.println(Thread.currentThread().getId());
                System.out.println(this.getId());
            }
        };
        thread.start();
    }
}

结果都是12。如果是继承Thread方式创建的继承,Thread.currentThread获得的对象实例就是当前的对象thread,因此可以用this来代替

但是利用实现Runnable接口和创建lamdba的方式创建线程就不能用this,this得到的不是当前对象的实例。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

咸鱼吐泡泡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值