多线程的总结(上)

多线程是什么?

概念: 一个java程序是由多个线程共同组成的,我们在学习多线程之前,主方法就是一个线程(main线程),而垃圾回收机制实质上也是一个线程,我们在写程序的时候垃圾回收机制在后台一直运行着,和主线程并存。多线程是将任务分成多个子任务一同运行,提高了程序的效率。

线程的执行权: 多个线程会一同抢夺CUP的时间片,抢到时间片就会执行任务,抢到时间片的概率大小和线程优先级有关。

线程的状态分为就绪状态,运行状态,阻塞状态:

就绪状态: 和其它线程一起抢夺CUP时间片,抢到了CPU的时间片就执行任务。

运行状态: 抢到时间片之后会进入运行状态,开始执行任务,时间片用完之后会再次回到就绪状态再次抢夺CPU的时间片,再次执行任务时会接着上次的任务继续执行。

阻塞状态: 例如在线程执行的时候接受用户的输入,这个线程就会暂停执行,等待用户输入后会回到就绪状态再次抢夺CPU时间片。

线程的生命周期: 从就绪状态开始一直到运行状态运行完任务,线程就死亡了。

多线程并发: 就是多个线程抢夺时间片执行各自的任务,提高了程序的效率,但这可能是存在线程安全问题。多线程并发存在两种机制:同步机制和异步机制(下一篇博客关于线程安全的问题的时候会讲到)


好了接下来都是干货了,先来一波多线程常用的方法来压压惊。

多线程的常用方法

1. currentThread()方法:
作用:表示当前线程,常与getName()方法连用,Thread.currentThread().getName();表示当前线程的名字
语法:线程对象(引用).currentThread();或者 Thread.currentThread();

2. getName()方法:
作用:获取线程名字,返回值为String类型。
语法:线程对象(引用).getName()

3. setName()方法:
作用:修改线程名字,无返回值
语法:线程对象(引用).setName(“线程名字”);

4. setDaemon()方法:
作用:传入参数true,将普通线程变为守护线程,无返回值。
语法:线程对象(引用).setDaemon(true);

5. setPriority()方法:
作用:修改线程优先级,传入一个整型数字(1到10以内)若不是1到10以内会发生异常,无返回值。
语法:线程对象(引用).setPriority(“整型数值”);

6. start()方法:
作用:启动线程,会自动调用run()方法(实现Callbale接口的线程除外)
语法:线程对象(引用).start();

7. join()方法:
作用:将一个线程加入到另一个线程中,当前线程会进入阻塞状态(直到加入的线程运行结束,当前线程才会继续运行),也可以理解为将两个线程合并。
语法:线程对象(引用).join();

8. stop()方法:
作用:强行使线程结束(不安全,容易造成数据丢失等,让线程停止有别的办法)。
语法:线程对象(引用).stop();

9. sleep()方法:
作用:让当前线程睡眠,传入一个整型参数,睡眠时间与传入的参数有关,参数单位为毫秒。
语法:Thread.sleep(整型参数);或者线程对象(引用).sleep(整型参数);
注意:sleep()方法有编译时异常需要处理。(关于异常可以看我异常概述的博客)。

10. schedule()方法:(这是定时器常用的方法,在此篇博客后面会对定时器进行讲解。)
作用:设置某个时间开始执行某个任务,可以使其每隔特定时间执行一次某个任务(常用于数据的备份)
语法:Timer类对象(引用).schedule(参数1,参数2,参数3);
第一个参数:需要将继承TimerTask类的对象传入,是执行的任务(也就是run()方法中所写的内容)
第二个参数:是开始执行的时间
第三个参数:每隔多久执行一次run()方法中的内容(单位是毫秒)

11. wait()方法:
作用:使在对象上进行的线程进入等待(也就是暂停线程的执行)
语法:对象(引用).wait();
注意:wait()方法并不是Thread类中的方法,是Object类中的方法,所有的java对象都有此方法。(常与线程安全问题有关,下一篇博客我将重点对此进行讲解)

12. notify()和notifyAll()方法:
作用:notify():唤醒在对象上等待的线程(也就是让暂停的线程开始执行),notifyAll():唤醒在对象上等待的所有线程(也就是让暂停的所有线程开始执行)
语法:对象(引用).notify(); 对象(引用).notifyAll();
注意:notify()方法和notifyAll()方法并不是Thread类中的方法,是Object类中的方法,所以的java对象都有此方法(常与线程安全问题有关,下一篇博客我将重点对此进行讲解)

线程优先级:

MIN_PRIORITY:最低优先级,值为1。
MAX_PRIORITY:最高优先级,值为10。
NORM_PRIORITY:默认优先级,值为5。

1.线程优先级为1到10,数字越大,优先级越高,默认线程优先级为5。

2.优先级越高表示抢到执行权的概率越大,优先级为最高优先级时也不并不代表一定会抢到执行权,只是概率大,同理,优先级为最低优先级也不代表一定抢不到执行权。

3.可以调用setPriority()方法对优先级进行修改,也可以调用getPriority()方法获取线程的优先级。

第一种实现线程方法:继承Thread类

步骤:
1. 继承Thread类重写run()方法
2. 创建Thread类型的重写类的对象(多态)
请看代码。

public class PracticeThreadWay01 {
    public static void main(String[] args){
        Thread t=new Thread1();
        t.setName("T线程");
        t.start();
        for (int i = 0; i <20 ; i++) {
            System.out.println(Thread.currentThread().getName()+"线程执行了!--->"+i);
        }
    }
}

class Thread1 extends Thread{
    public void run(){
        for (int i = 0; i <20 ; i++) {
            System.out.println(Thread.currentThread().getName()+"执行了!--->"+i);
        }
    }
}

第二种实现线程方法:实现Runnable接口

步骤:
1. 实现Runnable接口,重写run()方法。
2. 创建Thread对象,将实现Runnable接口的类的对象传入。
请看代码。

public class PracticeThreadWay02 {
    public static void main(String[] args) {
        Thread t=new Thread(new Thread2());
        t.setName("T线程");
        t.start();
        for (int i = 0; i <20 ; i++) {
            System.out.println(Thread.currentThread().getName()+"线程启动了!--->"+i);
        }
    }
}

class Thread2 implements Runnable{
    public void run(){
        for (int i = 0; i <20 ; i++) {
            System.out.println(Thread.currentThread().getName()+"启动了--->"+i);
        }
    }
}

也可以采用匿名内部类的方式实现线程
步骤:
1. 创建Thread对象,在创建对象的时候new Runnable并且实现run()方法。
请看代码。

public class PracticeThreadWay04 {
    public static void main(String[] args) {
        Thread t=new Thread(new Runnable(){
            public void run(){
                for (int i = 0; i <20 ; i++) {
                    System.out.println(Thread.currentThread().getName()+"线程启动了!--->"+i);
                }
            }
            //将线程名字初始化
        },"T线程");

        //启动线程
        t.start();

        for (int i = 0; i <20 ; i++) {
            System.out.println(Thread.currentThread().getName()+"线程启动了!--->"+i);
        }
    }
}

第三种实现线程的方法:实现Callable接口

步骤:
1. 实现Callable接口,重写public Object call()方法,返回值为Object。
2. 创建FutureTask对象,并且将实现Callable接口的类传入。
3. 创建Thread对象,将FutureTask对象传入。

优点: 这种实现线程的方式有返回值,可以通过接收返回值来判断线程是否结束。

缺点: 判断线程是否结束的时候会让其它线程进入阻塞状态,必须得到返回值结果才能进行后续程序(也就是说等待call()方法的结束),会使程序效率降低。

请看代码。

public class PracticeThreadWay03 {
    public static void main(String[] args) throws Exception {

        FutureTask task=new FutureTask(new Thread3());

        Thread t=new Thread(task);

        t.setName("T线程");
        t.start();

        for (int i = 0; i <20 ; i++) {
            //main线程
            System.out.println(Thread.currentThread().getName()+"启动");
        }

        //判断T线程是否结束,必须等待get()方法返回才可以进行后面的程序,也就是说这时候main线程进入了阻塞状态
        //"over".equals()的写法可以避免空指针异常的出现(算是一个小细节)
        if("over".equals(task.get())){
            System.out.println(t.getName()+"结束了");
            System.out.println(Thread.currentThread().getName()+"还在进行中");
        }
        //让主线程睡眠2秒
        Thread.sleep(2000);
    }
}
class Thread3 implements Callable {
    public Object call(){
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+"启动了!");
        }
        return "over";
    }
}

守护线程的实现

1. 守护线程概述: 守护线程是一种非用户线程(例如垃圾回收机制就是守护线程),守护线程在用户线程结束后会自动结束,当程序全是守护线程的时候,程序会自动结束。

2. 用户线程和守护线程的区别:
 (1).主线程结束后用户线程还会继续运行,JVM存活(只要还有一个用户线程没有结束,程序就不会结束,JVM就一直在运行)
 (2).如果用户线程结束了,那么JVM结束,守护线程会自动结束(所有的线程都会结束)。

3. 设置守护线程的办法: 调用setDaemon()方法,传入参数为true时就代表将普通线程(也就是用户线程)设置为了守护线程,传入参数为false时代表设置为用户线程。

4.实现守护线程:
步骤:
(1)创建一个普通线程(线程的run方法为死循环)
(2)调用setDaemon()方法,传入参数true将其修改为守护线程(设置为守护线程的方法,定时器除外)

请看代码。

我这里是采用匿名内部类的方式,也可以采用其它创建线程的方式都可以。

public class PracticeProtectedThread {
    public static void main(String[] args) {
        Thread t=new Thread(new Runnable(){
            public void run(){
                while(true) {
                    System.out.println(Thread.currentThread().getName() + "启动了!--->");
                }
            }
            //设置线程名字
        },"守护线程");

        //将此线程变为守护线程
        t.setDaemon(true);
        t.start();

        for (int i = 0; i <100 ; i++) {
            System.out.println(Thread.currentThread().getName()+"线程*****************************************"+i);
        }
    }
}

定时器的实现

1.定时器概述: 设置特定时间开始执行某个任务,其每隔特定时间执行一次某个任务(常用于数据的备份)

2.定时器的实现步骤:
(1)创建Timer类的对象,可传入参数true(代表将此定时器修改为守护线程),也可以不设置为守护线程。
(2)创建一个类继承TimerTask,并且重写run()方法。
(3)创建一个日期时间作为任务开始的执行时间。
(4)调用Timer对象的schedule()方法,schedule()方法一共有三个参数,schedule()方法中的参数说明如下。
第一个参数:需要将继承TimerTask类的对象传入,是执行的任务(也就是run()方法中所写的内容)
第二个参数:是开始执行的时间(例如:2020-4-16 08:20:00)
第三个参数:每隔多久执行一次run()方法中的内容(单位是毫秒)

请看代码。

public class PracticeTimerThread {
    public static void main(String[] args) throws Exception {
        Timer timer=new Timer("定时器线程",true);
        //创建日期格式
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        //将一定格式的日期转化为时间对象
        //注意一定要与创建的日期格式一样
        Date time=sdf.parse("2020-4-16 22:36:00");
        //设置定时任务,每隔5秒执行一次
        timer.schedule(new TimerThread(),time,1000*5);
        //让主线程睡眠20秒
        Thread.sleep(1000*20);
    }
}
class TimerThread extends TimerTask {
    public void run(){
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String time=sdf.format(new Date());
        System.out.println(time+"   "+Thread.currentThread().getName()+"将数据备份了");
    }
}

这次的博客就先到这里啦,下篇博客我将讲述线程安全问题以及生产者消费者模式


码字不易,不要白嫖,觉得有用的,可以给我点个赞。感谢!
因技术能力有限,如文中有不合理的地方,希望各位大佬指出,在下方评论区留言,谢谢,希望大家一起进步,一起成长。
如需转载请注明来源,谢谢!

发布了5 篇原创文章 · 获赞 13 · 访问量 260

猜你喜欢

转载自blog.csdn.net/weixin_46521681/article/details/105569335