Java基础学习笔记3——多线程

多线程相关概念

  1. 进程:是代码在数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位。

  2. 线程:是进程的一个执行路径,一个进程中至少有一个线程,进程中的多个线程共享进程的资源。

  3. 虽然系统是把资源分给进程,但是CPU很特殊,是被分配到线程的,所以线程是CPU分配的基本单位

  4. 一个进程中有多个线程,多个线程共享进程的堆和方法区资源,但是每个线程有自己的程序计数器和栈区域
    在这里插入图片描述

  5. 程序计数器:是一块内存区域,用来记录线程当前要执行的指令地址 。

  6. :用于存储该线程的局部变量,这些局部变量是该线程私有的,除此之外还用来存放线程的调用栈祯。

  7. :是一个进程中最大的一块内存,堆是被进程中的所有线程共享的。

  8. 方法区:用来存放 NM 加载的类、常量及静态变量等信息,也是线程共享的 。

  9. 进程与线程的区别:进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响;线程是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉

    • 一个程序至少有一个进程,一个进程至少有一个线程
    • 线程的划分尺度小于进程,使得多线程程序的并发性高
    • 进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
    • 每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
    • 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。
  10. 并发:是指同一个时间段内多个任务同时都在执行,并且都没有执行结束。并发任务强调在一个时间段内同时执行,而一个时间段由多个单位时间累积而成,所以说并发的多个任务在单位时间内不一定同时在执行 。

  11. 并行:是说在单位时间内多个任务同时在执行 。

  12. 在多线程编程实践中,线程的个数往往多于CPU个数,所以一般都称多线程并发编程而不是多线程并行编程。

  13. 线程安全:指在并发的情况之下,该代码经过多线程使用,线程的调度顺序不影响任何结果。这个时候使用多线程,我们只需要关注系统的内存,CPU是不是够用即可。

  14. 并发过程中常见的问题

    • 线程安全问题:多个线程同时操作共享变量1时,会出现线程1更新共享变量1的值,但是其他线程获取到的是共享变量没有被更新之前的值。就会导致数据不准确问题。
      在这里插入图片描述

    • 共享内存不可见性问题:Java 内存模型规定,将所有的变量都存放在主内存中,当线程使用变量时,会把主内存里面的变量复制到自己的工作空间或者叫作工作内存,线程读写变量时操作的是自己工作内存中的变量
      在这里插入图片描述
      在这里插入图片描述
      上图所示是一个双核 CPU 系统架构,每个核有自己的控制器和运算器,其中控制器包含一组寄存器和操作控制器运算器执行算术逻辑运算CPU的每个核都有自己的一级缓存,在有些架构里面还有一个所有CPU都共享的二级缓存。 那么Java内存模型里面的工作内存,就对应这里的 Ll或者 L2 缓存或者 CPU 的寄存器。

      (1)线程A首先获取共享变量X的值,由于两级Cache都没有命中,所以加载主内存中X的值,假如为0。然后把X=0的值缓存到两级缓存,线程A修改X的值为1,然后将其写入两级Cache,并且刷新到主内存。线程A操作完毕后,线程A所在的CPU的两级Cache内和主内存里面的X的值都是1。

      (2)线程B获取X的值,首先一级缓存没有命中,然后看二级缓存,二级缓存命中了,所以返回X=1;到这里一切都是正常的,因为这时候主内存中也是X=l。然后线程B修改X的值为2,并将其存放到线程2所在的一级Cache和共享二级Cache中,最后更新主内存中X的值为2,到这里一切都是好的。

      (3)线程A这次又需要修改X的值,获取时一级缓存命中,并且X=1这里问题就出现了,明明线程B已经把X的值修改为2,但这时线程A获取的还是1,这就是共享变量的内存不可见问题,也就是线程B写入的值对线程A不可见。

  15. synchronized 的内存语义:可以解决共享变量内存可见性问题

    进入synchronized块的内存语义是把在synchronized块内使用到的变量从线程的工作内存中清除,这样在synchronized块内使用到该变量时就不会从线程的工作内存中获取,而是直接从主内存中获取。

    退出synchronized块的内存语义是把在synchronized块内对共享变量的修改刷新到主内存。会造成上下文切换的开销,独占锁,降低并发性

  16. volatile:该关键字可以确保对一个变量的更新对其他线程马上可见。当一个变量被声明为volatile时,线程在写入变量时不会把值缓存在寄存器或者其他地方,而是会把值刷新回主内存。当其他线程读取该共享变量时,会从主内存重新获取最新值,而不是使用当前线程的工作内存中的值。volatile的内存语义和synchronized有相似之处,具体来说就是,当线程写入了volatile变量值时就等价于线程退出synchronized同步块(把写入工作内存的变量值同步到主内存),读取volatile变量值时就相当于进入同步块(先清空本地内存变量值,再从主内存获取最新值)不能保证原子性

  17. 同步:指的是通过人为的控制和调度,保证共享资源的多线程访问成为线程安全,来保证结果的准确。在保证结果准确的同时,提高性能,才是优秀的程序。线程安全的优先级高于性能

  18. 线程的状态
    在这里插入图片描述

    • NEW:线程刚创建, 尚未启动。
    • RUNNABLE:线程正在正常运行中, 当然可能会有某种耗时计算/IO等待的操作/CPU时间片切换等, 这个状态下发生的等待一般是其他系统资源, 而不是锁、sleep等。
    • BLOCKED:多个线程有同步操作的场景, 比如正在等待另一个线程的synchronized 块的执行释放, 或者可重入的 synchronized块里别人调用wait()方法, 也就是这里是线程在等待进入临界区。
    • WAITING:线程拥有了某个锁之后, 调用了他的wait方法, 等待其他线程/锁拥有者调用 notify / notifyAll 以便该线程可以继续下一步操作, 这里要区分 BLOCKEDWATING 的区别, 一个是在临界点外面等待进入, 一个是在临界点里面wait等待别人notify, 线程调用了join方法 join了另外的线程的时候, 也会进入WAITING状态, 等待被他join的线程执行结束。
    • TIMED_WAITING:有限的(时间限制)的WAITING, 一般出现在调用wait(long), join(long)等情况下, 另外一个线程sleep后, 也会进入TIMED_WAITING状态。
    • TERMINATED: 该线程的run方法已经执行完毕了, 基本上就等于死亡了(当时如果线程被持久持有, 可能不会被回收)。
  19. 线程特性

    • 线程能被标记为守护线程,也可以是用户线程。

    • 每个线程均分配一个name,默认为(Thread-自增数字)的组合。

    • 每个线程都有优先级。高优先级线程优先于低优先级线程执行。1-10,默认为5。

    • main所在的线程组为main,构造线程的时候没有显式的指定线程组,线程组默认和父线程一样。

    • 线程中的run()方法代码里面又创建了一个新的线程对象时,新创建的线程优先级和父线程优先级一样。

    • 当且仅当父线程为守护线程时,新创建的线程才会是守护线程。

    • 当JVM启动时,通常会有唯一的一个非守护线程(这一线程用于调用指定类的main()方法)。

      JVM会持续执行线程直到下面情况某一个发生为止:(1)类运行时exit()方法被调用且安全机制允许此exit()方法的调用;(2)所有非守护类型的线程均已经终止,或 run()方法调用返回 或 在run()方法外部抛出了一些可传播性的异常。

基本线程类

  1. 基本线程类指的是Thread类,Runnable接口,Callable接口

  2. Thread 类实现了Runnable接口。

    启动一个线程的方法:

    MyThread my = new MyThread();
    my.start();
    

    Thread 类相关方法:

    //当前线程可转让CPU控制权,让别的就绪状态线程运行(切换)
    public static Thread.yield() 
    //暂停一段时间
    public static Thread.sleep()  
    //在一个线程中调用other.join(),将等待other执行完后才继续本线程。    
    public join()
    //后两个函数皆可以被打断
    public interrupt()
    

    关于中断:并不像stop方法那样会中断一个正在运行的线程。线程会不时地检测中断标识位,以判断线程是否应该被中断(中断标识值是否为true)。中断只会影响到wait状态、sleep状态和join状态。被打断的线程会抛出InterruptedException

    Thread.interrupted()检查当前线程是否发生中断,返回boolean

    synchronized在获锁的过程中是不能被中断的。

    中断是一个状态interrupt()方法只是将这个状态置为true而已。所以说正常运行的程序不去检测状态,就不会终止,而wait等阻塞方法会去检查并抛出异常。如果在正常运行的程序中添加while(!Thread.interrupted()) ,则同样可以在中断后离开代码体。

    Thread类最佳实践:写的时候最好要设置线程名称 Thread.name,并设置线程组 ThreadGroup,目的是方便管理。在出现问题的时候,打印线程栈 (jstack -pid) 一眼就可以看出是哪个线程出的问题,这个线程是干什么的。

  3. RunnableThread类似。

  4. Callable接口:

    future模式:并发模式的一种,可以有两种形式,即无阻塞和阻塞,分别是isDoneget。其中Future对象用来存放该线程的返回值以及状态。

    ExecutorService e = Executors.newFixedThreadPool(3);
    //submit方法有多重参数版本,既支持callable也能够支持runnable接口类型.
    Future future = e.submit(new myCallable());
    future.isDone() //return true,false 无阻塞
    future.get() // return 返回值,阻塞直到该线程运行结束
    

创建线程

  1. 继承Thread类,重写该类的run()方法

    使用继承方式的好处是,在run()方法内获取当前线程直接使用this就可以了,无须使用Thread.currentThread()方法;不好的地方是Java不支持多继承,如果继承了Thread类,那么就不能再继承其他类。另外任务与代码没有分离,当多个线程执行一样的任务时需要多份任务代码。

    class MyThread extends Thread {
        
        private int i = 0;
    
        @Override
        public void run() {
            for (i = 0; i < 100; i++) {
                System.out.println(this.getName() + " " + i);
            }
        }
    }
    
    public class ThreadTest {
    
        public static void main(String[] args) {
            for (int i = 0; i < 100; i++) {
                System.out.println(this.getName() + " " + i);
                if (i == 30) {
                    // 创建一个新的线程  myThread1  此线程进入新建状态
                    Thread myThread1 = new MyThread();
                    // 创建一个新的线程 myThread2 此线程进入新建状态
                    Thread myThread2 = new MyThread();
                    // 调用start()方法使得线程进入就绪状态
                    myThread1.start();
                    // 调用start()方法使得线程进入就绪状态
                    myThread2.start();
                }
            }
        }
    }
    

    如上所示,继承Thread类,通过重写run()方法定义了一个新的线程类MyThread,其中run()方法的方法体代表了线程需要完成的任务,称之为线程执行体。当创建此线程类对象时,一个新的线程得以创建,并进入到线程新建状态。通过调用线程对象引用的start()方法,使得该线程进入到就绪状态,此时此线程并不一定会马上得以执行,这取决于CPU调度时机。

  2. 实现Runnable接口,并重写该接口的run()方法

    解决继承Thread的缺点,没有返回值。

    run()方法同样是线程执行体,创建Runnable实现类的实例,并以此实例作为Thread类的target来创建Thread对象,该Thread对象才是真正的线程对象。

    class MyRunnable implements Runnable {
        private int i = 0;
    
        @Override
        public void run() {
            for (i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName() + " " + i);
            }
        }
    }
    
    public class ThreadTest {
    
        public static void main(String[] args) {
            for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName() + " " + i);
                if (i == 30) {
                    // 创建一个Runnable实现类的对象
                    Runnable myRunnable = new MyRunnable(); 
                    // 将myRunnable作为Thread target创建新的线程
                    Thread thread1 = new Thread(myRunnable); 
                    Thread thread2 = new Thread(myRunnable);
                    // 调用start()方法使得线程进入就绪状态
                    thread1.start(); 
                    thread2.start();
                }
            }
        }
    }
    

    当执行到Thread类中的run()方法时,会首先判断target是否存在,存在则执行target中的run()方法,也就是实现了Runnable接口并重写了run()方法的类中的run()方法。

  3. 使用CallableFuture接口创建线程

    具体是创建Callable接口的实现类,并实现clall()方法。并使用FutureTask类来包装Callable实现类的对象,且以此FutureTask对象作为Thread对象的target来创建线程。

    class MyCallable implements Callable<Integer> {
        private int i = 0;
    
        // 与run()方法不同的是,call()方法具有返回值
        @Override
        public Integer call() {
            int sum = 0;
            for (; i < 100; i++) {
                System.out.println(Thread.currentThread().getName() + " " + i);
                sum += i;
            }
            return sum;
        }
    
    }
    
    public class ThreadTest {
    
        public static void main(String[] args) {
    
            // 创建MyCallable对象
            Callable<Integer> myCallable = new MyCallable();
            //使用FutureTask来包装MyCallable对象
            FutureTask<Integer> ft = new FutureTask<Integer>(myCallable); 
    
            for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName() + " " + i);
                if (i == 30) {
                    //FutureTask对象作为Thread对象的target创建新的线程
                    Thread thread = new Thread(ft);
                    //线程进入到就绪状态
                    thread.start();
                }
            }
    
            System.out.println("主线程for循环执行完毕..");
            
            try {
                //取得新创建的新线程中的call()方法返回的结果
                int sum = ft.get();
                System.out.println("sum = " + sum);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
    
        }
    }
    

    在实现Callable接口中,此时不再是run()方法了,而是call()方法,此call()方法作为线程执行体,同时还具有返回值。在创建新的线程时,是通过FutureTask来包装MyCallable对象,同时作为了Thread对象的target

    FutureTask类实际上是同时实现了RunnableFuture接口,由此才使得其具有FutureRunnable双重特性。通过Runnable特性,可以作为Thread对象的target,而Future特性,使得其可以取得新创建线程中的call()方法的返回值。

    通过ft.get()方法获取子线程call()方法的返回值时,当子线程此方法还未执行完毕,ft.get()方法会一直阻塞,直到call()方法执行完毕才能取到返回值。

  4. 使用继承方式的好处是方便传参,可以在子类里面添加成员变量,通过set方法设置参数或者通过构造函数进行传递,而如果使用Runnable方式,则只能使用主线程里面被声明为final的变量。不好的地方是Java不支持多继承,如果继承了Thread类,那么子类不能再继承其他类,而Runable则没有这个限制。前两种方式都没办法拿到任务的返回结果,但是Callable方式可以。

  5. 对于线程的启动而言,都是调用线程对象的start()方法,需要特别注意的是:不能对同一线程对象两次调用start()方法

相关方法

  1. start方法

    public synchronized void start() {
        /**
         * 此方法并不会被主要方法线程or由虚拟机创建的系统组线程所调用.
         * 任何向此方法添加的新功能方法在未来都会被添加到虚拟机中.
         * 0状态值代表了NEW的状态.
         */
        if (threadStatus != 0) // 线程不能重复start
            throw new IllegalThreadStateException();
    
        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. 
         */
        group.add(this);
    
        boolean started = false;
        try {
            start0(); //本地方法
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                      it will be passed up the call stack */
            }
        }
    }
    
    private native void start0();
    
  2. yield方法:是一个本地方法,提示线程调度器当前线程愿意放弃当前CPU的使用。如果当前资源不紧张,调度器可以忽略这个提示。本质上线程状态一直是RUNNABLE,但可以理解为RUNNABLERUNNING的转换。

    public static native void yield();
    
  3. sleep方法:有一个重载方法,sleep方法会释放CPU的时间片,但是不会释放锁,调用sleep()之后从RUNNABLE状态转为TIMED_WAITING状态。

    /**
     * 此方法会引起当前执行线程sleep(临时停止执行)指定毫秒数.
     * 此方法的调用不会引起当前线程放弃任何监听器(monitor)的所有权(ownership).
     */
    public static native void sleep(long millis) throws InterruptedException;
     
    public static void sleep(long millis, int nanos) throws InterruptedException {
        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
     
        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException(
                                "nanosecond timeout value out of range");
        }
     
        if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
            millis++;
        }
     
        sleep(millis);
    }
    
  4. join方法join某个线程A,会使得线程B进入等待,直到线程A结束,或者到达给定的时间,那么期间线程B处于BLOCKED的状态,而不是线程A。

    /**
     * 最多等待参数millis(ms)时长当前线程就会死亡.参数为0时则要持续等待.
     * 此方法在实现上:循环调用以this.isAlive()方法为条件的wait()方法.
     * 当线程终止时notifyAll()方法会被调用.
     * 建议应用程序不要在线程实例上使用wait,notify,notifyAll方法.
     */
    public final synchronized void join(long millis)
        throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;
    
        //如果等待时间<0,则抛出异常
        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
    
        //如果等待时间为0
        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }
    
    //等待时间单位为纳秒,其它解释都和上面方法一样
    public final synchronized void join(long millis, int nanos)
        throws InterruptedException {
    
        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
    
        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException(
                "nanosecond timeout value out of range");
        }
    
        if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
            millis++;
        }
    
        join(millis);
    }
    
    //方法功能:等待一直到线程死亡.
    public final void join() throws InterruptedException {
        join(0);
    }
    
  5. wait方法

    //本地方法 参数为毫秒
    public final native void wait(long timeout) throws InterruptedException; 
    
    //参数为毫秒和纳秒
    public final void wait(long timeout, int nanos) throws InterruptedException {
        if (timeout < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
    
        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException("nanosecond timeout value out of range");
        }
    
        if (nanos > 0) {
            timeout++;
        }
    
        wait(timeout);
    }
    
    public final void wait() throws InterruptedException {
        wait(0);
    }
    

    wait()wait(long timeout, int nanos)都在在内部调用了wait(long timeout)方法。

    wait方法会引起当前线程阻塞,直到另外一个线程在对应的对象上调用notify或者notifyAll()方法,或者达到了方法参数中指定的时间。

    调用wait方法的当前线程一定要拥有对象的监视器锁

    wait方法会把当前线程T放置在对应的object上的等待队列中,在这个对象上的所有同步请求都不会得到响应。线程调度将不会调用线程T,在以下四件事发生之前,线程T会被唤醒(线程T是在其代码中调用wait方法的那个线程):

    • 当其他的线程在对应的对象上调用notify方法,而在此对象的对应的等待队列中将会任意选择一个线程进行唤醒。
    • 其他的线程在此对象上调用了notifyAll方法。
    • 其他的线程调用了interrupt方法来中断线程T。
    • 等待的时间已经超过了wait中指定的时间。如果参数timeout的值为0,不是指真实的等待时间是0,而是线程等待直到被另外一个线程唤醒为止。

    被唤醒的线程T会被从对象的等待队列中移除并且重新能够被线程调度器调度。之后,线程T会像平常一样跟其他的线程竞争获取对象上的锁;一旦线程T获得了此对象上的锁,那么在此对象上的所有同步请求都会恢复到之前的状态,也就是恢复到wait被调用的情况下。然后线程T从wait方法的调用中返回。因此,当从wait方法返回时,对象的状态以及线程T的状态跟wait方法被调用的时候一样

    线程在没有被唤醒,中断或者时间耗尽的情况下仍然能够被唤醒,这叫做伪唤醒。虽然在实际中,这种情况很少发生,但是程序一定要测试这个能够唤醒线程的条件,并且在条件不满足时,线程继续等待。换言之,wait操作总是出现在循环中,就像下面这样:

    synchronized(对象){
        while(条件不满足){
         对象.wait();
      }
      对应的逻辑处理
    }
    

    如果当前的线程被其他的线程在当前线程等待之前或者正在等待时调用了interrupt()中断了,那么会抛出InterruptedExcaption异常。直到这个对象上面的锁状态恢复到上面描述的状态以前,这个异常是不会抛出的。

    wait方法把当前线程放置到这个对象的等待队列中,解锁也仅仅是在这个对象上;当前线程在其他对象上面上的锁在当前线程等待的过程中仍然持有其他对象的锁。

    wait(long timeout, int nanos)方法的实现中只要nanos大于0,那么timeout时间就加上一毫秒,主要是更精确的控制时间,其他的跟wait(long timeout)一样。

  6. notify方法:通知可能等待该对象的对象锁的其他线程。由JVM(与优先级无关)随机挑选一个处于wait状态的线程。

    public final native void notify(); //本地方法
    

    在调用notify()之前,线程必须获得该对象的对象级别锁

    执行完notify()方法后,不会马上释放锁,要直到退出synchronized代码块,当前线程才会释放锁。

    notify()一次只随机通知一个线程进行唤醒

  7. notifyAll()方法:和notify()差不多,只不过是使所有正在等待池中等待同一共享资源的全部线程从等待状态退出,进入可运行状态,让它们竞争对象的锁,只有获得锁的线程才能进入就绪状态。

    public final native void notifyAll();//本地方法
    

    每个锁对象有两个队列:

    • 就绪队列:存储将要获得锁的线程
    • 阻塞队列:存储被阻塞的线程

示例

  1. sleep

    public class ThreadDemo1 {
        public static void main(String[] args) {
            
            MyThread mt = new MyThread();
     
            //推荐
            MyRunnable mr = new MyRunnable();
            Thread t2 = new Thread(mr);
     
            mt.start();//启动线程
            t2.start();
     
            for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName() + "-" + i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
     
    /**
     * 实现线程的第一种方式:继承thread类
     */
    class MyThread extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                if (this.isInterrupted()) {
                    break;
                }
                System.out.println(Thread.currentThread().getName() + "-" + i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    this.interrupt();
                }
     
            }
        }
    }
     
    /**
     * 实现线程的第二种方式:实现Runnable接口
     */
    class MyRunnable implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName() + "-" + i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
  2. join和中断(推荐用标记中断):

    public class ThreadDemo2 {
     
        public static void main(String[] args){
     
            MyRunable2 mr2 = new MyRunable2();
            Thread t = new Thread(mr2);
    		// t.start();
     
            MyRunable3 mr3 = new MyRunable3();
            Thread t2 = new Thread(mr3);
            t2.start();
     
            for (int i = 0; i < 50; i++) {
                System.out.println(Thread.currentThread().getName()+"--"+i);
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if(i==20){
                	// 注释打开用来测试join
    			/**                
    				try {
    					//让t线程执行完毕
                        t.join();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //中断线程,只是作了一个中断标记,用于测试interrupt方法
                    t.interrupt();
    			*/
                    //用于测试标记中断
                    mr3.flag = false; 
                }
            }
        }
    }
     
    class MyRunable2 implements Runnable{
        @Override
        public void run() {
            for (int i = 0; i < 50; i++) {
                //测试中断状态,此方法会把中断状态清除
                if(Thread.interrupted()){
                    //....
                    break;
                }
                System.out.println(Thread.currentThread().getName()+"--"+i);
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    Thread.currentThread().interrupt();
                }
            }
        }
    }
    
    //标记中断
    class MyRunable3 implements Runnable{
        public boolean flag = true;
        public MyRunable3(){
            flag = true;
        }
        @Override
        public void run() {
            int i=0;
            while(flag){
                System.out.println(Thread.currentThread().getName()+"==="+(i++));
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
  3. 优先级和守护进程:

    public class ThreadDemo3 {
     
        public static void main(String[] args){
            MyRunnable4 mr4 = new MyRunnable4();
            Thread t = new Thread(mr4);
            t.setName("Thread-t");
            //优先级高可以提高该线程抢点CPU时间片的概率大
            t.setPriority(Thread.MAX_PRIORITY);
            //线程可以分成守护线程和 用户线程,当进程中没有用户线程时,JVM会退出
            //把线程设置为守护线程
            t.setDaemon(true);
            System.out.println(t.isAlive());
            t.start();
            System.out.println(t.isAlive());
     
            for (int i = 0; i < 50; i++) {
                System.out.println("main--"+i);
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (i==5){
                    //让出本次CPU执行时间片
                    Thread.yield();
                }
            }
     
        }
    }
     
    class MyRunnable4 implements Runnable{
        @Override
        public void run() {
            for (int i = 0; i < 50; i++) {
                System.out.println("--"+i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值