多线程

 

基本概念

•程序

  程序(Program) 是一个静态的概念,一般对应于操作系统中的一个可执行文件。

  比如:我们要启动酷狗听音乐,则对应酷狗的可执行程序。

  当我们双击酷狗,则加载程序到内存中,开始执行该程序,于是产生了“进程”。

•进程

  执行中的程序叫做进程(Process),是一个动态的概念。

•线程

  程序当中一条独立的执行线索。

  原本Java程序在只有主方法的情况下,它是最简单的单线程程序。

  然而如果我们需要程序同一时间能够做多件事情则必须使用多线程。

 


通过代码感知多线程

实现方式

  1. 通过继承Thread类实现多线程

  2. 通过Runnable接口实现多线程

  3. 通过Callable接口实现多线程

通过继承 Thread 类实现多线程

  继承类必须重写  run()  方法,该方法是新线程的入口点。

public class TestExtendsThread {

    public static void main(String[] args) {

        MyThread thread = new MyThread();
        /*
         * 通过 thread.start() 来启动多线程
         * 但不保证立即运行,由 CPU 调用
         * 如果调用的是 thread.run(),则只是调用了一个普通方法,并不是多线程
         */
        thread.start();

        for (int i = 0; i < 100; i++)
            System.out.println("主线程");
    }
}

class MyThread extends Thread {

    @Override
    public void run() {
        for (int i = 0; i < 100; i++)
            System.out.println("子线程");
    }
}

运行结果

  由于这和抢夺 CPU 有关,所以,每次运行的结果都不一样,这里就不展示了;

图解

通过继承 Runnable 接口实现多线程

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

        MyThread_2 thread = new MyThread_2();//创建实现类对象
        Thread t = new Thread(thread);//创建代理类对象
        t.start();//通过代理类对象启动
        
        /*
         * 或者你也可以这么写
         * new Thread(new MyThread_2()).start();
         */
        
        for (int i = 0; i < 100; i++)
            System.out.println("主线程");
    }
}

class MyThread_2 implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 100; i++)
            System.out.println("子线程");
    }
}

   推荐使用 Runnale,避免 Java 单继承的局限性。

 


线程的一生

•图示

新生状态(New)

  用 new 关键字建立一个线程对象后,该线程对象就处于新生状态。

  处于新生状态的线程有自己的内存空间,通过调用 start 方法进入就绪状态。

就绪状态(Runnable)

  处于就绪状态的线程已经具备了运行条件,但是还没有被分配到CPU,处于“线程就绪队列”,等待系统为其分配CPU。

  就绪状态并不是执行状态,当系统选定一个等待执行的Thread对象后,它就会进入执行状态。

  一旦获得CPU,线程就进入运行状态并自动调用自己的run方法。

  有 4 中原因会导致线程进入就绪状态:

    1. 新建线程:调用 start() 方法,进入就绪状态。

    2. 阻塞线程:阻塞解除,进入就绪状态。

    3. 运行线程:调用 yield() 方法,直接进入就绪状态。

    4. 运行线程:JVM 将 CPU 资源从本线程切换到其他线程。

运行状态(Running)

  在运行状态的线程执行自己 run() 方法中的代码,直到调用其他方法而终止或等待某资源而阻塞或完成任务而死亡。

  如果在给定的时间片内没有执行结束,就会被系统给换下来回到就绪状态。

  也可能由于某些 “导致阻塞的事件” 而进入阻塞状态。

阻塞状态(Blocked)

  阻塞指的是暂停一个线程的执行以等待某个条件发生(如某资源就绪)。

  有 4 种原因会导致阻塞:

  • 执行  sleep(int millsecond)  方法,使当前线程休眠

    • 进入阻塞状当指定的时间到了后,线程进入就绪状态
  • 执行  wait()  方法,使当前线程进入阻塞状态

    • 当使用  nofity()  方法唤醒这个线程后,它进入就绪状态
  • 线程运行时,某个操作进入阻塞状态

    • 比如执行IO流操作(read()/write()方法本身就是阻塞的方法)
    • 只有当引起该操作阻塞的原因消失后,线程进入就绪状态
  •  join() 线程联合

    • 当某个线程等待另一个线程执行结束后,才能继续执行时,使用join()方法

死亡状态(Dead)

  死亡状态是线程生命周期中的最后一个阶段。

  线程死亡的原因有两个:

    1. 正常运行的线程完成了它run()方法内的全部工作;

    2. 线程被强制终止,如通过执行stop()或destroy()方法来终止一个线程(注:stop()/destroy()方法已经被JDK废弃,不推荐使用)。

  当一个线程进入死亡状态以后,就不能再回到其它状态了。

 


暂停线程执行sleep/yield

•sleep与yield的区别

  •  sleep() 方法:可以让正在运行的线程进入阻塞状态,直到休眠时间满了,进入就绪状态。
  •  yield() 方法:可以让正在运行的线程直接进入就绪状态,让出CPU的使用权。

•sleep

函数原型

   public static void sleep(long millisec) 

概念

  在指定的毫秒数内让当前正在执行的线程休眠(暂停执行);

  此操作受到系统计时器和调度程序精度和准确性的影响。

code

public class TestExtendsThread {

    public static void main(String[] args) throws Exception {

        MyThread_1 thread = new MyThread_1();
        /*
         * 通过 thread.start() 来启动多线程
         * 但不保证立即运行,由 CPU 调用
         * 如果调用的是 thread.run(),则只是调用了一个普通方法,并不是多线程
         */
        thread.start();
        
        /*
         * 因为 sleep() 为静态方法
         * 所以与调用他的对象无关
         * 也就是说,在 main() 函数中调用的 .sleep() 方法
         * 休眠的都是打印 “主线程” 的语句
         */
        Thread.sleep(30);//让当前线程(主线程)休眠 30 毫秒

        for (int i = 0; i < 100; i++)
            System.out.println("主线程");
    }
}

class MyThread_1 extends Thread {

    @Override
    public void run() {
        
        try {
            Thread.sleep(10);//让当前线程(子线程)休眠 10 毫秒
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        for (int i = 0; i < 100; i++)
            System.out.println("子线程");
    }
}

说明  

  • 主线程休眠 30ms,子线程休眠 10ms
  • 启动程序,前 10ms 不执行任何操作
  • 从第 10ms 到 30ms ,只打印子线程的语句
  • 30ms 后,子线程 和 主线程 开始抢夺 CPU 资源
  • 直到两个线程的 for() 循环语句执行结束

•睡眠排序法

code

public class SleepSort {

    public static void main(String[] args) {

        int[] a = { 10, 30, 20, 6, 23 };

        for (int x : a) {
            SortThread st = new SortThread(x);
            st.start();
        }
    }
}

class SortThread extends Thread {

    private int num;

    SortThread(int num) {
        this.num = num;
    }

    public void run() {
        try {
            // 通过 sleep() 让 num 小的优先执行完,进而执行打印语句,实现数组从小到大排列
            Thread.sleep(num);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println(num);
    }
}

  一个 sleep() 的简单应用;

•Yeild

函数原型

   public static void yield() 

概念

  让当前线程(假设为线程A)从运行状态进入到就绪状态,与其他已就绪状态的线程(Other)争抢下一次 CPU 的使用权;

  所以下一次执行的线程还有可能是线程A。

有两个线程,一个线程负责打印 1~26,另一个负责打印 a~z;
现在给负责打印 1~26 的线程添加 yeild() 方法,测试执行情况。

 code

public class TestYield {

    public static void main(String[] args) {

        NumThread nt = new NumThread();
        CharThread ct = new CharThread();

        nt.start();
        ct.start();
    }
}

class NumThread extends Thread {

    public void run() {
        for (int i = 1; i <= 26; i++) {
            System.out.println(i);
            Thread.yield();
        }
    }
}
class CharThread extends Thread {

    public void run() {
        for (char c = 'a'; c <= 'z'; c++) {
            System.out.println(c);
        }
    }
}

  通过观察运行结果,你会发现,添加 yield 的线程并不会真正的放弃抢夺。

 

posted @ 2021-01-11 11:26  MElephant  阅读(129)  评论(0编辑  收藏  举报