三、Thread 类和Runnable 接口详解




一、Thread 类

        了解如何使用Thread 类实现多线程之后,继续学习Thread 类实现多线程之后的相关功能及方法。其中的一些方法是 static 的,由类名直接调用,通常都是在那个线程中执行,这些静态的方法就指定的那个线程。



1、操作线程名称的方法

  • 构造方法(实现 Runnable 接口时候使用)
    • public Thread(Runnable target,String name); 创建线程时设置线程名称。
  • 成员方法
    • public final void setName(String name); 设置线程的名称。
    • public final String getName(); 获取线程的名称。

  • Demo 代码示例:

    public class TestThread extends Thread{
    
    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            System.out.println("我正在编写多线程代码"+ i);
        }
    }
    
    //程序主线程 main 线程
    public static void main(String[] args) {
    
        //创建子类对象
        TestThread thread = new TestThread();
    
        // 设置线程名称
        thread.setName("姚青新创建的线程");
        
        //调用 start() 方法开启线程
        thread.start();
    
        for (int i = 1; i <= 10; i++) {
            System.out.println("我正在学习多线程"+ i);
        }
    
    	// 获取线程名称
        System.out.println(thread.getName());
       }
    }
    

运行结果:
在这里插入图片描述




2、获取当前正在执行的线程对象:currentThread()

  • public static Thread currentThread(); 返回当前正在执行的线程对象

    • 获取当前线程对象
      • Thread.currentThread();
    • 获取当前线程对象名称
      • Thread.currentThread().getName();
  • Demo代码示例:

     public class TestThread extends Thread{
    
         @Override
         public void run() {
    
            // 获取start()方法创建出来的线程对象
            System.out.println("start() 创建的线程对象:"+Thread.currentThread());
    
            // 获取start()方法创建出来的线程对象名称
            System.out.println("start() 创建的线程对象名称:"+Thread.currentThread().getName());
        }
    
         public static void main(String[] args) {
            TestThread thread = new TestThread();
            thread.setName("姚青新创建的线程");
            thread.start();
    
            //获取 主线程对象
            System.out.println("主线程对象:"+Thread.currentThread());
    
            // 获取main()主线程对象名称
            System.out.println("主线程对象名称"+Thread.currentThread().getName());
        }
    }
    

运行结果:
在这里插入图片描述

         在使用这个方法的时候需要注意一点,该方法固定的写法就是 Thread.currentThread(); 放在那个线程中执行这个方法就是指定的那个线程。
         这个写法主要作用就是获取当前线程的线程对象,获取线程对象之后还可以继续对该线程对象的一些状态进行操作。



3、线程休眠 :sleep()

  • public static void sleep(long millis); 根据传入的时间参数让当前线程休眠
    • 休眠以毫秒为单位:millis

Demo代码示例

public class TestThread extends Thread{

    @Override
    public void run() {
    
        System.out.println("当前线程名称:"+Thread.currentThread().getName());
        
        try {
            // 将线程休眠五秒
            Thread.sleep(5000);
            System.out.println("将线程休眠五秒");
            
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread()+":"+i);
            }
            
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    public static void main(String[] args) {
        TestThread thread = new TestThread();
        thread.setName("姚青创建的新线程");
        thread.start();
        
        System.out.println("当前线程名称:"+Thread.currentThread().getName());
    }
}

运行结果:被指定的线程休眠了五秒,五秒后恢复运行
在这里插入图片描述




1、Object 类的 wait(), notify(), notifyAll() 等方法介绍

线程休眠的功能 Object 类的 wait(), notify(), notifyAll() 等方法也同意具备。
  • public final void wait(): 让当前线程进入休眠状态,同时,wait()也会让当前线程释放它所持有的锁。当其他线程调用此对象的notify()方法或 notifyAll() 方法,当前线程就会被唤醒(进入“就绪状态”)。

  • public final native void wait(long timeout): 让当前线程处于休眠状态”,并且设置一个唤醒时间,当其他线程调用此对象的notify()方法或 notifyAll() 方法,或者超过指定的时间,当前线程就会被唤醒(进入“就绪状态”)。

  • public final native void notify(): 唤醒当前正在休眠的线程,如果等待池中有多条线程的话就随机唤醒其中的一个线程;

  • public final native void notifyAll(): 唤醒等待池中所有的线程

wait(),notify(),notifyAll()必须在同步(Synchronized)方法/代码块中调用。因为wait()被唤醒时是需要释放锁的,但是如果没加锁就无法释放锁了,所有需要在同步代码块中先获取到锁在释放锁。
  • sleep()和wait()方法的区别?
    • sleep():必须指时间;不释放锁。
    • wait():可以不指定时间,也可以指定时间;释放锁。




4、线程中断状态控制

  • public final void stop(); 中断线程
    • 使用 stop() 后该线程就停止了,不再继续执行,并且线程无法恢复,可能会造成线程不安全问题,不建议使用
  • public void interrupt(); 中断线程
    • 使用 interrupt() 会终止线程的状态,还会继续执行run方法里面的代码,且线程可以恢复,较为安全。线程最安全的终止状态是让程序运行完,线程自己停下来
  • public boolean isInterrupted(); 用来判断当前线程是否为中断状态
    • 如果当前线程是中断状态就返回 true ,不是中断状态返回 false
  • public static boolean interrupted(); 用来恢复线程的中断状态
    • 检验当前线程是否是中断状态,如果是中断状态,返回 true ,并将线程重新恢复成流通状态。如果不是中断状态,返回 false,直接退出方法。

Demo 代码示例:

 public class TestThread extends Thread{

    @Override
    public void run() {
    
        System.out.println("当前线程名称:"+Thread.currentThread().getName());

        try {
            // 将线程休眠五秒
            Thread.sleep(5000);
            System.out.println("将线程休眠五秒");
        } catch (InterruptedException e) {
            // 如果线程休眠出现异常,就将线程中断
            Thread.currentThread().interrupt();
        }
    }

    public static void main(String[] args) {
        TestThread thread = new TestThread();
        thread.start();

        System.out.println("当前线程名称:"+Thread.currentThread().getName());

        Thread.currentThread().interrupt();
        System.out.println("将主线程中断");

        //判断运行当前线程是否是中断状态
        System.out.println("检验当前线程是否是中断状态:"+Thread.currentThread().isInterrupted());

        // 将主线程重新连接
        System.out.println("当前线程是否需要恢复中断状态:"+Thread.interrupted());
        System.out.println("当前线程是否需要恢复中断状态:"+Thread.interrupted());
    }
}

运行结果:
在这里插入图片描述




5、观测线程状态 Thread.State 以及获取当前线程状态 getState()

         在 Java 语言中,程序的线程定义 Thread 类中的 State 中,定义了六种线程状态
  • Thread.State :线程状态观测,线程状态一共有下面六种:
    • NEW :尚未启动的线程处于此状态
    • RUNNABLE :正在 Java 虚拟机中执行的线程处于此状态
    • BLOCKED :被堵塞等待监视器锁定的线程处于此状态
    • WAITING: 正在等待另一个线程执行特定动作的线程处于此状态
    • TIMED_WAITING:正在等待另一个线程执行特定动作达到指定时间的线程处于此状态
    • TERMINATED:已退出的线程处于此状态

Demo代码示例:

	//Thread.State.上面六个中的一个:指定线程状态



  • public State getState() :返回当前线程的线程状态信息
    • 通过此方法,可以得到线程的各个执行状态

Demo代码示例:

	public class SellTicket implements Runnable {
	
	    @Override
	    public void run() {
	
	        Thread thread = Thread.currentThread();
	
	        // 查看线程状态
	        Thread.State state = thread.getState();
	        System.out.println("第二次查看:"+state);
	
	        // 如果 此时线程的状态是 RUNNABLE ,就将线程休眠五秒
	        if(state == Thread.State.RUNNABLE){
	            try {
	                Thread.sleep(5000);
	            } catch (InterruptedException e) {
	               // 如果休眠出现异常就见线程中断
	                thread.interrupt();
	            }
	        }
	
	    }
	
	}
	
	 class SellTicketDemo {
	    public static void main(String[] args) {
	
	        SellTicket st = new SellTicket();
	        // 创建三个线程对象
	        Thread thread = new Thread(st, "姚青");
	
	        // 查看线程状态
	        Thread.State state = thread.getState();
	        System.out.println("第一次查看:"+state);
	
	        // 启动线程
	        thread.start();
	
	    }
	}

运行结果:
在这里插入图片描述




6、线程优先执行(线程加入):join()


  • public final void join(); 当通过线程对象调用该方法时,线程就会呈现堵塞状态,只有调用该方法的线程可以流通。直到线程对象的 run() 执行完毕,线程的堵塞状态结束。

    • Demo 代码示例:
    public class ThreadJoin extends Thread {
        @Override
        public void run() {
            for (int x = 0; x < 5; x++) {
                System.out.println(getName() + ":" + x);
            }
        }
    
        public static void main(String[] args) {
            ThreadJoin tj1 = new ThreadJoin();
            ThreadJoin tj2 = new ThreadJoin();
            ThreadJoin tj3 = new ThreadJoin();
    
            tj1.setName("皮卡丘");
            tj2.setName("可达鸭");
            tj3.setName("呱呱");
    
            tj1.start();
    
            try {
            	// 将线程切换成堵塞状态,只执行 tj1 线程,run() 执行完成之后,堵塞结束
                tj1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            tj2.start();
            tj3.start();
       }
    }
    

运行结果:可以看出,皮卡丘是先执行的,当这个线程执行完成之后,其他两个线程同步执行。
在这里插入图片描述




7、线程延后执行(线程礼让): yield()


  • public final void yield(); 线程礼让
    • yield() 的作用就是让步,当多个线程同时执行时,调用该方法的线程会将当前的线程执行状态让出来,和其他线程处于同一个起跑线上。但是线程的执行顺序无法控制,可能是其他线程,也可能是调用 yield() 的线程。
    • 使用该方法,当前线程会放弃现有的CPU资源,和其他线程一起去抢夺现有的CPU资源,但是这样做会让程序运行花费更多的时间。

Demo 代码示例:不调用 yield() 方法看运行的时间差

	public class ThreadYield extends Thread{
	
	    int count = 0;
	
	    @Override
	    public void run() {
	        //获得的是自1970-1-01 00:00:00.000 到当前时刻的时间距离,类型为long。
	        long beginTime =System.currentTimeMillis();
	        for (int i = 0 ;i<= 50000000; i++) {
	            //Thread.yield();
	            count = count +(i+1);
	        }
	        long endTime = System.currentTimeMillis();
	
	        // 用第二次获取到的时间距离减第一次获取到的时间距离
	        System.out.println("Time spent by the program is :" +(endTime-beginTime)+"ms");
	    }
	
	    public static void main(String[] args) {
	        ThreadYield thread = new ThreadYield();
	
	        thread.start();
	    }
	}

运行结果:

在这里插入图片描述



Demo 代码示例:调用 yield() 方法看运行的时间差

	public class ThreadYield extends Thread{
	
	    int count = 0;
	
	    @Override
	    public void run() {
	        //获得的是自1970-1-01 00:00:00.000 到当前时刻的时间距离,类型为long。
	        long beginTime =System.currentTimeMillis();
	        for (int i = 0 ;i<= 50000000; i++) {
	            Thread.yield();
	            count = count +(i+1);
	        }
	        long endTime = System.currentTimeMillis();
	
	        // 用第二次获取到的时间距离减第一次获取到的时间距离
	        System.out.println("Time spent by the program is :" +(endTime-beginTime)+"ms");
	    }
	
	    public static void main(String[] args) {
	        ThreadYield thread = new ThreadYield();
	
	        thread.start();
	    }
	}

运行结果:

在这里插入图片描述

         通过两次运行结果可以看出程序运行的时间差别还是很大的。调用 yield() 运行的时间明显延长。


8、守护线程和用户线程的设置:setDaemon()


  • public final void setDaemon(boolean on); 设置守护线程和用户线程
    • java 中线程分为两种类型:用户线程和守护线程。通过 Thread.setDaemon(false); 设置为用户线程;通过 Thread.setDaemon(true); 设置为守护线程。如果不设置次属性,默认为用户线程。

    • 该方法必须在用户线程执行之前执行

      • 用户线程和守护线程的区别:

        1. 主线程结束后,用户线程还会继续运行,JVM是存活状态;
        2. 主线程结束后,如果没有用户线程运行,守护线程和JVM便都结束运行。
        3. 主线程结束后,如果有用户线程运行,守护线程和JVM便继续为用户线程服务。
        4. 垃圾回收机制便是典型的守护线程,用户线程存在时,便会回收用户线程制造出来的垃圾,用户线程结束后,垃圾回收机制也结束。

Demo 代码示例:

public class ThreadsetDaemonDemo extends Thread {
    @Override
    public void run() {
        for (int x = 0; x < 10; x++) {
            System.out.println(getName() + ":" + x);
        }
    }

    public static void main(String[] args) {
        ThreadDaemon td1 = new ThreadDaemon();
        ThreadDaemon td2 = new ThreadDaemon();

        td1.setName("皮卡丘");
        td2.setName("喷火龙");

        // 设置守护线程
        td1.setDaemon(true);
        td2.setDaemon(true);

        td1.start();
        td2.start();

        Thread.currentThread().setName("小智");
        for (int x = 0; x < 5; x++) {
            System.out.println(Thread.currentThread().getName() + ":" + x);
        }
        
    }
}

         运行结果:通过结果可以看出,用户线程一共执行了五次,守护线程也是执行了五次。但是其实守护线程是设置成10次的,因为用户线程执行结束了,所有守护线程自行离开,所有接下来的程序自然也就不继续执行了。

在这里插入图片描述



二、Runnable 接口

         在创建多线程时候推荐使用实现 Runnable 接口的方式,该接口中只有一个 run() 的抽象方法,我们在使用时候只需要实现该抽线方法即可。然后通过实例化 Thread 类,调用 Thread 类的构造方法,然后将我们创建的线程类对象传入该构造方法,并且可以给该线程设置名称,然后调用 Thread 类对象调用 start() 创建和启动线程,从而实现多线程的过程。这是一种代理模式(静态代理模式),可以很好的降低程序的耦合。
        并且因为接口可以多实现,这就避免了单继承的局限性,灵活便捷,方便同一个对象被多线程使用。

Demo 代码的复用示例:

public class TestRunnable implements Runnable{

    //run()方法线程
    @Override
    public void run() {

        // 获取新创建的线程名称
        String str = Thread.currentThread().getName();
        System.out.println("姚青创建的线程:"+str);


        try {
            // 将线程休眠4秒
            Thread.sleep(4000);
            System.out.println(Thread.currentThread().getName()+"休眠4秒");
        } catch (InterruptedException e) {
            // 如果线程休眠时出现线程异常,就将线程中断
            Thread.currentThread().interrupt();
        }
    }

    //程序主线程   main线程
    public static void main(String[] args) {

        // 创建 Runnable 接口实现类对象
        TestRunnable tr = new TestRunnable();

        // 创建线程对象,设置线程名称,并通过线程对象来开启线程,这种启动线程的方式是代理的方式
        Thread thread = new Thread(tr, "姚青创建的线程");
        thread .start()// 获取当前线程的线程名称
        String str = Thread.currentThread().getName();
        System.out.println("主线程:" + str);

        // 将当前线程中断
        Thread.currentThread().interrupt();
        System.out.println(Thread.currentThread().getName()+"线程中断了!");

        // 判断当前线程是否是中断状态
        if (Thread.currentThread().isInterrupted() == true) {
            // 如果是中断状态就重新链接
            Thread.interrupted();
            System.out.println(Thread.currentThread().getName()+"线程重新链接了!");
        }

    }
}

运行结果:

在这里插入图片描述

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值