zoukankan      html  css  js  c++  java
  • java多线程与线程池(一):多线程概述

    引用自https://www.runoob.com/java/java-multithreading.html

    Java 给多线程编程提供了内置的支持。 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

    多线程是多任务的一种特别的形式,但多线程使用了更小的资源开销。

    这里定义和线程相关的另一个术语 - 进程:一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。

    多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的。

    一个线程也有他的生命周期:

    • 新建状态:

      使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。

    • 就绪状态:

      当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。

    • 运行状态:

      如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。

    • 阻塞状态:

      如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:

      • 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。

      • 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。

      • 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。

    • 死亡状态:

      一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。

    其实这与操作系统中的概念,没有太大的区别。

    下面来说说如何在java中创建一个线程:

    1)通过继承Thread类:

    public class MyThread extends Thread{
        private int i=0;
        public MyThread(){}
    
        @Override
        public synchronized void start() {
            super.start();
        }
    
        @Override
        public void run() {
            for (i=0;i<3;i++){
                System.out.println(Thread.currentThread().getName() + " " + i);
            }
        }
    }
    public class TestThread {
        public static void main(String[] args) {
            for(int i = 0;i<5;i++){
                System.out.println(Thread.currentThread().getName() + i);
                if(i == 3){
                    Thread thread1 = new MyThread();
                    Thread thread2 = new MyThread();
                    thread1.start();
                    thread2.start();
                }
            }
        }
    }

    运行结果如下:

    如果在if里加上sleep(100),那么main4就会在最下面输出。这里还有一点要注意的是,在这种实现多线程方法下,调用start()即会调用对应线程的run(),无需start()后再调用run()。如果再调用run(),就会变成这样:

    图中带空格的main i,就是因为main中直接调用线程类的run()方法导致的,因为是main直接调用线程的run()方法,所以currentThread.getName()结果就是main(下面线程的结果我没有截进来)

    2)通过实现Runnable接口来创建线程

    public class MyThread implements Runnable{
        Thread thread;
        MyThread(){}
        public void start(){
            if(thread == null){
                thread = new Thread(this);
                thread.start();
            }
        }
        @Override
        public void run() {
            int i;
            for (i=0;i<3;i++){
                System.out.println(Thread.currentThread().getName() + " " + i);
            }
        }
    }
    public class TestThread {
        public static void main(String[] args) {
            for(int i = 0;i<5;i++){
                System.out.println(Thread.currentThread().getName() + i);
                if(i == 3){
                    MyThread thread1 = new MyThread();
                    MyThread thread2 = new MyThread();
                    thread1.start();
                    thread2.start();
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    这种方法下就需要自己在线程类下实例化Thread,实例化时目标选为this,再调用start()方法,之后再main里执行start()时,被启动的线程便会自动调用其run()方法。

    3)通过Callable和Future创建线程

    public class TestThread implements Callable<Integer> {
        public static void main(String[] args) {
            TestThread thread = new TestThread();
            FutureTask<Integer> futureTask = new FutureTask<>(thread);
            for(int i = 0;i<5;i++){
                System.out.println(Thread.currentThread().getName() + i);
                if(i==2){
                    new Thread(futureTask).start();
                }
            }
            try {
                System.out.println("子线程的返回值:"+ futureTask.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
        @Override
        public Integer call() throws Exception {
            int i = 0;
            for(;i<3;i++){
                System.out.println(Thread.currentThread().getName() + " " + i);
            }
            return i;
        }
    }

    说实话这种方法我觉得异常的繁琐,但是使用他的优点是线程结束后可以有返回值,想必也有它独特的用处,先了解着。

    创建线程的三种方式的对比:

    • 1. 采用实现 Runnable、Callable 接口的方式创建多线程时,线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。

    • 2. 使用继承 Thread 类的方式创建多线程时,编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。

    之后的内容:

    • 线程同步
    • 线程间通信
    • 线程死锁
    • 线程控制:挂起、停止和恢复
    • 线程池

    多线程使用时的注意事项:

    有效利用多线程的关键是理解程序是并发执行而不是串行执行的。例如:程序中有两个子系统需要并发执行,这时候就需要利用多线程编程。

    通过对多线程的使用,可以编写出非常高效的程序。不过请注意,如果你创建太多的线程,程序执行的效率实际上是降低了,而不是提升了。

    请记住,上下文的切换开销也很重要,如果你创建了太多的线程,CPU 花费在上下文的切换的时间将多于执行程序的时间!

  • 相关阅读:
    springboot集成flowable oracle数据库版本报错
    Vue.js中this.$nextTick()的使用
    Centos下虚拟环境的创建以及python3安装
    SaltStack实战
    第一章 Jenkins安装配置
    JavaScript 常用正则表达式
    ps 掉出字符设备面板,修改颜色等
    博客验证码破解
    我终于想起密码了~
    Linux grep 命令
  • 原文地址:https://www.cnblogs.com/MYoda/p/11231707.html
Copyright © 2011-2022 走看看