多线程笔记

什么是进程?

// 进程是一个应用程序(1个进程是一个软件)

什么是线程?

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

例如:

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


进程和线程的关系?例如
阿里巴巴:进程
		马云:阿里巴巴的一个线程
		童文红:阿里巴巴的一个线程
京东:进程
		强东:京东的一个线程
		妹妹:京东的一个线程
// 进程可以看做是现实生活当中的公司。
// 线程可以看做是公司当中的某个员工。
进程A和进程B的内存独立不共享。
进程A和进程B的内存独立不共享。(阿里巴巴和京东资源不会共享的!)
		魔兽游戏是一个进程
		酷狗音乐是一个进程
		这两个进程是独立的,不共享资源。
线程A和线程B的堆内存和方法区共享,但栈内存独立
在java语言中:
		线程A和线程B,堆内存和方法区内存共享。
		但是栈内存独立,一个线程一个栈。
假设启动10个线程,会有10个栈空间,每个栈和每个栈之间,
互不干扰,各自执行各自的,这就是多线程并发。
举例
火车站(进程)
	售票窗口(线程)
	我-->窗口1购票(不需要相互等,互补影响)
    你-->窗口2购票(不需要相互等,互补影响)
	目的:多线程并发可以提高程序的处理效率。

假设:使用多线程机制后,main方法结束后,程序有可能不会结束吗?

// main方法结束只是主线程结束了,主栈空了,其它的栈(线程)可能还在压栈弹栈。

实现线程的第一种方式:
编写一个类,直接继承java.lang.Thread,重写run方法。
怎么创建线程对象?
new
怎么创建线程呢?
调用线程对象的start() 方法。

注意:
// 恒古不变的道理:
// 方法体当中的代码永远都是自上而下的顺序依次执行的。

public class ThreadTest02 {
    public static void main(String[] args) {
        // 这里是main方法,这里的代码属于主线程,在主栈中运行。
        // 新建一个分支线程对象
        MyThread myThread = new MyThread();
        // 启动线程
        // myThread.run(); // 不会启动线程,不会分配新的分支栈。
        // start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。
        // 这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开出来,start()方法就结束了。线程就启动成功了。
        // 启动成功的线程会自动调用run方法,并且run方法在分支栈中的栈底部(压栈)
        // run方法在分支栈的栈底部,main方法在主栈的栈底部,run和main是平级的。
        myThread.start();
        // 这里的代码还是运行在主线程中。
        for (int i = 0; i < 1000; i++) {
            System.out.println("主线程--->" + i);
        }
    }
}

class MyThread extends Thread{
    @Override
    public void run() {
        // 编写程序,这段程序运行在分支线程中(分支线)。
        for (int i = 0; i < 1000; i++) {
            System.out.println("分支线程--->" + i);
        }
    }
}
实现线程的第二种方式:
编写一个类,实现java.lang.Runnable接口,实现run方法。
public class ThreadTest03 {
    public static void main(String[] args) {

        Thread t = new Thread(new MyRunnable());

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

class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("分支线程--->" + i);
        }
    }
}

注意:
第二种方式实现接口比较常用,因为一个类实现了接口,它还可以去继承其它的类,更灵活。

采用匿名内部类创建:
public class ThreadTest04 {
    public static void main(String[] args) {

        // 创建线程对象,采用匿名内部类方式。
        // 这是通过一个没有名字的类,new出来的对象。
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    System.out.println("支线程-->" + i);
                }
            }
        });

        thread.start();

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

线程对象的生命周期:

新建状态 --> 就绪状态 – > 运行状态 --> 死亡状态
在这里插入图片描述

阻塞状态。

使用Callable接口创建线程:
// 这种方式的优点:可以获取到线程的执行结果
// 这种方式的缺点:效率比较低,在获取t线程执行结果的时候,当前线程受阻塞,效率较低。
代码演示:
public class Callable {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 第一步:创建一个“未来任务类”对象
        // 参数非常重要,需要给一个Callable接口实现类对象。
        FutureTask task = new FutureTask(new java.util.concurrent.Callable() { // call()方法就相当于run方法,只不过这个有返回值
            // 线程执行一个任务,执行之后可能会有一个执行结果。
            // 模拟执行
            @Override
            public Object call() throws Exception {
                System.out.println("call method begin");
                Thread.sleep(1000 * 10);
                System.out.println("call method end!");
                int a = 100;
                int b = 200;
                return a + b; // 自动装箱Integer
            }
        });

​    Thread t1 = new Thread(task);
​    t1.setName("t1");
​    t1.start();// 在这里是main方法,这是在主线程中// 主线程中怎么获取t线程的返回结果// get() 方法的执行会导致当前线程的阻塞
​    Object obj = task.get();
​    System.out.println(obj);// main方法这里的程序要想执行必须等待get()方法的结束// 而get()方法可能需要很久。因为get()方法是为了拿另一个线程的执行结构// 另一个线程执行是需要时间的。
}

}

怎么获取当前线程对象?
Thread t = Thread.currentThread();
返回值t就是当前对象。
获取线程对象的名字:
String name = 线程对象.getName();
修改线程对象的名字:
线程对象.setName("线程名字");
当线程没有设置名字的时候,默认的名字有什么规律?
Thread-0
Thread-1
Thread-2
Thread-3

public class ThreadTest05 {
    public void doSome(){
        String name = Thread.currentThread().getName();
        System.out.println("----->" + name);
    }

    public static void main(String[] args) {
        ThreadTest05 tt = new ThreadTest05();
        tt.doSome();


        Thread currentThread = Thread.currentThread();
        System.out.println(currentThread.getName());
        // 创建线程对象
        MyThread3 myThread3 = new MyThread3();
        // 设置线程的名字
        myThread3.setName("线程名字:冯旭");
        // 获取线程的名字
        String name = myThread3.getName();

        System.out.println(name); // Thread-0

        MyThread3 t2  = new MyThread3();
        t2.setName("线程名字:新志鹏");
        System.out.println(t2.getName()); // Thread-1
        t2.start();

        // 启动线程
        myThread3.start();
    }
}

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

关于线程的sleep方法:
static void sleep(long millis)

1. 静态方法:Thread.sleep(1000);
2. 参数是毫秒
3. 作用:让当前线程进入休眠,进入“阻塞状态”,就放弃占有cpu时间片,让给其他线程使用。
    这行代码出现在A线程中,A线程就会进入休眠。
    这行代码出现在B线程中,B线程就会进入休眠。
4. Thread.sleep

public class ThreadTest06 {
    public static void main(String[] args) {

        // 让当前线程进入休眠,睡眠5秒
        // 当前线程是主线程!!!
        try{
            Thread.sleep(1000 * 5);
        } catch(InterruptedException e) {
            e.printStackTrace();
        }

        // 5秒之后执行这里的代码
        System.out.println("辛志鹏是个憨批");

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

            // 睡眠1秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

面试题:
public class ThreadTest07 {
    public static void main(String[] args) {
        // 使用多态创建线程对象
        Thread t = new MyThread7();
        t.setName("t");
        t.start();// 调用sleep方法try{// 问题:这行代码会让线程t进入休眠状态吗?
​        t.sleep(1000 * 5); // 5秒// 在执行的时候还是会转换成:Thread.sleep(1000 * 5);// 这行代码的作用是:让当前线程进入休眠,也就是说main线程进入休眠} catch(InterruptedException e) {
​        e.printStackTrace();}

​    System.out.println("Hello world");

}

}

class MyThread7 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + i);
        }
    }
}
sleep睡眠太久了,如何叫醒一个线程?
注意:这个不是中断线程的执行,是终止线程的睡眠。
public class ThreadTest08 {
    public static void main(String[] args) {
        Thread t = new Thread(new MyThread8());
        t.setName("t");
        t.start();

        // 希望5秒之后,t线程醒来(5秒之后主线程手里的活儿干完了)
        try {
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 终端t线程的睡眠(这种终端睡眠的方式依靠了java的异常处理机制)
        t.interrupt(); // 对正在睡眠的线程进行干扰,打断睡眠。

    }
}

class MyThread8 implements Runnable{

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


        // 调用doOther
//        doOther();


    }

    // 其他方法可以throws
    public void doOther() throws Exception{

    }
}

在java中怎么强行终止一个线程的执行。
这种方式存在很大的缺陷,容易丢失数据,因为这种方式是直接将线程杀死了。
线程没有保存的数据将会丢失,不建议使用。
public class ThreadTest09 {
    public static void main(String[] args) {
        Thread t = new Thread(new MyThread9());
        t.setName("t");
        t.start();

        // 模拟5秒
        try {
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 5秒之后强行终止t线程
        t.stop(); // 已过时(不建议使用)
    }
}

class MyThread9 implements Runnable{
    public void run(){
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "--> " + i);
            try{
                Thread.sleep(1000);
            } catch(InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}

合理终止一个线程:
public class ThreadTest10 {
    public static void main(String[] args) {
        MyThread10 m = new MyThread10();
        Thread t = new Thread(m);
        t.setName("t");
        t.start();

        try {
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        m.run = false;

    }
}

class MyThread10 implements Runnable{
    // 打一个布尔标记
    boolean run = true;
    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;
            }
        }
    }
}
关于线程的调度:

1.常见的线程调度模型有哪些?

抢占式调度模型:
那个线程的优先级比较高,抢到的CPU时间片的概率就高一些/多一些。
// java采用的就是抢占式调度模型。
均分式调度模型:
平均分配CPU时间片。每个线程占有的CPU时间片时间长度一样。
平均分配,一切平等。
有一些编程语言,线程调度模型采用的是这种方式。

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

实例方法:
void setPriority(int newPriority) 设置线程的优先级
	int getPriority() 获取线程优先级
	最低优先级1
	默认优先级是5
	最高优先级10
	优先级比较高的获取CPU时间片可能会多一些。(但也不完全是,大概率是多的。)
静态方法:
static void yield()  让位方法
	暂停当前正在执行的线程对象,并执行其他线程
	yield()方法不是阻塞方法。让当前线程让位,让给其它线程使用。
	yield()方法的执行会让当前线程从“运行状态”回到“就绪状态”。
	注意:在回到就绪之后,有可能还会再次抢到。

实例方法:

void join()  
合并线程
	class MyThread1 extends Thread {
	public void doSome(){
	MyThread2 t = new MyThread2();
		t.join(); // 当前线程进入阻塞,t线程执行,直到t线程结束。当前线程才可以继续。
	}
}
	class MyThread2 extends Thread{
			
}

什么时候数据在多线程并发的环境下会存在安全问题呢?

三个条件:
条件1:多线程并发。
条件2:有共享数据。
条件3:共享数据有修改的行为。

// 满足以上3个条件之后,就会存在线程安全问题。
怎么解决线程安全问题呢?
当多线程并发的环境下,有共享数据,并且这个数据还会被修改,此时就存在线程安全问题,怎么解决这个问题?
// 使用“线程同步机制”
线程排队执行。(不能并发)。
	用排队执行解决线程安全问题。
	这种机制被称为:线程同步机制。
专业术语叫做:线程同步,实际上就是线程不能并发了,线程必须排队执行。
线程同步就是线程排队了,线程排队了就会牺牲一部分效率,没办法,数据安全
第一位,只有数据安全了,我们才可以谈效率。数据不安全,没有效率的事儿。

是一上来就选择线程同步吗?synchronized
不是,synchronized会让程序的执行效率降低,用户体验不好。
		系统的用户吞吐量降低。用户体验差。在不得已的情况下再选择
		线程同步机制。
第一种方案:尽量使用局部变量代替“实例变量和静态变量”。

第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样
实例变量的内存就不共享了。(一个线程对应1个对象,100个线程对应100个对象,
对象不共享,就没有数据安全问题了。)

第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候
就只能选择synchronized了。线程同步机制。
两种编程模型:
异步编程模型:
线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,谁也不需要等谁,这种编程模型叫做:异步编程模型。
// 其实就是:多线程并发(效率较高。)
// 异步就是并发。
同步编程模型:
线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行结束,或者说在t2线程执行的时候,必须等待t1线程执行结束,
两个线程之间发生了等待关系,这就是同步编程模型。
// 效率较低。线程排队执行。
// 同步就是排队。

模拟两个线程对同一个账户取款:

存在线程安全的:
public class Account {
    // 账号
    private String actno;
    // 余额
    private double balance;
    Account(){

    }
    Account(String actno, double balance){
        this.actno = actno;
        this.balance = balance;
    }
    public String getActno() {
        return actno;
    }
    public void setActno(String actno) {
        this.actno = actno;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }
    // 取款的方法
    public void withdraw(double money)  {
        // t1和t2并发这个方法。。。(t1和t2是两个栈,两个线程操作堆中同一个对象
        // 取款之前的余额
        double before = this.getBalance(); // 10000
        // 取款之后的余额
        double after = before - money;

        // 在这里模拟一下网络延迟,100%会出现问题
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 更新余额
        // 思考:t1执行到这里了,但还没有来的及执行这行代码,t2线程进来withdraw方法了。此时一定出问题。
        this.setBalance(after);
    }
}
public class AccountThread extends Thread{
    // 两个线程必须共享一个账户对象。
    private Account act;

    // 通过构造方法传递过来账户信息
    public AccountThread(Account act) {
        this.act = act;
    }

    @Override
    public void run(){
        // run方法的执行表示取款操作
        // 取款5000
        double money = 5000;
        // 取款
        // 多线程并发执行这个方法
        act.withdraw(money);
        System.out.println(Thread.currentThread().getName() +"对" + act.getActno() + ",取款成功,余额:" + act.getBalance());

    }


}
public class Test {
    public static void main(String[] args) {
        // 创建一个账户对象(只创建一个)
        Account act = new Account("ACT-0001",10000);
        // 创建两个线程
        Thread t1 = new AccountThread(act);
        Thread t2 = new AccountThread(act);
        // 设置name
        t1.setName("t1");
        t2.setName("t2");
        // 启动线程取款
        t1.start();
        t2.start();
    }
}

解决这个线程安全问题:
public class Account {
    // 账号
    private String actno;

    // 余额
    private double balance;

    Object obj = new Object(); // 实例变量,《Account对象时多线程共享的,Account对象中的实例变量obj也是共享的》


    Account(){

    }

    Account(String actno, double balance){
        this.actno = actno;
        this.balance = balance;
    }

    public String getActno() {
        return actno;
    }

    public void setActno(String actno) {
        this.actno = actno;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    // 取款的方法
    public void withdraw(double money) {
        // 以下这几行代码必须是线程排队的,不能并发
        // 一个线程把这里的代码全部执行结束之后,另一个线程才能开始
        /*
        线程同步机制的语法是:
            synchronized(){
                // 线程同步代码块。
            }
            synchronized后面小括号中传的这个“数据”是相当关键的。
            这个数据必须是多线程共享的数据,才能达到多线程排队。

            ()中写什么?
                那要看你让那些线程同步。
                假设t1、t2、t3、t4、t5 有5个线程
                你只希望t1、t2、t3排队,t4、t5不需要排队,怎么办?
                你一定要()中写一个t1、t2、t3共享的对象,而这个
                对象对于t4、t5来说不是共享的。

            这里的共享对象是:账户对象。
            账户对像是共享的,

            在java对象中,任何一个对象都有“一把锁”,其实这把锁就是标记。(只是把它叫锁)
            100个对象,100把锁,1个对象1把锁

            以下代码的执行原理?
                1.假设t1和t2线程并发,开始执行以下代码的时候,肯定有一个先一个后。
                2.假设t1先执行了,遇到了synchronized,这个时候自动找“后面共享对象”的对象锁,
                找到之后,并占有这把锁,然后执行同步代码块中的程序,在程序执行过程中一直都是占有
                这把锁的的。知道同步代码块结束,这把锁才会释放。
                3.假设t1已结占有这把锁,此时t2也遇到synchronized关键字,也会去占有后面
                共享对象的这把锁,结果这把锁被t1占有,t2只能在同步代码块外面等待t1的结束,
                直到t1把同步代码块执行结束了,t1会归还这把锁,此时t2终于等到这把锁,然后
                t2占有这把锁之后,进入同步代码块执行程序,

                这样就达到了线程排队执行。
                注意:
                    这个共享对象一定要选好了。这个共享对象一定是你需要排队执行的这些线程对象所
                    共享的。
         */
//        synchronized (this){
//        synchronized (obj){
        Object obj2 = new Object();
//        synchronized (obj2){ // 这样编写就不安全了,因为obj2不是共享对象。
//        synchronized ("abc"){ // 在字符串常量池当中
//        synchronized (null){
//        synchronized (this) {
            double before = this.getBalance(); // 10000
            double after = before - money;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.setBalance(after);
        }

    }

synchronized应用在实例方法上:
public class Account {
    // 账号
    private String actno;

    // 余额
    private double balance;

    Account(){

    }

    Account(String actno, double balance){
        this.actno = actno;
        this.balance = balance;
    }

    public String getActno() {
        return actno;
    }

    public void setActno(String actno) {
        this.actno = actno;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

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

        synchronized使用在实例方法上有什么优点?
            代码少了,节简了

        如果共享的对象就是this并且需要同步的代码块是整个方法体,
        建议使用这个方式
     */
    public synchronized void withdraw(double money)  {
        double before = this.getBalance();
        double after = before - money;
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.setBalance(after);
    }
}

总结:
synchronized有三种写法:
	第一种:同步代码块
		灵活
		synchronized(线程共享对象){
			同步代码块;
		}

	第二种:在实例方法上使用synchronized
		表示共享对象一定是this
		并且同步代码块是整个方法体。
	
	第三种:在静态方法上使用synchronized
		表示找类锁。
		类锁永远只有1把。
		就算创建了100个对象,那类锁也只有一把。
	
	对象锁:1个对象1把锁,100个对象100把锁。
	类锁:100个对象,也可能只是1把类锁。

Java中有三大变量?【重要的内容。】:
实例变量:在堆中。

静态变量:在方法区。

局部变量:在栈中。

以上三大变量中:
	局部变量永远都不会存在线程安全问题。
	因为局部变量不共享。(一个线程一个栈。)
	局部变量在栈中。所以局部变量永远都不会共享。

实例变量在堆中,堆只有1个。
静态变量在方法区中,方法区只有1个。
堆和方法区都是多线程共享的,所以可能存在线程安全问题。

局部变量+常量:不会有线程安全问题。
成员变量:可能会有线程安全问题。
如果使用局部变量的话:
// 建议使用:StringBuilder。
因为局部变量不存在线程安全问题。选择StringBuilder。
StringBuffer效率比较低。

ArrayList是非线程安全的。
Vector是线程安全的。
HashMap HashSet是非线程安全的。
Hashtable是线程安全的。
死锁:
/*
死锁代码要会写。
一般面试官要求你会写。
只有会写的,才会在以后的开发中注意这个事儿。
因为死锁很难调试。
 */

public class DeadLock {
    public static void main(String[] args) {
        Object o1 = new Object();
        Object o2 = new Object();// t1和t2两个线程共享o1,o2
​    Thread t1 = new MyThread1(o1,o2);
​    Thread t2 = new MyThread2(o1,o2);

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

}

}

class MyThread1 extends Thread {
    Object o1;
    Object o2;

public MyThread1(Object o1,Object o2) {
    this.o1 = o1;
    this.o2 = o2;
}

public void run(){
    synchronized (o1){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (o2){}}

	}

}

class MyThread2 extends Thread{
    Object o1;
    Object o2;

public MyThread2(Object o1, Object o2){
    this.o1 = o1;
    this.o2 = o2;
}
public void run(){
    synchronized (o2){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (o1){}}
}

}

java语言中线程分为两大类:
// 一类是:用户线程
// 一类是:守护线程(后台线程)
其中具有代表性的就是:垃圾回收线程(守护线程)。
守护线程的特点:
一般守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束。

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

代码演示:
public class ThreadTest14 {
    public static void main(String[] args) {
        Thread t =new BakDataThread();
        t.setName("备份数据的线程");

​    // 启动线程之前,将线程设置为守护线程
​    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 BakDataThread extends Thread{
    public void run(){
        int i = 0;
        while(true){
            System.out.println(Thread.currentThread().getName() + "-->" + (++i));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

定时器的作用:
// 间隔特定的时间,执行特定的程序。
每周要进行银行账户的总账操作。
每天要进行数据的备份操作。
代码演示:
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class TimerTest {
    public static void main(String[] args) throws ParseException {// 创建定时器对象
​    Timer timer = new Timer();// Timer timer = new Timer(true); // 守护线程的方式// 指定定时对象// timer.schedule(定时任务,第一次执行时间,间隔多久执行一次)
​    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
​    Date firestTime = sdf.parse("2021-04-20 14:34:00");// 匿名内部类的方式
​    timer.schedule(new TimerTask(){public void run(){// code....}},firestTime,1000*10);
}

}

// 编写一个任务定时器
// 记录日志的定时任务
class LogTimerTask extends TimerTask{
    public void run(){
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String strTime = sdf.format(new Date());
        System.out.println(strTime + ", 成功完成了一次数据备份");
    }
}

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


生产者与消费者模式:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XBf6cApR-1619232587979)(14wait和notify.assets/007-生产者和消费者模式.png)]

代码演示:
import java.util.ArrayList;
import java.util.List;

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

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

3. wait和notify方法不是线程对象的方法,是普通java对象都有的方法。

4. wait方法和notify方法建立在线程同步的基础之上,因为多线程要同时操作一个仓库,有线程安全问题。

5. wait方法作用:o.wait() 让正在o对象上活动的线程t进入等待状态,并且释放掉t线程之前占有的o对象的锁。

6. notify方法作用:o.notify()让正在o对象上等待的线程唤醒,只是通知,不会释放o对象上之前占有的锁。


 */
public class ThreadTest16 {
    public static void main(String[] args) {
        // 创建一个集合对象,仓库
        List list = new ArrayList();

        // 生产者线程
        Thread t1 = new Thread(new protection(list));
        // 消费者线程
        Thread t2 = new Thread(new Consumer(list));

        t1.setName("生产者线程:");
        t2.setName("消费者线程:");

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


    }
}

// 生产线程
class protection implements Runnable{
    // 仓库
    private List list;

    public protection(List list){
        this.list = list;
    }

    public void run(){
        // 一直生产
        while(true){
            // 给仓库对象list加锁
            synchronized (list){
                if (list.size() > 0){ // 大于0,说明仓库已经有1个元素了。
                    try {
                        // 当前线程进入等待状态,并且释放protection之前占有的list集合的锁
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                // 程序能够执行到这里说明仓库是空的,可以生产
                Object obj = new Object();
                list.add(obj);
                System.out.println(Thread.currentThread().getName() + "-->" + obj);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 唤醒消费者进行消费
                list.notifyAll();
            }

        }
    }
}
// 消费线程
class Consumer implements Runnable{
    // 仓库
    private List list;

    public Consumer(List list) {
        this.list = list;
    }

    public void run(){
        // 一直消费
        while(true){
            synchronized (list){
                if (list.size() == 0){
                    try {
                        // 仓库已经空了
                        // 消费者线程等待,释放的list集合的锁
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                // 程序能够执行到此处说明仓库中有数据进行消费
                Object obj = list.remove(0);
                System.out.println(Thread.currentThread().getName() + "-->" + obj);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 唤醒生产者生产
                list.notifyAll();
            }
        }
    }
}
   System.out.println(Thread.currentThread().getName() + "-->" + obj);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 唤醒消费者进行消费
                list.notifyAll();
            }

        }
    }
}
// 消费线程
class Consumer implements Runnable{
    // 仓库
    private List list;

    public Consumer(List list) {
        this.list = list;
    }

    public void run(){
        // 一直消费
        while(true){
            synchronized (list){
                if (list.size() == 0){
                    try {
                        // 仓库已经空了
                        // 消费者线程等待,释放的list集合的锁
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                // 程序能够执行到此处说明仓库中有数据进行消费
                Object obj = list.remove(0);
                System.out.println(Thread.currentThread().getName() + "-->" + obj);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 唤醒生产者生产
                list.notifyAll();
            }
        }
    }
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值