Java多线程处理笔记

学习视频:598-JavaSE进阶-多线程概述_哔哩哔哩_bilibili


       目录

多线程概述

进程和线程的关系

 多线程并发的理解

分析程序存在几个线程

 实现线程的第一种方式

实现线程的第二种方式 

 采用匿名内部类的方式

线程生命周期

获取线程的名字

 获取当前线程对象

线程的sleep方法 

sleep方法的相关面试题 

终止线程的睡眠

强制终止线程的执行 

线程调度概述

线程调度的方法

线程让位 

线程合并

线程安全

同步代码块synchronized

哪些变量具有线程安全问题 

synchronized出现在实例方法上

synchronized的三种写法

synchronized相关面试题

 死锁概述

开发中如何解决线程安全问题

守护线程概述

      实现守护线程

实现定时器

实现线程的第三种方式 :实现Callable接口(JDK8新特性)


多线程概述

1.什么是线程? 什么是进程?

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

线程是一个进程中的执行场景/执行单元

一个进程可以启动多个线程

2.对于java程序来说,当在DOS命令窗口中输入:

java HelloWorld 回车之后

会先启动JVM,而JVM就是一个进程

JVM再启动一个主线程调用main方法

同时再启动一个垃圾回收线程负责看护,回收垃圾.

最起码,现在的java程序中至少两个线程并发。

一个是垃圾回收线程,一个是执行main方法的主线程.


进程和线程的关系

进程可以看做是现实生活当中的公司,线程可以看做是公司当中的某个员工。

        注意:

                进程A和B的内存独立不共享.

                        永劫无间是一个进程

                        原神是一个进程        

                             这两个进程是独立的,不共享资源

                线程A和B:

                        在java语言中,线程A和线程B,堆内存和方法区内存共享.

                        但是栈内存独立,一个线程一个栈

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

        java中之所以有多线程机制,目的就是为了提高程序的处理效率.


 多线程并发的理解

什么是真正的多线程并发?

        t1线程执行t1的

        t2线程执行t2的        

        t1不会影响t2,t2也不会影响t1,这叫做真正的多线程并发.

对于多核的CPU电脑来说,真正的多线程并发是没问题的.

        4核CPU表示同一个时间点上,可以真正的有4个进程并发执行

单核的CPU表示只有一个大脑:

        不能够做到真正的多线程并发,但是可以做到给人一种"多线程并发”的感觉.

对于单核的CPU来说,在某一个时间点上实际上只能处理一件事情,但是由于CPU的处理速度极快,

多个线程直接频繁切换执行,会给人一种错觉:貌似多个事情同时在做


分析程序存在几个线程

package thread1;

public class ThreadTest01 {
    public static void main(String[] args) {
        System.out.println("main begin");
        m1();
        System.out.println("main over");


    }

    private static void m1() {
        System.out.println("m1 begin");
        m2();
        System.out.println("m1 over");
    }
    private static void m2() {
        System.out.println("m2 begin");
        m3();
        System.out.println("m2 over");
    }

    private static void m3() {
        System.out.println("m3 execute!");
    }
}


 实现线程的第一种方式

java支持多线程机制,并且java已经将多线程实现了,我们只需要继承就行了.

第一种方式:编写一个类,直接继承java.lang.Thread,重写run方法

package thread1;
/*
    实现线程的第一种方式:
        编写一个类,直接继承java.lang.Thread,重写run方法.
 */
public class ThreadTest02 {
    public static void main(String[] args) {
    //新建一个分支线程对象
        MyThread myThread=new MyThread();
        //启动线程
        //t.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);
        }
    }
}


实现线程的第二种方式 

package thread1;
/*
    实现线程的第二种方式,编写一个类实现java.lang.Runnable接口
 */
public class ThreadTest03 {
    public static void main(String[] args) {
//    //创建一个可运行对象
//        MyRunnable r=new MyRunnable();
//    //将可运行的对象封装成一个线程对象
//        Thread t=new Thread(r);
       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);
        }
    }
}

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


 采用匿名内部类的方式

package thread1;

public class ThreadTest04 {
    public static void main(String[] args) {
        //创建线程对象,采用匿名内部类的形式
        //这是通过一个没有名字的类,new 出来的对象
        Thread t=new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=0;i<1000;i++)
                {
                    System.out.println("t线程--->"+i);
                }
            }
        });
    //启动线程
        t.start();
        for(int i=0;i<1000;i++)
        {
            System.out.println("main线程--->"+i);
        }
    }
}


线程生命周期

        新建状态

        就绪状态

        运行状态

        阻塞状态

        死亡状态


获取线程的名字

 

package thread1;
/*
    1.怎么获取当前线程对象?
    2.获取线程对象的名字
    3.修改线程对象的名字
 */
public class ThreadTest05 {
    public static void main(String[] args) {
        //创建线程对象
        MyThread2 t=new MyThread2();
        //设置线程的名字
//        t.setName("tt");
        //获取线程的名字
      String tname=t.getName();
        System.out.println(tname);//tt

        MyThread2 t2=new MyThread2();
        System.out.println(t2.getName());
        //启动线程
        t.start();

    }
}
class  MyThread2 extends Thread
{
    @Override
    public void run() {
        for (int i=0;i<100;i++)
        {
            System.out.println("分支线程--->"+i);
        }
    }
}


 获取当前线程对象

package thread1;
/*
    1.怎么获取当前线程对象?
    2.获取线程对象的名字
    3.修改线程对象的名字
 */
public class ThreadTest05 {
    public static void main(String[] args) {
        //currentThread就是当前线程对象
        //这个代码出现在main方法当中,所以当前线程就是主线程
        Thread currentThread=Thread.currentThread();
        System.out.println(currentThread);

        //创建线程对象
        MyThread2 t=new MyThread2();
        //设置线程的名字
//        t.setName("tt");
        //获取线程的名字
      String tname=t.getName();
        System.out.println(tname);//tt

        MyThread2 t2=new MyThread2();
        System.out.println(t2.getName());
        //启动线程
        t.start();
        t2.start();
    }
}
class  MyThread2 extends Thread
{
    @Override
    public void run() {
        for (int i=0;i<100;i++)
        {
            //currentThread就是当前线程对象,就是t.start()的t
            Thread currentThread=Thread.currentThread();
            System.out.println(currentThread.getName()+"--->"+i);
        }
    }
}


线程的sleep方法 

package thread1;
/*
关于线程的sleep()方法:
    static void sleep(long millis)
    1.静态方法:Thread.sleep(1000);
    2.参数是毫秒
    3.作用:让当前线程进入休眠,进入"阻塞状态",放弃占有CPU时间片,让给其它线程使用。
        出现在A线程中,A线程就会进入休眠
 */
public class ThreadTest06 {
    public static void main(String[] args) {
    //让当前线程进入休眠5s
        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //5秒之后执行这里的代码
        System.out.println("hello,world!");
    }
}

sleep方法的相关面试题 

package thread1;

public class ThreadTest07 {
    public static void main(String[] args) {
    //创建线程对象
        Mythread3 t=new Mythread3();
        t.setName("t");
        t.start();

        //调用sleep方法
        try {
            // 问题 :这行代码会让线程t进入休眠状态吗?
            t.sleep(1000*5);//在执行的时候还是会转换成:Thread.sleep(1000*5)
                                 //这行代码的作用是:让当前线程进入休眠,也就是说main线程进入休眠
                                 // 这行代码出现在main方法中,main线程睡眠

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //5秒之后这里才会执行
        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);
        }
    }
}

终止线程的睡眠

package thread1;
/*
    sleep睡眠太久了,如果希望半道上醒来,你应该怎么办?也就是说怎么终止/叫醒一个正在睡眠的线程
    注意:这个不是中断线程的执行,而是中断线程的睡眠
 */
public class ThreadTest08 {
    public static void main(String[] args) {
    Thread t=new Thread(new MyRunnable2());
    t.setName("t");
    t.start();

    //希望5s之后,t线程醒来
        try {
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
            //打印异常信息
            e.printStackTrace();
        }
    // 中断t线程的睡眠 (中断睡眠的方式依靠了java的异常处理机制)
        t.interrupt();// 干扰,一盆冷水过去!


    }
}
class  MyRunnable2 implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"----> begin");
        try {
            //睡眠一年
            Thread.sleep(1000*60*60*24*365);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"----> end");
    }


}


强制终止线程的执行 

package thread1;

public class ThreadTest10 {
    public static void main(String[] args) {
        MyRunable4 r=new MyRunable4();
    Thread t=new Thread(r);
    t.setName("t");
    t.start();
    //模拟5s
        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    //终止线程
    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;
        }
    }

    }
}


线程调度概述

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

        抢占式调度模型:

                那个线程的优先级比较高,抢到的CPU时间片的概率就高一些/多一些。

                java采用的就是抢占式调度模型。        

        均分式调度模型:

                平均分配CPU时间片,每个线程占有的CPU时间片时间长度一样。平均分配,

             一切平等。

                有一些编程语言,线程调度模型采用的是这种方式。


线程调度的方法

实例方法:

        void setPriority(int newPriority)  设置线程的优先级

        int getPriority()  获取线程的优先级

        最低优先级1

        默认优先级5

        最高优先级10

静态方法:

        static void yield() 让位方法

        暂停当前正在执行的线程对象,并执行其他线程

        yield()方法不是阻塞方法,让当前线程让位,让给其他线程使用.

        yield()方法的执行会让当前线程从"运行状态"回到"就绪状态"

实例方法:

        void join()

        合并线程

        class MyThread1  extends Thread{

        public void doSome()

{

        MyThread2 t=new MtThread2();

        t.join();  // 当前线程进入阻塞,t线程执行,直到t线程结束,当前线程才可以执行

}

}

class MyThread2 extends Thread{

}

package thread1;
/*
关于线程的优先级
 */
public class ThreadTest11 {
    public static void main(String[] args) {
        System.out.println("最高优先级 "+Thread.MAX_PRIORITY);
        System.out.println("最低优先级 "+Thread.MIN_PRIORITY);
        System.out.println("默认优先级 "+Thread.NORM_PRIORITY);

        //获取当前线程对象,获取当前线程的优先级
        Thread currentThread=Thread.currentThread();
        //main默认优先级:5
//        System.out.println(currentThread.getName()+"线程的默认优先级是 :"+currentThread.getPriority());
        Thread t=new Thread(new MyRunnable5());
        t.setPriority(10);
        t.setName("t");
        t.start();
        //优先级较高的,抢到CPU的时间相对多一些(处于运行状态占的时间多一些)
        for (int i=0;i<1000;i++)
        {
            System.out.println(Thread.currentThread().getName()+"--->"+i);
        }


    }
}

class  MyRunnable5 implements  Runnable{

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

    }
}


线程让位 

package thread1;
/*
让位,当前线程暂停,回到就绪状态,让给其他线程
静态方法:Thread.yield();
 */
public class ThreadTest12 {
    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);
        }
    }
}


线程合并

package thread1;
/*
线程合并:
 */
public class ThreadTest13 {
    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<100;i++)
        {
            System.out.println(Thread.currentThread().getName()+"--->"+i);
        }
    }
}

 在内存上线程合并并不是意味着两个栈合并了,而是协调关系。

当前线程阻塞,t线程执行,t线程执行完了当前线程才可以执行.


线程安全

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

        条件1:多线程并发

        条件2:   有共享数据

        条件3:   共享数据有修改行为

满足上述三个条件后,就会存在线程安全问题

怎么解决线程安全问题?

        线程排队执行(不能并发)(线程同步机制)

线程同步:线程不能并发了,需要排队执行,尽管排队执行会牺牲一部分效率,但是安全第一

异步编程模型:

        线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,谁也不需要等谁,这种编程模型叫做:异步编程模型。其实就是:多线程并发(效率较高)

异步--并发

同步编程模型:

        线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行结束,或者说在t2线程执行的时候,       必须等待t1线程执行结束,两个线程之间发生了等待关系,这就是同步编程模型.效率较低,线程排队

执行。

同步--排队


Account类

package bean;


import static java.lang.Thread.sleep;

/*
银行账户
 */
public class Account {
    private String  actno;//账号

    private  double balance;//余额

    public Account() {
    }

    public 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();
        //取款之后的余额
        double after=before-money;
        try {
            sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //更新余额
        this.setBalance(after);

    }

}

 AccountThread类
 

package bean;

public class AccountThread extends  Thread{
    //两个线程必须共享同一个账户对象
    private Account act;

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

    public void run()
    {
        // run方法的执行表示取款操作
        //假设取款5000
        double money=5000;
        //取款
        act.withdraw(money);
        System.out.println(Thread.currentThread().getName()+"对账户"+act.getActno()+"取款成功,余额"+act.getBalance());
    }

}

测试类

package bean;

public class ThreadTest14 {
    public static void main(String[] args) {
        // 创建账户对象(只创建1个)
        Account act=new Account("act-01",10000);
        // 创建2个线程
        Thread t1=new AccountThread(act);
        Thread t2=new AccountThread(act);
        t1.setName("t1");
        t2.setName("t2");
        t1.start();
        t2.start();


    }
}

存在问题,2个人都取5000,余额应该为0;


同步代码块synchronized

 

import static java.lang.Thread.sleep;

/*
银行账户
 */
public class Account {
    private String  actno;//账号

    private  double balance;//余额

    public Account() {
    }

    public 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 来说不是共享的。

            这里的共享对象是: 账户对象
            账户对象是共享的,那么this 就是账户对象吧!!!
            不一定是this,这里这要是多线程共享的那个对象就行.

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

         */
        synchronized (this) {
            double before = this.getBalance();
            //取款之后的余额
            double after = before - money;
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //更新余额
            this.setBalance(after);
        }

    }

}

synchronized()括号里面只要是t1和t2共享的对象就行,比如 括号里填obj也可以

当然,若是填"abc",则所有的对象都共享了,一个人取款,要让全天下的人都等待,应该让机制变成只有对应账户A的人取款才需要等待正在取款账户A的人操作完毕再取款。


哪些变量具有线程安全问题 

 java中有三大变量

实例变量:在堆中

静态变量: 在方法区

局部变量: 在栈中

以上三大变量中:                

        局部变量永远都不会存在线程安全问题

        因为局部变量不共享(一个线程一个栈)

 实例变量在堆中,堆只有一个.

静态变量在方法区,方法区只有1个。

堆和方法区都是多线程共享的,所以可能存在线程安全问题。

局部变量+常量:不会有线程安全问题。

成员变量:可能会有线程安全问题。

如果使用局部变量的话,建议使用:StringBuilder.

因为局部变量不存在线程安全问题,选择StringBuilder,StringBuffer效率比较低。        


synchronized出现在实例方法上

这个时候synchronized锁的一定是this 

如果共享的对象是this,并且需要同步的代码块是整个方法体,

建议使用这种方式


synchronized的三种写法

第一种:同步代码块

        灵活

        synchronized(线程共享对象)

        {

        同步代码块;

        }

第二种:在实例方法上使用synchronized

        表示共享对象一定是this

        并且同步代码块是整个方在法体

第三种:在静态方法上使用synchronized

        表示找类锁

        类锁永远只有一把

        就算创建了100个对象,类锁也只有一把。

对象锁:1个对象一把


synchronized相关面试题

package bean;
/*
doOther()方法的执行需要等待doSome()方法的结束吗?
    不需要,它不是synchronized修饰的
 */
public class Exaxm01 {
    public static void main(String[] args) {
    Myclass mc=new Myclass();
    Thread t1=new MyThread(mc);
    Thread t2=new MyThread(mc);
    t1.setName("t1");
    t2.setName("t2");

    t1.start();
        try {
            Thread.sleep(1000);//这个睡眠的作用是为了保证t1先执行
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.start();



    }
}
class MyThread extends Thread{
    private Myclass mc;
    public MyThread(Myclass mc)
    {
        this.mc=mc;
    }

    @Override
    public void run() {
    if (Thread.currentThread().getName().equals("t1"))
    {
        mc.doSome();
    }
    if (Thread.currentThread().getName().equals("t2"))
    {
        mc.doOther();
    }

    }
}

class Myclass{
    public synchronized void doSome()
    {
        System.out.println("doSome begin");
        try {
            Thread.sleep(1000*10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("doSome over");

    }
    public void doOther()
    {
        System.out.println("doOther begin");
        System.out.println("doOther over");
    }
}

doOther()方法前面加上 synchronized  需要等待

 new两个Myclass对象(两把锁)不需要等待

 synchronizedc搞静态方法(类锁)需要等待

 


 死锁概述

 

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;
    }

    @Override
    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  ;
    }

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

            }
        }
    }
}

发生死锁,10年后还是这样


开发中如何解决线程安全问题

一上来就用synchronized让线程同步吗?

     不是,synchronized会让程序的执行效率降低,用户体验不好,系统的用户吞吐量降低,用户体验差,在不得已的情况下再选择线程同步机制。

第一种方案:尽量使用 局部变量 代替 实例变量 静态变量

第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了.

(一个线程对应1个对象,100个线程对应100个对象,对象不共享,就没有数据安全问题了)

第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候只能选择synchronized,线程同步机制。


守护线程概述

        java语言中线程分为两大类:

        一类是:用户线程

        一类是:守护线程(后台线程)

其中具有代表性的就是:垃圾回收线程(守护线程)

        守护线程的特点:

                一般守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束。

注意:主线程main方法是一个用户线程.

        守护线程用在什么地方?

                每天00:00的时候系统数据自动备份

                这个需要使用到定时器,并且我们可以将定时器设置为守护线程

                一直在那里看着,每次到00:00的时候就备份一次。所有的用户线程如果结束了,守护线程自动退出,没有必要进行数据备份了。

      实现守护线程

package bean;

public class ThreadTest15 {
    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{
    @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();
            }
        }
    }
}

 主线程结束后,备份数据的线程也跟着结束


实现定时器

定时器的作用:间隔特定的时间,执行特定的程序。


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 Exception{
        //创建定时器对象

        Timer timer=new Timer();
        //Timer timer=new Timer(true);//守护线程方式

        //指定定时任务
        //timer.schedule(定时任务,第一次执行时间,间隔多久执行一次)
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
      Date firstime=sdf.parse("2022-12-10 00:16:00");
        timer.schedule(new logTimerTask(),firstime,1000*10);


    }
}
class  logTimerTask extends TimerTask{
    @Override
    public void run() {
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String strtime=sdf.format(new Date());
        System.out.println(strtime+":成功完成了一次数据备份 ");

    }
}


实现线程的第三种方式 :实现Callable接口(JDK8新特性)

前面两种方式是无法获取线程返回值的,因为run()方法返回void。

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class ThreadTest16 {
    public static void main(String[] args) throws Exception{
    //第一步:创建一个"未来任务类"对象
        FutureTask task=new FutureTask(new Callable() {
            @Override
            public Object call() throws Exception {//call()方法相当于run()方法.只不过这个有返回值
                // 线程执行一个任务,执行之后可能会有一个执行结果
                // 模拟执行
                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;//自动装箱(300变成Integer)
            }
        });
        //创建线程对象
    Thread t=new Thread(task);

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

        //怎么在主线程中获取t线程的返回结果
        Object obj=task.get();
        System.out.println("线程执行结果:"+obj);

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


    }
}

 还是两个栈,java虚拟机调度

这种方式的优点:可以获取线程的执行结果

这种方式的缺点:   效率比较低,在获取t线程执行结果的时候,当前线程受阻塞,效率较低.


以上是今天的视频笔记。学习如逆水行舟,不进则退

  • 6
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小吴有想法

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

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

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

打赏作者

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

抵扣说明:

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

余额充值