多线程&JUC:多线程的实现和常用成员方法(守护、礼让、插入线程)

‍作者简介:一位大四、研0学生,正在努力准备大四暑假的实习
上期文章:首期文章
订阅专栏:多线程&JUC
希望文章对你们有所帮助

JUC的学习也是需要一些计算机、操作系统的知识的,也算是比较重要的吧,其实自己也是接触了不少的,包括之前做Redis的项目的时候也是老摸这些玩意。但是还没有非常系统的学过,为了即将到来的面试,JUC抓紧速成一波。

多线程的实现和常用成员方法

  • 实现多线程
    • 继承Thread类
    • 实现Runnable接口
    • 利用Callable和Future接口
    • 总结
  • 多线程的常用成员方法
    • 线程优先级
    • 守护线程
    • 礼让线程
    • 插入线程

实现多线程

继承Thread类

将类声明为Thread类的子类,该子类应重写Thread类的new方法,接下来就可以分配并启动该子类的实例,开启一个线程。

这里实现一个简单的Demo:
1、创建Thread类的子类:

public class MyThread extends Thread{
    @Override
    public void run() {
        //线程要执行的代码
        for (int i = 0; i < 100; i++) {
            System.out.println(this.getName() + "Hello World!");
        }
    }
}

2、创建对象开启实例,观察结果:

public class ThreadDemo {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();

        //给线程起名字,方便观察运行结果
        t1.setName("线程1");
        t2.setName("线程2");

        //开启线程
        t1.start();
        t2.start();
    }
}

最终运行结果显示线程1和线程2是并行执行,而不是串行的了。

实现Runnable接口

先自己定义一个类,这个类实现了Runnable接口,并重写run方法。接着在测试类中创建这个类的对象,再创建一个Thread类的对象,并开启线程。

1、定义MyRun类并实现Runnable接口,由于这个类没有继承Thread类,所以不能使用getName方法,但是Thread类本身具有静态方法currentThread可以获取当前在执行任务的线程的对象,因此可以通过这个对象来执行getName方法:

public class MyRun implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            //获取当前线程的对象
            Thread thread = Thread.currentThread();
            System.out.println(thread.getName() + "Hello World");
        }
    }
}

2、测试类中创建该类的对象、Thread类的对象,并开启线程:

public class ThreadDemo {
    public static void main(String[] args) {
        //创建MyRun的对象,表示多线程要执行的任务
        MyRun mr = new MyRun();
        //创建线程对象,并setName
        Thread t1 = new Thread(mr);
        t1.setName("线程1");
        Thread t2 = new Thread(mr);
        t2.setName("线程2");
        //开启线程
        t1.start();
        t2.start();
    }
}

利用Callable和Future接口

第三种实现方式实际上是对前面两种方法的补充。
前面两种实现方式都重写了run,但是其没有返回值,我们就无法获取多线程运行的结果。
第三种实现方式的特点就是可以获取多线程运行结果

其实现的流程:

1、创建一个MyCallable类实现Callable接口
2、重写call方法,方法是有返回值的,表示多线程运行的结果
3、编写测试类:
(1)创建MyCallable对象表示多线程要执行的任务
(2)创建Future对象,该对象可以管理多线程运行的结果,由于Future是一个接口无法创建对象,所以要创建其实现类FutureTask的对象
(3)创建Thread对象,再开启线程

1、MyCallable类:

public class MyCallable implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        //求1-100之间的和
        int sum = 0;
        for (int i = 0; i <= 100; i++) {
            sum += i;
        }
        return sum;
    }
}

2、测试类:

public class ThreadDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建MyRun的对象,表示多线程要执行的任务
        MyCallable mc = new MyCallable();
        //创建FutureTask对象
        FutureTask<Integer> ft = new FutureTask<>(mc);
        //创建线程对象
        Thread t = new Thread(ft);
        //开启线程
        t.start();
        Integer result = ft.get();
        System.out.println(result);
    }
}

总结

1、继承Thread:
(1)优点:变成简单,可以直接使用Thread类中的方法
(2)缺点:可拓展性差,不能再继承其他的类
2、实现Runnable接口:
(1)优点:拓展性强,实现该接口的同时还能继承其他的类
(2)编程相对复杂,不能直接使用Thread类中的方法
3、实现Callable接口:
(1)除了Runnable接口的优点,还可以观察线程运行的结果
(2)编程相对复杂,不能直接使用Thread类中的方法

多线程的常用成员方法

下面是多线程常用的一些成员方法,其实很多也都是见过的:

方法名称 说明
String getName() 返回线程名称
void setName(String name) 设置线程名,也可以使用构造方法
static Thread currentThread() 获取当前线程对象
static void sleep(long time) 让线程休眠指定的时间,单位为毫秒
setPriority(int newPriority) 设置线程优先级
final int getDaemon(boolean on) 设置为守护线程
public static void yield() 礼让线程
public static void join() 插入进程

对于前4种方法,有一些注意点和细节:

1、若没有给线程设置名字,使用getName也是会有默认的名字的,格式为Thread-X(X为序号,从0开始),可以自行去底层看一下
2、除了可以用setName可以给线程设置名字,也可以使用构造函数MyThread t = new MyThread("线程");,但是需要保证MyThread里面的构造函数内部使用super关键字,从而重写父类的构造方法。
3、如果我们没有启动线程,执行Thread.currntThread(),也可以获取到线程,这个线程的名称即为main,即JVM虚拟机启动以后,会自动启动多条线程,其中有一条就是main线程,作用是调用main方法,并执行里面的代码
4、sleep():
(1)哪条线程执行到这个方法,哪条线程就睡眠
(2)方法的参数就表示睡眠的时间,单位为毫秒
(3)当时间到了以后,线程会自动被唤醒,继续执行下面的代码

线程优先级

首先需要了解一下操作系统中的系统调度,调度分为抢占式调度和非抢占式调度,在java虚拟机中采用的是抢占式调度,因此可以给线程设置优先级,优先级越大,抢占到CPU、被执行的概率就越大。
线程的优先级可以设置为1-10,默认为5。
多线程&JUC:多线程的实现和常用成员方法(守护、礼让、插入线程)_第1张图片
注意:

1、可以使用getPriority来获取线程的优先级
2、优先级越高只能代表该线程被执行的概率越大,而不代表一定会被执行

守护线程

守护线程这个名称听起来不是很好理解,可以想象成是一个备胎线程,当其他的非守护线程执行完毕后,守护线程其实根本没有执行的必要了,会陆续的结束。将线程设置为守护线程调用t.setDaemon(true)即可。
这其实还是有应用场景的,比如QQ中的聊天和发送文件可以视为2个线程,当我们把聊天的线程关闭了以后,线程2就没有执行的必要了,就会自动终止。

礼让线程

我们之前设置了两个线程来执行相同的任务,可以发现两个线程确实是交替执行的,但是经常会出现线程1连续输出了好多内容这种情况,出现的原因就是因为线程的执行是有概率性的,而礼让线程表示将当前CPU的执行权出让:

public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(this.getName());
            //出让当前CPU的执行权
            Thread.yield();
        }
    }
}

但这里也只能尽量让结果均衡,但是还是可能会出现那种情况,所以这种方法用的少。

插入线程

观察下面的代码:

MyThread t = new MyThread();
t.setName("土豆");
t.start();
for(int i = 0; i < 10 ; ++i) {
	System.out.println("main线程" + i);
}

最后执行会先执行main线程,毕竟执行的就是main函数。
而使用join方法就可以将其插入到main线程之前,使得其先被执行:

t.join();//把t线程插入到当前线程之前,在这里当前线程就是main线程

这个方法用的也不多,只需要了解一下就好了。

你可能感兴趣的:(多线程&JUC,java,javase,jvm,面试,JUC)