两万字带你了解Java多线程(详细大总结)

CSDN话题挑战赛第2期
参赛话题:学习笔记

多线程概念

什么是进程?
进程是一个应用程序(1个进程是一个软件)。

什么是线程?
线程是一个进程中的执行场景/执行单元。一个进程可以启动多个线程。

对于java程序来说,当在DOS命令窗口中输入:java HelloWorld回车之后。
会先启动JVM,而JVM就是一个进程。 JVM再启动一个主线程调用main方法。
同时再启动一个垃圾回收线程负责看护,回收垃圾。最起码,现在的java程序中至少有两个线程并发,一个是垃圾回收线程,一个是执行main方法的主线程。

进程可以看做是现实生活当中的公司,线程可以看做是公司当中的某个员工。
注意:进程A和进程B的内存独立不共享。

线程A和线程B呢?
在java语言中:
线程A和线程B,堆内存和方法区内存共享。但是栈内存独立,一个线程一个栈。

假设启动10个线程,会有10个栈空间,每个栈和每个栈之间,互不干扰,各自执行各自的,这就是多线程并发。

什么是真正的多线程并发?
t1线程执行t1的。 t2线程执行t的。
t1不会影响t2,t2也不会影响t1。这叫做真正的多线程并发。

举例
火车站,可以看做是一个进程。
火车站中的每一个售票窗口可以看做是一个线程。
我在窗口1购票,你可以在窗口2购票,你不需要等我,我也不需要等你。所以多线程并发可以提高效率。
java中之所以有多线程机制,目的就是为了提高程序的处理效率。

实现线程的第一种方式

实现线程的第一种方式:
编写一个类,直接继承java.lang.Thread,重写run方法。
怎么创建线程对象?new就行了。
怎么启动线程呢?调用线程对象的start()方法。
注意:亘古不变的道理:
方法体当中的代码永远都是自上而下的顺序依次逐行执行的。

在这里插入图片描述
代码示例

public class 实现线程的第一种方式 {
    
    
    public static void main(String[] args) {
    
    
        // 这里是main方法,这里的代码属于主线程,在主栈中进行
        // 新建一个分支对象
        MyThresd myThresd = new MyThresd();

        //start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成后,瞬间就结束了。
        //这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开出来,start()方法就结束了,线程就启动了。
        //启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部(压栈)。
        //run方法在分支栈的栈底部,main方法在主栈的栈底部。run和main是平级的。
        myThresd.start();

        //这里的代码还是运行在主线程中
        for (int i = 0; i < 1000; i++) {
    
    
            System.out.println("主线程-->"+i);
        }
    }
}
class MyThresd extends Thread{
    
    
    @Override
    public void run() {
    
    
        //编写程序,这段程序运行在分支路线(分支栈)
        for (int i = 0; i < 1000; i++) {
    
    
            System.out.println("分支线程-->"+i);
        }
    }
}

输出结果
在这里插入图片描述

实现线程的第二种方式

第二种方式以接口的方式:编写一个类,实现Runnable接口,里面实现run方法。new一个Thread对象,里面传递的参数是Runnable对象,编写的那个类实现了Runnable,所以直接new这个类的对象传进去。

代码示例

public class 实现线程的第二种方式 {
    
    
    public static void main(String[] args) {
    
    
        //创建一个可运行的对象
        //MyRunnable r = new MyRunnable();
        //将可运行的对象封装成一个线程对象
        //Thread t = new Thread(r);

        //上面两步合并到一起的写法
        //Thread构造方法有一个是传递一个Runnable,MyRunnable实现了Runnable,所以传MyRunnable也ok
        Thread thread = new Thread(new MyRunnable());

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

        for (int i = 0; i < 100; i++) {
    
    
            System.out.println("主线程-->"+i);
        }
        /*//采用匿名内部类的方式
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println("分支线程-->"+i);
                }
            }
        });*/
    }
}

// 这并不是一个线程类,是一个可运行的类。它还不是一个线程。
class MyRunnable implements Runnable {
    
    
    @Override
    public void run() {
    
    
        for (int i = 0; i < 100; i++) {
    
    
            System.out.println("分支线程-->"+i);
        }
    }
}

输出结果
在这里插入图片描述

采用匿名内部类方式

public class 实现线程的第二种方式 {
    
    
    public static void main(String[] args) {
    
    
        
        //采用匿名内部类的方式
        Thread t = new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                for (int i = 0; i < 100; i++) {
    
    
                    System.out.println("分支线程-->"+i);
                }
            }
        });

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

        for (int i = 0; i < 100; i++) {
    
    
            System.out.println("主线程-->"+i);
        }
    }
}

实现线程的第三种方式

实现线程的第三种方式:实现Callable接口。(JDK8新特性。)
这种方式实现的线程可以获取线程的返回值。
之前讲解的那两种方式是无法获取线程返回值的,因为run方法返回void
思考:
系统委派一个线程去执行一个任务,该线程执行完任务之后,可能会有一个执行结果,我们怎么能拿到这个执行结果呢?
使用第三种方式:实现callable接口方式。

实现线程的第三种方式:
实现Callable接口
这种方式的优点:可以获取到线程的执行结果。
这种方式的缺点:效率比较低,在获取t线程执行结果的时候,当前线程受阻塞,效率较低

public class 实现线程的第三种方式 {
    
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        //第一步:创建一个“未来任务类”对象。
        //参数非常重要,需要给一个Callable接口实现类对象。这里使用的是匿名内部类的方式。
        FutureTask task = new FutureTask(new Callable() {
    
    
            @Override
            public Object call() throws Exception {
    
     //call()方法相当于run方法,只不过这个有返回值
                //线程执行一个任务,执行之后可能会有一个执行结果
                //模拟执行
            System.out.println("call method begin!");
            Thread.sleep(1000*5);
            System.out.println("call method end!");
            int a = 100;
            int b = 200;
            return a+b; //自动装箱(300结果变成Integer)
            }
        });

        //创建线程对象
        Thread t = new Thread(task);

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

        //这里是main方法,这里是在主线程中。
        //在主线程中怎么获取t线程的返回结果呢?
        //get()方法的执行会导致“当前线程阻塞”
        Object o = task.get();
        System.out.println("线程执行结果是:"+o);

        //main方法这里的程序想要执行必须要等get方法结束
        //而get方法可能需要很久。因为get()方法是为了拿到另一个线程的执行结果
        //另一个线程执行是需要时间的。
        System.out.println("hello world!");
    }
}


线程的生命周期

关于线程对象的生命周期
新建状态
就绪状态
运行状态
阻塞状态
死亡状态

这个要记住,面试可能会问。

在这里插入图片描述

线程对象的方法

在这里插入图片描述
代码演示

public class 线程对象的方法 {
    
    
    public static void main(String[] args) {
    
    
        //currentThread:获取当前线程对象
        //thread就是当前线程对象
        //这个代码出现在main方法中,所以当前线程就是主线程
        Thread thread = Thread.currentThread();  // 静态方法
        System.out.println(thread.getName());//main

        //创建线程对象
        MyThread2 t1 = new MyThread2();
        //getName:获取线程名字
        String a = t1.getName();
        System.out.println(a);//Thread-0 默认的
        //setName:修改线程名字
        t1.setName("t1");
        System.out.println(t1.getName()); //t1

        MyThread2 t2 = new MyThread2();
        t2.setName("t2");

        t2.start();
        t1.start();
    }
}
class MyThread2 extends Thread{
    
    
    @Override
    public void run() {
    
    
        for (int i = 0; i < 100; i++) {
    
    
            //thread就是当前线程对象。那么当前线程是谁呢?
            //当t1线程执行run方法,那么这个当前线程就是t1
            //当t2线程执行run方法,那么这个当前线程就是t2。  类似于this 谁调用我 我就是谁
            Thread thread = Thread.currentThread();
            System.out.println(thread.getName()+"-->"+i);
        }
    }
}

执行结果

在这里插入图片描述

Sleep方法

在这里插入图片描述
举例

public class sleep方法 {
    
    
    public static void main(String[] args) {
    
    

        //让当前线程进入休眠 失眠五秒
        //这个代码出现在main方法中,当前线程就是主线程!!
        try {
    
    
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }

        //5秒之后执行这里的代码
        System.out.println("hello world!");

        for (int i = 0; i < 10; i++) {
    
    
            System.out.println(Thread.currentThread().getName()+"--->"+i);
            //休眠一秒
            //这个方法还是出现在主线程中,所以主线程受阻
            try {
    
    
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
    }
}

Sleep面试题


public class sleep方法面试题 {
    
    
    public static void main(String[] args) {
    
    
        //创建线程对象
        Thread t = new MyThread3();
        t.setName("t");
        t.start();

        //调用sleep方法
        try {
    
    
            //问题:这行代码会让线程t进入休眠状态吗?
            t.sleep(1000*5);            //在执行的时候还是会转成Thread.sleep(1000*5);
                                                //这行代码的作用是:让当前线程进入休眠。也就是说main'线程进入休眠
                                                //这样代码出现在main方法,main线程睡眠
                                                //5秒后就如就绪状态 抢到执行权 执行hello world
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println("hello World!");

    }

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

        for (int i = 0; i < 10000; i++) {
    
    
            System.out.println(Thread.currentThread().getName()+"-->"+i);
        }
    }
}

终止线程的睡眠

sleep睡眠太久了,如果希望半道上醒来,你应该怎么办呢?也就是说怎么叫醒一个正在睡眠的线程??
注意:这个不是终断线程的执行,是终断线程的睡眠。

/*
sleep睡眠太久 如果希望半道醒来,就要终止睡眠
 */
public class 终止线程的睡眠 {
    
    
    public static void main(String[] args) {
    
    
        Thread t = new Thread(new MyRunnable2());
        t.setName("t");
        t.start();

        //希望五秒之后,t线程醒来(五秒之后主线程手里的话干完了,不用争执行权了)
        try {
    
    
            Thread.sleep(5000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        //终止t线程的睡眠( 这种终端睡眠的方法依靠java的异常处理机制)
        t.interrupt();//干扰 一盆冷水过去
    }
}
class MyRunnable2 implements Runnable{
    
    

    // 重点: run()当中的异常不能throws,只能try catch
    // 因为run()方法在父类中没有抛出任何异常,子类不能比父类抛出更多的异常。
    @Override
    public void run() {
    
    
        System.out.println(Thread.currentThread().getName()+"--->"+"big");
        try {
    
    
            //睡眠一年
            Thread.sleep(1000L *60*60*24*365);
        } catch (InterruptedException e) {
    
    
            //打印异常信息
            e.printStackTrace();
        }
        //一年之后才会执行到这里
        System.out.println(Thread.currentThread().getName()+"--->"+"end");
    }
}

终止一个线程的执行

public class 终止一个线程的执行 {
    
    
    public static void main(String[] args) {
    
    
        MyRunable4 r = new MyRunable4();
        Thread t = new Thread(r);
        t.setName("t");
        t.start();
        //模拟5秒
        try {
    
    
            Thread.sleep(5000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }

        //终止线程
        //你想要什么时候终止t的执行,那么把标记修改为false,就结束了
        r.run =false;
    }
}
class MyRunable4 implements Runnable{
    
    
    //打一个布尔标记
    boolean run = true;

    @Override
    public void run() {
    
    
        for (int i = 0; i < 10; i++) {
    
    
            if (run){
    
    
                System.out.println(Thread.currentThread().getName()+"-->"+i);
                try {
    
    
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }else {
    
    
                //return就结束了
                //结束前可以保存呀
                //save...

                //终止当前线程
                return;
            }
        }
    }
}

线程调度(了解)

常见的线程调度模型有哪些?
抢占式调度模型:
那个线程的优先级比较高,抢到的CPU时间片的概率就高一些/多一些。 java采用的就是抢占式调度模型。

均分式调度模型:
平均分配CPU时间片。每个线程占有的CPU时间片时间长度一样。平均分配,一切平等。有一些编程语言,线程调度模型采用的是这种方式。

java中提供了哪些方法是和线程调度有关系的呢

线程优先级

/*
关于线程的优先级
setPriority(): 设置优先级
getPriority(): 获取当前线程对象优先级
默认优先级是5
最低是1
最高是10
 */
public class 线程优先级 {
    
    
    public static void main(String[] args) {
    
    
        //main线程的默认优先级是:5
        System.out.println(Thread.currentThread().getName()+"线程的默认优先级是:"+Thread.currentThread().getPriority());

        //设置主线程优先级为1
        Thread.currentThread().setPriority(1);

        Thread t = new Thread(new MyRunnable5());
        t.setPriority(10);
        t.setName("t");
        t.start();
        //优先级较高的,只是抢到的CPU时间片多一些。
        //大概率方向更偏向于优先级比较高的。
        for (int i = 0; i < 10000; i++) {
    
    
            System.out.println(Thread.currentThread().getName()+"-->"+i);
        }
    }
}
class MyRunnable5 implements Runnable{
    
    

    @Override
    public void run() {
    
    
        for (int i = 0; i < 10000; i++) {
    
    
            System.out.println(Thread.currentThread().getName()+"-->"+i);
        }
    }
}

线程让位

/*
让位,当前线程暂停,回到就绪状态,让给其它线程。
静态方法:Thread.yield()
 */

public class 线程让位 {
    
    
    public static void main(String[] args) {
    
    
        Thread t = new Thread(new MyRunnable6());
        t.setName("t");
        t.start();
        for (int i = 1; i <= 10000; i++) {
    
    
            System.out.println(Thread.currentThread().getName()+"--->"+i);
        }
    }
}
class MyRunnable6 implements Runnable{
    
    

    @Override
    public void run() {
    
    
        for (int i = 1; i <= 10000; i++) {
    
    
            if (i%100==0){
    
    
                Thread.yield(); //当前线程暂停一下,让给主线程
            }
            System.out.println(Thread.currentThread().getName()+"--->"+i);
        }
    }
}

线程合并

public class 线程合并 {
    
    
    public static void main(String[] args) {
    
    
        System.out.println("main begin");

        Thread t = new Thread(new MyRunnable7());
        t.setName("t");
        t.start();

        //线程合并
        try {
    
    
            t.join(); //t合并到当前线程中,当前线程受阻塞,t线程执行直到结束,当前线程才执行。
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }

        System.out.println("main over");
    }
}
class MyRunnable7 implements Runnable{
    
    

    @Override
    public void run() {
    
    
        for (int i = 0; i < 1000; i++) {
    
    
            System.out.println(Thread.currentThread().getName()+"-->"+i);
        }
    }
}

线程安全(重点)

关于多线程并发环境下,数据的安全问题。
为什么这个是重点?
以后在开发中,我们的项目都是运行在服务器当中,而服务器已经将线程的定义,线程对象的创建,线程的启动等,都已经实现完了。这些代码我们都不需要编写。
最重要的是:你要知道,你编写的程序需要放到一个多线程的环境下运行,你更需要关注的是这些数据在多线程并发的环境下是否是安全的。(重点)

什么时候数据在多线程并发的环境下会存在安全问题呢?
三个条件:
条件1:多线程并发。
条件2:有共享数据。
条件3:共享数据有修改的行为。
满足以上3个条件之后,就会存在线程安全问题。

怎么解决线程安全问题呢?
当多线程并发的环境下,有共享数据,并且这个数据还会被修改,此时就存在线程安全问题,怎么解决这个问题?
线程排队执行。(不能并发)。用排队执行解决线程安全问题。这种机制被称为:线程同步机制。专业术语叫做:线程同步,实际上就是线程不能并发了,线程必须排队执行。

怎么解决线程安全问题?
使用“线程同步机制"。
线程同步就是线程排队了,线程排队了就会牺牲一部分效率,没办法,数据安全第一位,只有数据安全了,我们才可以谈效率。数据不安全,没有效率的事儿。

说到线程同步这块,涉及到这两个专业术语:
异步编程模型:
线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,谁也不需要等谁,这种编程模型叫做:异步编程模型。其实就是:多线程并发(效率较高。)
异步就是并发。
同步编程模型:
线程t1和线程t2,在线程t执行的时候,必须等待t2线程执行结束,或者说在t2线程执行的时候,必须等待t1线程执行结束,两个线程之间发生了等待关系,这就是同步编程模型。效率较低。线程排队执行。
同步就是排队。

Java中有三大变量?【重要的内容。】
实例变量:在堆中。
静态变量:在方法区。
局部变量:在栈中。
以上三大变量中:
局部变量永远都不会存在线程安全问题。
因为局部变量不共享。(一个线程一个栈。)
局部变量在栈中。所以局部变量永远都不会共享。
实例变量在堆中,堆只有1个,
静态变量在方法区中,方法区只有1个。
堆和方法区都是多线程共享的,所以可能存在线程安全问题。
局部变量+常量:不会有线程安全问题。
成员变量:可能会有线程安全问题。

如果使用局部变量的话:
建议使用:StringBuilder.
因为局部变量不存在线程安全问题。选择stringBuildere stringBuffer效率比较低。
ArrayList是非线程安全的。 Vector是线程安全的。
HashMap Hashset是非线程安全的。 Hashtable是线程安全的。

总结:
synchronized有三种写法:
第一种:同步代码块。 灵活
synchronized(线程共享对象){
    
    
	同步代码块;
}
第二种:在实例方法上使用synchronized
表示共享对象一定是this
并且同步代码块是整个方法体。

第三种:在静态方法上使用synchronized
表示找类锁。
类锁永远只有1把。
就算创建了100个对象,那类锁也只有一把,所以这个类new出来的对象都要排队
对象锁:1个对象1把锁,100个对象100把锁。类锁:100个对象,也可能只是1把类锁。

聊一聊,我们以后开发中应该怎么解决线程安全问题?
是一上来就选择线程同步吗?synchronized
不是,synchronized会让程序的执行效率降低,用户体验不好。系统的用户吞吐量降低。用户体验差。在不得已的情况下再选择线程同步机制。
第一种方案:尽量使用局部变量代替"实例变量和静态变量"。
第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样
实例变量的内存就不共享了。(一个线程对应1个对象,100个线程对应100个对象,对象不共享,就没有数据安全问题了。)
第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候就只能选择synchronized了。线程同步机制。

同步代码块synchronized

线程同步机制的语法是:
synchronized(){
    
    
	// 线程同步代码块。
}
synchronized后面小括号中传的这个“数据”是相当关键的。这个数据必须是多线程共享的数据。才能达到多线程排队。
()中写什么?
那要看你想让哪些线程同步。
假设t1、t2、t3、t4、t5,有5个线程,
你只希望t1t2t3排队,t4t5不需要排队。怎么办?你一定要在()中写一个t1t2t3共享的对象。而这个对象对于t4t5来说不是共享的。

在java语言中,任何一个对象都有"一把锁”,其实这把便就是标记。(只是把它叫做锁。100个对象,100把锁。1个对象1把锁。
以下代码的执行原理?
1、假设t1和t2线程并发,开始执行代码的时候,肯定有一个先一个后。
2、线设t1先执行了,遇到了synchronized,这个时候自动找“后面共享对亲”的对象锁,找到之后,并占有这把锁,然后执行同步代码块中的程序,在程序执行过程中一直都是占有这把锁的。直到同步代码块代码结束,这把锁才会释放。
3、假设t1已经占有这把锁,此时t2也遇到synchronized关键字,也会去占有后同共享对象的这把锁,结果这把锁被t1占有,t2只能在同步代码块外面等待的结束,直到t1把同步代码快执行结束了,t1会归还这把锁,此时t2终于等到这把锁,然后 t2占有这把锁之后,进入同步代码块执行程序。
这样就达到了线程排队执行。
这里需要注意的是,这个共享对象一定要选好了。这个共享对象一定是你需要排队执行的这些线程对象所共享的

实例方法上和静态方法上使用synchronized

在实例方法上可以使用synchronized吗?可以的。
synchronized出现在实例方法上,一定锁的是this没得挑。只能是this。不能是其他的对象了。所以这种方式不灵活。
另外还有一个缺点:synchronized出现在实例方法上,表示整个方法体都需要同步,可能会无故扩大同步的
范围,导致程序的执行效率降低。所以这种方式不常用。

synchronized使用在实例方法上有什么优点?
代码写的少了。节俭了。
如果共享的对象就是this,并且需要同步的代码块是整个方法体建议使用这种方式。

synchronized出现在静态方法上是找类锁,因为静态方法是类锁,不管创建几个对象类锁只有一把。

死锁

在这里插入图片描述

死锁代码要会写,一般面试官要求你会写,只有会写的,才会在以后的开发中注意这个事,因为死锁很难调试

实例

public class 死锁 {
    
    
    public static void main(String[] args) {
    
    
        Object a = new Object();
        Object b = new Object();

        //t1 和t2两个线程共享a,b
        Thread t1 = new aa1(a,b);
        Thread t2 = new aa2(a,b);
        t1.start();
        t2.start();
    }
}
class aa1 extends Thread{
    
    
    Object o1;
    Object o2;
    public aa1(Object o1,Object o2){
    
    
        this.o1 = o1;
        this.o2 = o2 ;
    }
    //t1执行时占o1锁,t2执行时占o2锁;
    // t1执行完o1,要执行o1里面的o2,但是o2已经被t2占了;
    // t2执行完o2,要执行o2里面的o1,但是o1已经被t1占了;
    //这就屎机了
    //所以尽量少循环嵌套
    public void run() {
    
    
        synchronized (o1){
    
     //o1嵌套o2
            try {
    
    
                sleep(1000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            synchronized (o2){
    
    
            }
        }
    }
}
class aa2 extends Thread{
    
    
    Object o1;
    Object o2;
    public aa2(Object o1,Object o2){
    
    
        this.o1 = o1;
        this.o2 = o2 ;
    }
    @Override
    public void run() {
    
    
        synchronized (o2){
    
     //o2嵌套o1
            try {
    
    
                sleep(1000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            synchronized (o1){
    
    
            }
        }
    }
}

守护线程

守护线程
java语言中线程分为两大类:
一类是:用户线程
一类是:守护线程(后台线程)
其中具有代表性的就是:垃圾回收线程(守护线程)。

守护线程的特点:
一般守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束。
注意:主线程main方法是一个用户线程。

守护线程用在什么地方呢?
每天00:00的时候系统数据自动备份。
这个需要使用到定时器,并且我们可以将定时器设置为守护线程。一直在那里看着,每到00:00的时候就备份一次。所有的用户线程如果结束了,守护线程自动退出,没有必要进行数据备份了。

实例

/*
所有的用户线程结束,守护线程自动结束
守护守护的意思是:用户线程在,你有必要守护。用户线程没了,你就没必要守护了。
 */
public class 线程守护 {
    
    
    public static void main(String[] args) {
    
    
        Thread t = new mm();

        //启动线程前,将线程设置为守护线程
        t.setDaemon(true);

        t.start();

        //主线程  :主线程是用户线程
        for (int i = 0; i < 10; i++) {
    
    
            System.out.println(Thread.currentThread().getName() +"-->"+i);
            try {
    
    
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }

    }
}
class mm extends Thread{
    
    
    @Override
    public void run() {
    
    
        int i = 0 ;
        //即使是死循环,但由于该线程是守护线程,当用户线程结束,守护线程自动终止。
        while (true){
    
    
            System.out.println(Thread.currentThread().getName() +"-->"+(++i));
            try {
    
    
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
    }
}

定时器

定时器的作用:
间隔特定的时间,执行特定的程序。
每周要进行银行账户的总账操作,每天要进行数据的备份操作…。

在实际的开发中,每隔多久执行一段特定的程序,这种需求是很常见的,那么在java中其实可以采用多种方式实现:
可以使用sleep方法,睡眠,设置睡眠时间,每到这个时间点醒来,执行任务。这种方式是最原始的定时器。(比较low)
在java的类库中已经写好了一个定时器:java.util.Timer,可以直接拿来用。不过,这种方式在目前的开发中也很少用,因为现在有很多高级框架都是支持定时任务的。在实际的开发中,目前使用较多的是Spring框架中提供的SpringTask框架,这个框架只要进行简单的配置,就可以完成定时器的任务。

实例

/*
使用定时器指定任务
 */
public class 定时器 {
    
    
    public static void main(String[] args) throws Exception {
    
    
        // 创建定时器对象
        Timer timer = new Timer();

        //Timer timer = new Timer(true); 守护线程方式
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date parse = sdf.parse("2022-9-19 11:52:30");
        //timer.schedule(定时任务,第一次执行时间,间隔多久执行一次);
        timer.schedule(new LogTimerTask(),parse,1000*5); //也可以使用匿名内部类,直接new抽象类

    }
}
class LogTimerTask extends TimerTask{
    
     //子类继承抽象类,要重写抽象方法

    @Override
    public void run() {
    
    
        //编写需要执行的任务
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String format = sdf.format(new Date());
        System.out.println(format + ": 成功完成一次数据备份");
    }
}

生产者和消费者模式

wait和notify方法

关于object类中的wait和notify方法。(生产者和消费者模式!)
第一:wait和notify方法不是线程对象的方法,是java中任何一个java对象都有的方法,因为这两个方式是Object类中自带的。
wait方法和notify方法不是通过线程对象调用,
不是这样的:t.wait(),也不是这样的:t.notify()…不对。
第二:wait()方法作用?
Objecto=newobject(); o.wait();表示:
让正在o对象上活动的线程进入等待状态,无期限等待,直到被唤醒为止。
o.wait();方法的调用,会让"当前线程(正在o对象上活动的线程)“进入等待状态。
第三:notify()方法作用?
Objecto-newbject(); o.notify();表示:
唤醒正在o对象上等待的线程。
还有一个notifyAll方法:
这个方法是唤醒o对象上处于等待的所有线程。

在这里插入图片描述

使用wait和notify方法实现生产者和消费者模式

什么是“生产者和消费者模式”?
生产线程负责生产,消费线程负责消费。生产线程和消费线程要达到均衡。
这是一种特殊的业务需求,在这种特殊的情况下需要使用wait方法和notify方法。

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/YOU__FEI/article/details/126915376
今日推荐