java多线程

多线程:

1. 介绍:

  1. 什么是进程,什么是线程:

    进程是一个应用程序,线程是一个进程中的执行场景或执行单元,一个进程可以启动多个线程。java中之所以有多线程机制,目的是为了提高程序的处理效率。

  2. 在java语言中,堆内存和方法区内存共享,但是栈内存独立,一个线程一个栈。

  3. 使用了多线程机制之后,main方法结束,程序可能也不会结束。main方法结束只是主线程结束了,主栈空了,其它的栈(线程)可能还在压栈弹栈。

2. 创建线程的方式:

  1. 在java语言中,实现线程一般有两种方式:

    • 第一种方式:

      • 编写一个类,直接继承java.lang.Thread,重写run()方法

      • 创建分线程对象,调用start()方法

      • start()方法作用:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了

      • 启动成功的线程会自动调用run方法,并且run方法在分支栈的底部(压栈)。run方法在分支栈的栈底部,main方法在主线程的栈底部。run和main是平级的

      • //定义线程类
        public class MyThread extends Thread{
            //重写run方法
            public  void run(){
                
            } 
        }
        
        //创建线程对象
        MyThread t=new MyThread();
        //启动线程
        t.start();
        
    • 第二种方式:

      • 编写一个类,实现java.lang.Runnable接口,实现run方法。比较常用。

      • //定义线程类
        public class RunnableTest implements Runnable{
            //重写run方法
            public  void run(){
                
            } 
        }
        
        //创建一个可运行的对象
        RunnableTest rt=new RunnableTest();
        //将一个可运行的对象封装成一个线程对象
        Thread th=new Thread(rt);
        //启动线程
        th.start();
        
    • 采用匿名内部类方式:

      public class ThreadTest04 {
          public static void main(String[] args) {
              //采用匿名内部类方式,创建线程对象
              Thread t=new Thread(new Runnable() {
                  @Override
                  public void run() {
                      for (int i=1;i<101;i++){
                          System.out.println("分支线程-->"+i);
      
                      }
                  }
              });
              //启动线程
              t.start();
              for (int i=1;i<101;i++){
                  System.out.println("main-->"+i);
      
              }
          }
      }
      
      

3. 线程的生命周期图:

java多线程_第1张图片

4. 线程的常见方法:

1. 获取当前线程对象

void setName(String name); //修改线程名字
String getName() ; //获取线程名称,默认为Thread-0
static Thread currentThread(); //返回当前线程对象
  1. 代码演示:

    public class ThreadTest05 {
        public static void main(String[] args) {
            //创建线程对象
            MyThread2 t1=new MyThread2();
           // t.setName("xiaoma");
            //获取线程的名字
            String tName=t1.getName();
            System.out.println(tName);
    
            MyThread2 t2=new MyThread2();
            System.out.println(t2.getName());
            //启动线程
           t1.start();
            System.out.println(Thread.currentThread().getName());
    
    
        }
    }
    
    class MyThread2 extends  Thread{
        @Override
        public void run() {
            for (int i=0;i<100;i++){
                //当t1线程执行run方法,那么当前线程就是t1
                //当t2线程执行run方法,那么当前线程就是t2
                Thread currentThread=Thread.currentThread();
                System.out.println(currentThread.getName()+"---->"+i); //Thread-0--->1
            }
        }
    }
    
    

2 . sleep()方法:

  1. 关于线程的sleep()方法:

    //1. 静态方法: Thread.sleep(1000);
    //2. 参数是毫秒
    //3. 作用:让当前线程进入休眠状态,进入“堵塞状态”,放弃占用CPU时间片,让给其他线程使用
    //4. Thread.sleep()方法,可以做到间隔特定的时间,去执行一段特定的代码,每隔多久执行一次
    static void sleep(long millis);
    
  2. 代码演示:

    /*
    关于线程的sleep()方法
    * */
    public class ThreadTest06 {
        public static void main(String[] args) {
           /* try {
                //让当前线程进入休眠,睡眠5秒
                Thread.sleep(1000 *5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //5秒之后执行这里代码
            System.out.println("xiaoma");*/
    
           for(int i=0;i<10;i++){
               //每隔一秒,输出一次
               System.out.println(Thread.currentThread().getName()+"--->"+i);
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
        }
    }
    

1. 关于sleep()方法的面试题:

  1. 问题:这行代码会让线程t进入休眠状态嘛?

    不会,sleep()是静态方法和t线程没关系,在执行的时候还是会转为Thread.sleep(1000*5)。
    这行代码的作用是:让当前线程进入休眠,也就是说让main线程进入休眠

public class ThreadTest07 {
    public static void main(String[] args) {
        //创建线程对象
        Thread t=new MyThread3();
        t.setName("t");
        t.start();
        //调用sleep()方法
        try {
            //问题:这行代码会让线程t进入休眠状态嘛? 不会
            //sleep()是静态方法和t线程没关系
            // 在执行的时候还是会转为Thread.sleep(1000*5)
            //这行代码的作用是:让当前线程进入休眠,也就是说让main线程进入休眠
            t.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //5秒之后这里才会执行
        System.out.println("hello world");

    }
}

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

3. 唤醒线程 interrupted():

  1. void interrupt() //依靠了java的异常处理机制,终断睡眠,会打印出异常信息
    
  2. 代码演示:

    /*
    sleep睡眠太久了,如何唤醒一个正在睡眠的线程。
    * */
    public class ThreadTest08 {
        public static void main(String[] args) {
            Thread t=new Thread(new MyRunnable2());
            t.setName("t");
            t.start();
    
            //希望5秒之后t线程醒来
            try {
                Thread.sleep(1000*5);
            } 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()+"---> begin");
            try {
                // 睡眠1年
                Thread.sleep(1000*60*60*24*365);
            } catch (InterruptedException e) {
                //打印异常信息
                e.printStackTrace();
            }
            //1年之后才执行这个
            System.out.println(Thread.currentThread().getName()+"---> end");
        }
    }
    
    

4. 终止线程:

1. 第一种方式:

使用stop()方法:已过时,不建议使用,容易丢失数据。因为这种方式是直接将线程杀死,线程没有保存的数据会丢失

  1. 代码演示:

    public class ThreadTest09 {
        public static void main(String[] args) {
            Thread t=new Thread(new MyRunnable3());
            t.setName("t");
            t.start();
    
            //模拟5秒睡眠
            try {
                Thread.sleep(1000*5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            //5秒之后强行终止t线程
            t.stop(); //已过时(不建议使用)
        }
    }
    
    class MyRunnable3 implements Runnable{
        @Override
        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();
                }
            }
        }
    }
    

2. 第二种方式:

在自定义的线程类中打一个boolean标记,想什么时候终止t的执行,就时候把标记修改为false。这种方式是很常用的

  1. 代码演示:

    /*
    如何合理的终止一个线程的执行,这种方式是很常用的
    * */
    public class ThreadTest10 {
        public static void main(String[] args) {
          MyRunnable4 r=  new MyRunnable4();
        Thread t=new Thread(r);
        t.setName("t");
        t.start();
        //模拟5秒
            try {
                Thread.sleep(1000*5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            //终止线程
            //想什么时候终止t的执行,就什么时候把标记修改为false,就行了
            r.run=false;
        }
    }
    
    class MyRunnable4 implements Runnable{
    
        //打一个boolean标记
         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;
                }
    
            }
        }
    }
    
    

5. 线程的调度:

  1. 常见的线程模型

    • 抢占式调度模型:哪个线程的优先级比较高,抢到的CPU时间片的概率就高一些。java采用的就是抢占式调度模型
    • 均分式调度模型:平均分配CPU时间片,每个线程占有的CPU时间片时间长度一样,平均分配,一切平等。
  2. java中提供哪些方法和线程调度有关:

    int getPriority(); //获取线程优先级
    void setPriority(int newPriority); //设置线程优先级
    static void yield() ; // 暂停当前正在执行的线程对象,并执行其他线程。
    void join() ; //合并线程(当前线程进入堵塞,等待调用的线程执行完)
     
    

    注意:线程最低优先级是1,默认优先级是5,最高优先级是10。

    ​ yield()方法不是堵塞方法。让当前线程让位,让给其他线程使用。yield()方法的执行会让当前线程从“运行状态”回到“就绪状 态,有可能再抢到CPU时间片。

  3. join()方法演示:

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

6. 线程安全(重点):

  1. 为什么这个是重点:

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

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

java多线程_第2张图片

存在安全问题的三个条件

  • 多线程并发。
  • 有共享数据。
  • 共享数据有修改的行为。
  1. 如何解决线程安全问题:

    **线程排队执行,不能并发。用排队执行解决线程安全问题,这种机制被称为线程同步机制。**线程同步会牺牲一部分效率,没办法,数据安全第一位,只有数据安全了,我们才可以谈效率。

  2. 说到线程同步这块,就涉及到两个专业术语:

    • 异步编程模型:线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1。谁也不需要等谁,这种模型称为异步编程模型。其实就是多线程并发,效率较高。
    • 同步编程模型:线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行结束,或者说在t2线程执行的时候,必须等待t1线程执行结束,两个线程之间发生了等待关系,这就是同步编程模型,效率低,线程排队执行。

1. 代码演示两个线程对同一个账户取款:

  1. 不使用线程同步机制,多线程对同一个账户取款,出现线程安全问题:
/*
* 银行账户
* */
public class Account {
    //账户
    private String actno;
    //余额
    private BigDecimal balance;

    public Account() {
    }

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

    public String getActno() {
        return actno;
    }

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

    public BigDecimal getBalance() {
        return balance;
    }

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

    @Override
    public String toString() {
        return "Account{" +
                "actno='" + actno + '\'' +
                ", balance=" + balance +
                '}';
    }

    //取款方法
    public  void withdraw(BigDecimal money){
        // t1和t2并发这个方法....(t1和t2两个栈,两个栈操作堆中同一个对象。)
        //取款之前的余额
        BigDecimal before=this.getBalance();
        //取款之后的余额
        BigDecimal after=before.subtract(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;
    }
    public void run(){
        //run方法的执行表示取款操作
        //假设取款5000
        BigDecimal money=new BigDecimal(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-001",new BigDecimal(10000));
        //创建两个线程
        Thread t1=new AccountThread(act);
        Thread t2=new AccountThread(act);

        //设置线程name
        t1.setName("t1");
        t2.setName("t2");

        //启动线程取款
        t1.start();
        t2.start();
    }
}


如图:运行结果出问题,都取款5000,余额还剩5000:

java多线程_第3张图片

2.代码改进(synchronized):

  1. 想不出现上面代码问题,以下这几行代码必须是线程排队的,不能并发,一个线程把这里的代码全部执行结束之后,另一个线程才能进来。

  2. 线程同步机制的语法是:

    synchronized(){
    //线程同步代码块。
    }
    
  3. 注意:synchronized后面小括号中传的这个 “数据”是相当关键的。这个数据必须是多线程共享的数据,才能达到多线程排队。

  4. ()中写什么?假设t1,t2,t3,t4,t5有5个线程,你只希望t1,t2,t3排队,t4,t5不需要排队。怎么办

    那要看你想让哪些线程同步。你一定要在()中写一个t1 t2 t3共享的对象。而这个对象对于t4 t5来说不是共享的。

  5. 这里的共享对象是:账户对象。那么this就是账户对象。

  6. 改进Account类的withdraw()方法,代码如下:

        //取款方法
        public  void withdraw(BigDecimal money){
           //以下这几行代码必须是线程排队的,不能并发
            //一个线程把这里的代码全部执行结束之后,另一个线程才能进来。
            /*
            * 线程同步机制的语法是:
            * synchronized(){
            * //线程同步代码块。
            * }
            *
            * synchronized后面小括号中传的这个 “数据”是相当关键的。
            * 这个数据必须是多线程共享的数据,才能达到多线程排队。
            *
            * ()中写什么?
            * 那要看你想让哪些线程同步。假设t1,t2,t3,t4,t5有5个线程
            * 你只希望t1,t2,t3排队,t4,t5不需要排队。怎么办
            * 你一定要在()中写一个t1 t2 t3共享的对象。而这个对象对于t4 t5来说不是共享的。
            *
            * 这里的共享对象是:账户对象。
            * 账户对象是共享的,那么this就是账户对象。
            * 这里不一定是this,这里只要是多线程共享的那个对象就行。
            * */
            synchronized (this){
                //取款之前的余额
                BigDecimal before=this.getBalance();
                //取款之后的余额
                BigDecimal after=before.subtract(money);
    
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //更新余额
    
                this.setBalance(after);
            }
    
        }
    
  7. synchronized是如何保证线程安全的:

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

  8. 上面代码的执行原理:

    假设t1和t2线程并发,开始执行以下代码的时候,肯定有一个先一个后。若t1先执行了,遇到synchronized,这个时候自动找 "后面共享对象"的对象锁,找到之后,并占有这把锁,然后执行同步代码块中的程序,在程序执行过程中t1一直都是占有这把锁的。t2此时都是在同步代码块外面等待t1的。直到同步代码块代码结束,t1才会归还这把锁。此时t2才会获得这把锁,然后t2占有这把锁之后,才进入同步代码块,执行程序。

    这样就达到了线程排队执行。要注意的是这个共享对象一定要选好,这个共享对象一定是你需要排队执行的这些线程对象所共享的。

  9. 注意:共享对象中的实例变量obj也是共享的。注意不能是局部变量。若写字符串“abc”时(不可变,存在字符串常量池中,共享),可以,但现有线程都会共享。

    如:

    public class Account {
        //账户
        private String actno;
        //余额
        private BigDecimal balance;
    
         //对象
        //实例变量(Account对象是多线程共享的,Account对象中的实例变量obj也是共享的)
        Object obj=new Object();
        
        ....
            
         // synchronized (this){
            synchronized (obj){
                //取款之前的余额
                BigDecimal before=this.getBalance();
                //取款之后的余额
                BigDecimal after=before.subtract(money);
            
            ...
            
    
  10. java中有三大变量:

    • 实例变量:在堆中。
    • 静态变量:在方法区中。
    • 局部变量:在栈中

    以上三大变量中,局部变量永远都不会存在线程安全问题,因为局部变量在栈中,所有局部变量永远都不会共享。(一个线程一个栈)。实例变量在堆中,堆只有一个。静态变量在方法区中,方法区只有一个。堆和方法区都是多线程共享的。

    总结:

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

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

  11. 可以在实例方法上使用synchronized,不常用。

    缺点:

    • synchronized出现在实例方法上,一定锁的是this,不能是其他的对象了,所以这种方式是不灵活的。
    • synchronized出现在实例方法上,表示整个方法体都需要同步,可能会无故扩大同步的范围,导致程序的执行效率降低

    优点:

    • 节俭代码。如果共享的对象就是this,并且需要同步的代码块是整个方法体,建议使用这个方式。
                           ...
         
          //可以在实例方法上使用synchronized,synchronized出现在实例方法上,一定锁的是this
        public synchronized void withdraw(BigDecimal money){
    
            //取款之前的余额
            BigDecimal before=this.getBalance();
            //取款之后的余额
            BigDecimal after=before.subtract(money);
                            .....
    
  12. 如果使用局部变量的话,建议使用StringBuilder。因为局部变量不存在线程安全问题。选择StringBuffer效率比较低。

    另外:ArrayList是非线程安全的,Vector是线程安全的。HashMap,HashSet是非线程安全的,Hashtable是线程安全的。

3.总结:

  1. synchronized的三种写法:

    • 同步代码块:灵活

      synchronized(线程共享对象){
      同步代码块;
      }
      
    • 在实例方法上使用synchronized:需要共享对象一定是this,并且同步代码块是整个方法体。

         public synchronized void withdraw(BigDecimal money){
             同步代码块;
         }
      
    • 在静态方法上使用synchronized:表示找类锁。类锁永远只有一把。就算创建了100个对象,类锁还是一把。保证静态变量安全

4. synchrozied面试题:

以下代码,doOther方法执行的时候需要等待doSome方法结束嘛?

  1. 不需要,因为doOther()方法没有synchronized,t2不需要等待。
/*
* 面试题: doOther方法执行的时候需要等待doSome方法结束嘛?
* 不需要,因为doOther()方法没有synchronized ,t2不需要等待。
* */
public class Exam01 {
    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;
    }
    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");
    }
}

  1. 需要,因为doOther()方法有synchronized ,并且this指向同一个对象 mc,但mc锁被doSome()占用
              ....
                  
              class Myclass{
                public synchronized  void doSome(){
                    ....
                }
                  
                public synchronized  void doSome(){
                    .....
                }  
              }
                .....

3.不需要,因为this指向的不是同一个对象,锁不同。

public class Exam01 {
    public static void main(String[] args) {
        Myclass mc1=new Myclass();
        Myclass mc2=new Myclass();

        Thread t1=new MyThread(mc1);
        Thread t2=new MyThread(mc2);
        ....
    }
}
           ....
                  
class Myclass{
      public synchronized  void doSome(){
                    ....
                }
                  
       public synchronized  void doSome(){
                    .....
                }  
              }
                .....               
               

4.需要,因为synchronized出现在静态方法上是类锁,不管创建几个对象,类锁只有1把。

public class Exam01 {
    public static void main(String[] args) {
        Myclass mc1=new Myclass();
        Myclass mc2=new Myclass();

        Thread t1=new MyThread(mc1);
        Thread t2=new MyThread(mc2);
        ....
    }
}
           ....
                  
class Myclass{
       //synchronized出现在静态方法上是类锁
      public synchronized static void doSome(){
                    ....
                }
                  
       public synchronized static void doSome(){
                    .....
                }  
              }
                .....  

5. 死锁:

  1. 什么是死锁:

    死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。

  2. 代码实现:

    /*
    * 死锁代码要会写
    *
    * */
    public class DeadLock {
        public static void main(String[] args) {
            Object o1=new Object();
            Object o2=new Object();
    
            //t1和t2两个线程共享o1,o2
            Thread t1=new MyThread01(o1,o2);
            Thread t2=new MyThread02(o1,o2);
            t1.start();
            t2.start();
    
        }
    }
    
    
    class MyThread01 extends Thread{
        Object o1;
        Object o2;
        public MyThread01(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 MyThread02 extends Thread{
        Object o1;
        Object o2;
        public MyThread02(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){
    
        }
    }
        }
    }
    
  3. 我们在以后开发中应该怎么解决线程安全问题?

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

7. 守护线程:

  1. 概述:

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

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

  3. 守护一般用在什么地方:如每天00:00的时候,系统数据自动备份。这个需要使用到定时器,并且我们可以将定时器设置为守护线程,一直去监听,每到00:00的时候就备份一次

  4. 如何设置线程为守护线程:

void setDaemon(boolean on)  //boolean为true时,即该线程为守护线程
  1. 守护线程代码演示:线程t是main方法的守护线程,只要main方法一结束,就算线程t是一个死循环,t依旧会结束。
/*
* 守护线程
* */
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();
            }
        }
    }
}

8. 定时器(重要):

  1. 定时器的作用:

    间隔特定的时间,执行特定的程序。比如每周要进行银行账户的走账操作,每天要进行数据的备份操作。在实际的开发中,每隔多久执行一段特定的程序,这种需求很常见。在java中有多种方式实现。

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

    //创建定时器对
    Timer timer=new Timer();
    //指定定时任务
    //timer.schedule(定时任务,第一次执行时间,间隔多久执行一次);
    
    //其中定时任务可写一个类继承TimerTask接口,或者使用匿名内部类的方式
    
  3. 代码如下

/*
* 使用定时器指定定时任务
* */
public class TimerTest {
    public static void main(String[] args) throws ParseException {
        //创建定时器对象
        Timer timer=new Timer();
        //Timer timer1=new Timer("ma",true);以守护线程的方式创建一个叫ma的定时器

        //指定定时任务
        //timer.schedule(定时任务,第一次执行时间,间隔多久执行一次);

        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date firstTime=sdf.parse("2020-07-30 10:40:00");

        //timer.schedule(new LogTimerTask(),firstTime,1000*10);

        //采用匿名内部类的方式
        timer.schedule(new 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+":成功完成一次数据备份");
            }
        },firstTime,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+":成功完成一次数据备份");
    }
}*/

9. 实现线程的第三种方式:

  1. 创建一个FutureTask未来任务类对象,通过在Callable接口的实现类中重写call()方法,来实现多线程。这种实现的线程可以通过**get()**方法获取线程的返回值。之前的两种方法都是无法获取返回值的,因为run方法返回void。

  2. 优点:可以获取线程执行结果。

    缺点:效率比较低,在获取t线程的执行结果的时候,当前线程会受堵塞。

  3. 代码演示:

/*
* 实现线程的第三种方式,实现Callable接口
* */
public class ThreadTest15 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
      //1. 创建一个未来任务类对象。
      //   参数非常重要,需要给一个Callable接口的实现类对象
        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; //自动装箱
            }
        });

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

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

        //在主线程中,如何获取t线程的返回结果。
        //get()方法的执行会导致当前线程阻塞。
        System.out.println("线程执行结果"+task.get());

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

        System.out.println("hello world");
    }
}

10. wait和notify方法:

  1. 概述:wait和notify方法不是线程对象的方法,是java中任何一个java对象都有的方法,因为这两个方法是Object类中自带的。wait方法和notify方法不是通过线程对象调用

  2. 两个方法的作用:

    • wait()方法作用:

      Object o=new Object();
      o.wait();
      //表示 让正在o对象上活动的线程进入无期限等待状态,直到被notify()或notifyAll()唤醒
      
    • notify()方法作用:唤醒正在等待的线程。

1. 生产者和消费者模式:

  1. 分析如图:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hEPE6CFP-1596362088799)(C:\Users\user\Desktop\java分析图\生产者和消费者.png)]

  2. 代码实现:

    /*
    * 使用wait()方法和notify()方法实现生产者和消费者模式
    * 1. 生产线程负责生产,消费线程负责消费,生产线程和消费线程要达到均衡这是一种特殊的业务需求
    * 在这种特殊的情况下,需要使用wait()和notify()方法。
    * 2.  wait和notify不是线程对象的方法,是普通java对象都有的方法。
    * 3.  wait方法和notify方法建立在线程同步的基础之上,因为多线程要同时操作一个仓库,
    * 有线程安全问题。
    * 4.  wait方法作用:o.wait()让正在o对象上活动的线程t进入等待状态,并且释放t对象之前占
    * 有的o对象的锁
    * 5.  notify()作用:o.notify()让正在o对象上等待的线程唤醒,只是通知,不会释放o对象上之前
    * 占用的锁
    *
    * 6.   模拟这样的需求:
    *  仓库我们采用list集合,List集合中假设只能存储1个元素。1个元素就表示仓库满了,
    * 如果List集合中元素个数是0,就表示仓库是空的。保证List集合中是最多存储1个元素。
    * 必须做到这样效果:生产一个,消费一个
    * */
    public class ThreadTest16 {
        public static void main(String[] args) {
            //创建一个仓库对象,共享的
            List list=new ArrayList();
    
            //创建两个线程对象
            //生产者线程
            Thread t1=new Thread(new Producer(list));
            //消费者线程
            Thread t2=new Thread(new Consumer(list));
    
            t1.setName("生产者线程");
            t2.setName("消费者线程");
    
            t1.start();
            t2.start();
    
        }
    }
    
    
    //生产线程
    class Producer implements Runnable{
        //仓库
        private List list;
    
        public Producer(List list){
            this.list=list;
        }
        @Override
        public void run() {
            //一直生产(使用死循环模拟一直生产)
            while (true){
                //表示给仓库对象list加锁
                synchronized (list){
                    if(list.size()>0){ //大于0 说明仓库已经有一个元素了,仓库已满
                        //当前线程进入等待状态,并且释放list仓库的锁
                        try {
                            list.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    //程序能够执行到这里说明仓库是空的,可以生产
                    Object obj=new Object();
                    list.add(obj);
                    System.out.println(Thread.currentThread().getName()+"生产了--->"+obj);
                    //唤醒消费者进行消费。
                    list.notify();
    
                }
    
            }
    
        }
    }
    
    
    // 消费线程
    class Consumer implements Runnable{
        //仓库
        private List list;
    
        public Consumer(List list){
            this.list=list;
        }
        @Override
        public void run() {
            //一直消费
            while (true){
                synchronized (list){
                    if(list.size()==0){ //仓库已经空了
                        try {
                            //仓库空了,消费者线程等待,释放lsit集合的锁
                            list.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
    
                    //程序能够执行到此,说明仓库中有数据进行消费
                    Object obj=list.remove(0);
                    System.out.println(Thread.currentThread().getName()+"消费了 --->"+obj);
                    //唤醒生产者生产
                    list.notify();
                }
            }
        }
    }
    

1. 作业:

  1. 创建两个线程,实现t1输出奇数,t2输出偶数,交替输出:
public class ThreadTest17 {
    public static void main(String[] args) {

        Num num=new Num();

        Thread t1=new Thread(new ji(num));
        Thread t2=new Thread(new ou(num));
        t1.setName("t1");
        t2.setName("t2");
        t1.start();
        t2.start();
    }
}

class Num{
    int i=1;

}

class ji implements Runnable{
private Num num ;
public ji(Num num){
    this.num=num;
}
    @Override
    public void run() {
   while (true){
       synchronized (num){
           if(num.i%2==0){
               try {
                   num.wait();
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
           System.out.println(Thread.currentThread().getName()+"-->"+num.i);
           num.i++;
           num.notifyAll();
       }
   }
    }
}


class ou implements Runnable{
    private Num num;
    public ou(Num num){

        this.num=num;
    }
    @Override
    public void run() {
        while (true){
            synchronized (num){
                if(num.i%2!=0){
                    try {
                        num.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread().getName()+"-->"+num.i);
                num.i++;
                num.notifyAll();
            }
        }
    }
}

你可能感兴趣的:(javaSE,多线程,java,多线程)