JAVA多线程

JAVA多线程

多线程,说白了就是多条执行路径,原来是一条路径,就主路径(main),现在是多条路径。

一,常用概念

1.1 程序

Java源程序和字节码文件被称为“程序” ( Program ),是一个静态的概念。

1.2 进程

执行中的程序叫做进程(Process),是一个动态的概念。为了使计算机程序得以运行,计算机需要加载代 码,同时也要加载数据。

  • 进程是程序的一次动态执行过程, 占用特定的地址空间。
  • 每个进程由3部分组成:cpu,data,code。每个进程都是独立的,保有自己的cpu时间,代码和数 据,即便用同一份程序产生好几个进程,它们之间还是拥有自己的这3样东西。
  • 多任务(Multitasking)操作系统将CPU时间动态地划分给每个进程,操作系统同时执行多个进程,每 个进程独立运行。以进程的观点来看,它会以为自己独占Cpu的使用权

1.3 线程

线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程 并行执行不同的任务。

1.4 进程与线程之间的区别

区别 进程 线程
根本区别 作为资源分配的单位 调度和执行的单位
开销 每个进程都有独立的代码和数据空间,进程间的切换有较大开销 线程可以看做轻量级的进程,同一类线程共享 代码和数据空间,每个线程有独立运行栈和程 序计数器(PC),线程切换的开销小
所处环境 在操作系统中能同时运行多个任务(程序) 在同一个应用程序中有多个顺序流同时进行
分配内存 系统在运行的时候会为每个进程分配不 同的内存区域 线程间共享进程的所有资源,每个线程只有有 自己的堆栈和局部变量。线程由CPU独立调度 执行,在多CPU环境下就允许多个线程同时运 行
包含关系 没有线程的进程可以看作单线程,如果 一个进程拥有多个线程,则执行过程不 是一条线的,而是多条线(线程)共同 完成的 线程是进程的一部分,所以线程有的时候会被 称为是轻量级进程或轻权进程

注意:有的多线程是模拟出来的,真正的多线程是指有多个 cpu,即多核,如服务器。如果是模拟出来的 多线程,即一个 cpu 的情况下,在同一个时间点,cpu 只能执行一个代码, 因为切换的很快,所以就有同时执行的错觉。

1.5 多线程的优缺点

优点

资源利用率更好;程序设计在某些情况下更简单;程序响应更快

缺点

  • 设计更复杂,虽然有一些多线程应用程序比单线程的应用程序要简单,但其他的一般都更复杂。在 多线程访问共享数据的时候,这部分代码需要特别的注意。线程之间的交互往 往非常复杂。不正确 的线程同步产 生的错误非常难以被发现,并且重现以修复。
  • 上下文切换的开销 当 CPU 从执行一个线程切换到执行另外一个线程的时候,它需要 先存储当前线 程的本地的数据,程序 指针等,然后载入另一个线程的本地数据,程序指针 等,最后才开始执 行。这种切换称为“上下文切 换”(“context switch”)。CPU 会在一 个上下文中执行一个线程,然后 切换到另外一个上下文中执 行另外一个线程。上下文切换 并不廉价。如果没有必要,应该减少上 下文切换的发生。

二,创建线程

2.1 继承Thread类实现

  1. 创建线程类: 继承 Thread类 +重写 run() 方法
  2. 构造线程类对象: 创建 子类的对象
  3. 启动线程: 通过子类对象调用 start() 方法
//继承多线程父类
public class ThreadDemo extends Thread {
    //重写run()方法
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("吃饭");
            //只能抓取异常,父类没有抛出异常
            try {
                Thread.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        ThreadDemo td = new ThreadDemo();
        Sleep s = new Sleep();
        //开始多线程
        td.start();
        s.start();
        for (int i = 0; i < 20; i++) {
            System.out.println("打豆豆");
            try {
                Thread.sleep(2);//延迟执行,放大问题
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

这种方式的特点:那就是如果我们的类已经从一个类继承,则无法再 继承 Thread 类,异常只能捕获。

2.2 实现Runnable接口实现

  1. 创建实现 Runnable 接口的实现类 + 重写 run() 方法
  2. 创建一个实现类对象
  3. 利用实现类对象创建Thread类对象
  4. 启动线程
public class ThreadInterfaceDemo implements Runnable {
    public static void main(String[] args) {
        //真实角色
        ThreadInterfaceDemo tid = new ThreadInterfaceDemo();

        //创建代理线程
        Thread td = new Thread(tid);

        //开始线程
        td.start();

        for (int i = 0; i < 100; i++) {
            System.out.println("Thread");
        }
    }

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

优点:接口可以多实现,类只能单继承,实现资源共享

2.3 实现Callable接口实现

  1. 创建实现 Callable 接口的实现类 + 重写 call() 方法
  2. 创建一个实现类对象
  3. 由 Callable 创建一个 FutureTask 对象
  4. 由 FutureTask 创建一个 Thread 对象
  5. 启动线程
public class CallAbleTest {
    public static void main(String[] args) throws Exception{
        MyCallable callable = new MyCallable();
        // 将Callable包装成FutureTask,FutureTask也是一种Runnable
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        // 将FutureTask包装成Thread
        new Thread(futureTask).start();
        System.out.println(futureTask.isDone());
        System.out.println(futureTask.get());
    }
}

class MyCallable implements Callable<Integer>{
    @Override
	public Integer call() throws Exception {
        int sum = 0;
		for (int i = 0; i <= 100000; i++) {
        	sum += i;
    	}
    	return sum;
    }
}
  • Thread: 继承方式, 不建议使用, 因为Java是单继承的,继承了Thread就没办法继承其它类了,不够 灵活
  • Runnable: 实现接口,比Thread类更加灵活,没有单继承的限制 Callable: Thread和Runnable都是重写的run()方法并且没有返回值,
  • Callable是重写的call()方法并 且有返回值并可以借助FutureTask类来判断线程是否已经执行完毕或者取消线程执行
  • 当线程不需要返回值时使用Runnable,需要返回值时就使用Callable,一般情况下不直接把线程体 代码放到Thread类中,一般通过Thread类来启动线程
  • Thread类是实现Runnable,Callable封装成FutureTask,FutureTask实现RunnableFuture, RunnableFuture继承Runnable,所以Callable也算是一种Runnable,所以三种实现方式本质上都 是Runnable实现

优点:

call方法可以抛出异常可以定义返回值

缺点:

使用复杂麻烦

2.4 使用线程池创建

线程池

就是首先创建一些线程,它们的集合称为线程池。使用线程池可以很好地提高性能,线程池在系 统启动时即创建大量空闲的线程,程序将一个任务传给线程池,线程池就会启动一条线程来执行这个任 务,执行结束以后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个任务。

线程池的工作机制

在线程池的编程模式下,任务是提交给整个线程池,而不是直接提交给某个线程,线程池在拿到任 务后,就在内部寻找是否有空闲的线程,如果有,则将任务交给某个空闲的线程。 一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务。

使用

  1. 使用 Executors 类中的 newFixedThreadPool(int num) 方法创建一个线程数量为num的线程池
  2. 调用线程池中的 execute() 方法执行由实现 Runnable 接口创建的线程;调用 submit() 方法执行 由实现 Callable 接口创建的线程
  3. 调用线程池中的 shutdown() 方法关闭线程池
public class ThreadPoolTest {
    public static void main(String[] args)throws Exception {
        Thread.currentThread().setName("主线程");
        System.out.println(Thread.currentThread().getName() + ": 输出的结果" );
        // 通过线程池工厂创建线程数量为2的线程池
        ExecutorService service = Executors.newFixedThreadPool(2);
        //执行线程,execute()适用于实现Runnable接口创建的线程
        service.execute(new MyThread());
        //submit()适用于实现Callable接口创建的线程
        Future<Integer> task = service.submit(new MyCallable());
        System.out.println(task.get());
        // 关闭线程池
        service.shutdown();
    }
}

三,线程的5种状态

3.1 新建状态

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

3.2 就绪状态

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

3.3 运行状态

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

3.4 阻塞状态

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

  • 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
  • 同步阻塞:线程在获取 synchronized同步锁失败(因为同步锁被其他线程占用)。
  • 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O请求时,线程就会进入到阻塞状态。 当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状 态。

3.5 终止状态

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

上一篇:线程池之Executor


下一篇:成功解决TypeError: ‘tuple‘ object is not callable