Java 并发编程 ---Java 多线程基本概念 (1),java 多线程下载视频
时间过的那么快,今天就是六一了,好长时间没有
更新文章了,又变懒了!借着活动挑战一下自己!加油兄弟们! 凭我的脑子和钱包过个六一儿童节应该不过分吧哈哈哈
Java 程序就是天生的多线程程序,所以学好 Java,就必须得学多线程,在 Android 中多线程用的还是很多的,今天就一起了解一些多线程的基本概念,和线程的生命周期
1. 基本概念
1.1 CPU 核心数和线程数的关系
1.1.1 CPU 核心数:
在计算机里,核心就是指的是处理器 我们去买电脑的的时候,不论买的是台式的还是笔记本,还是手机,都会关注这个 Cpu 是几核的
对吧,看我的电脑就是 6 个内核,也就是六个处理器,在计算机的早期,cpu 是没有多核概念的,是单核的,后来为什么出现了多核呢? 多核就是因为按照摩尔定律,计算机的芯片里的晶体管的密度,会每 18 个月会翻一番,翻到了如今这个地步,cpu 里的自存会到 3nm,到 3nm 就会翻不动了,为啥翻不动了,我也不知道,牵扯到量子隧穿这个东西了,为了继续提高运算速度,才提出了多核心的概念,在 一块物理芯片上面,我们集成多个物理处理器(CMP),CMP 是由美国斯坦福大学提出的,单芯片多处理器,也指多核心其思想是将大规模并行处理器中的 SMP(对称多处理器)集成到同一芯片内,各个处理器并行执行不同的进程
1.1.2 cpu 核心数和线程数的关系
CPU 核心数:线程数=1:1,他俩是一比一的关系,线程数是指的是,正在运行的线程最大数量,比如我的电脑是六核的,我电脑同时在跑的线程,最大的数量的是 6 个,线程数并不是我们程序里进程开了多少个线程,但是有的 CPU 也是六核的,但是线程数能达到 12 个,这是怎么做到的呢?Intel 的超线程技术可以做到,把一个物理的 cpu,把它模拟成两个逻辑 cpu,一个物理核心对应两个逻辑核心
1.2 cpu 的时间片轮转机制
在我们开发的过程中,好像并没有受到核心数的影响,我们在程序中,想起几个线程就起几个线程,cpu 轮转机制是最简单,最古老,最公平,而且使用的也是最广泛的一种算法机制,也被称为 RR 调度,即使我就有一个 CPU,有一百个线程在执行,但是 cpu 给我们的感觉是。一百个线程同时在运行的,这是怎么做到的呢
我们把 cpu 的运行时间分片,打个比方每个线程执行 5ms,分到很小很小 5ms,每个线程执行 5ms,100 个线程都执行一遍,也就 500ms,对于我们人来说,我们可能就感觉不到他在停顿,就是因为这个机制,所以才让我们感觉我们起的线程,就是在同时运行,事实上不是那么回事 时间片设置的大小,和操作系统有关,和 Cpu 也有关系 CPU 是时间轮转的,在 java 中能不能,让 CPU 指定执行某个线程呢,这个功能是能做到,但是在 java 里不能实现,得调用 Linux 内核的一个 API 去设置
1.3 什么是线程,什么是进程
标准说法:进程是操作系统进行资源分配和调度的基本单位,线程 thread 是操作系统能够进行运算调度的最小单位 我们电脑启动的程序就是一个进程,比如微信,AndroidStudio,IDEA,从概念规模上来讲,进程是大于线程的,线程不能单独存在的,线程必须依附于进程才能存在,如果一个进程中有一个线程活着,那么这个进程就是活着的,进程里的线程是会共享进程里边的所有资源内存,cpu 和 IO,进程和进程之间是相互独立的,进程和 cpu 没有必然关系,线程才是调度 cpu 的最小单位 在操作系统中对线程的限制,Linux 中 new 出的线程不能超过 1000,在 Windows 不能超过 2000,
1.4 并行和并发
专业术语有点麻烦,并行,就是能同时运行的最大线程数量,intel 的四核并行度是 8,它可以同时运行 8 个线程 并发 指单位时间处理任务的个数,一定是和时间挂钩的,对于并发来讲, 脱离了单位时间是没什么意义的,比如 1 秒钟 CPU 执行了 100 个时间片,他的并发量就是 100,另一个 cpu 它能一秒钟执行 200 个时间片,他的并发量就是 200
1.5 高并发编程的意义,好处和注意事项
如果不进行高并发编程,我们就不能够完全最大化的利用这种多核多线程的能力,使用高并发编程,就能提高我们程序的响应能力,比如迅雷,提供了多线程下载, 高并发编程可以在我们编程的时候,使我们的程序模块化,异步化,和简单化, 但是在并发编程里面最需要注意的就是安全问题,因为在同一个进程下的线程是共享这个进程的所有资源,很有可能产生线程安全问题和死锁的问题,在 linux 里或者在 windows 里,线程开的太多了,会造成内存耗尽,从而造成死机 时间片轮转机制也是有代价的,会进行上下文的切换,这个线程在让出 cpu 的时候,操作系统会把当前的数据状态存到内存或者磁盘里,当再一次轮到这个线程的时候,又把它取出来加载进去,这个就是上下文切换,切换一次上下文需要耗费大约 20000 个 cpu 的时间周期,这个也是非常消耗 cpu 资源的,如果线程过多,会进行大量的上下文切换,这样就很有可能造成一种情况,假如你起了 100 个线程,用时间片轮转机制所耗费的时间,远远大于你一个一个线程执行的时间,所以多线程要用好,就得考虑的要全面
2. 认识 Java 里的线程
public class OnlyMain {public static void main(String[] args) {//虚拟机线程管理的接口 ThreadMXBean threadMXBean= ManagementFactory.getThreadMXBean();//获取线程信息 ThreadInfo[] threadInfos=threadMXBean.dumpAllThreads(false,false);for (ThreadInfo threadInfo:threadInfos) {System.out.println("["+threadInfo.getThreadId()+"]"+" "+threadInfo.getThreadName());}}}
我们看上面这一串代码,如果我执行这个 main 方法,他会执行几个线程呢,有兴趣的小伙伴可以把代码跑一遍,去验证一下,没错他会执行 6 个线程
为啥我只执行了一个 main 方法,为啥会有六个线程在执行呢 1、main 就是我们所说的主线程 2、Finalizer
我们在学习 Java 的时候,Java 是一个面向对象的语言,所有类的父类是那个是 Object,在 Object 里有这样一个方法
我们一个对象在有资源要回收的时候,会重写这个方法,然后将回收资源的操作,放到这个方法里面执行,这个方法就会由 Finalizer 这个线程执行,但是有的时候会发现,这个 finalize 方法不一定会执行,导,有些关于对象的资源就会没有被回收掉,为什么会这样呢?这个锅得 Finalizer 线程来背了,是因为 Finalizer 这个线程是一个守护线程,和主线程同生共死,极有可能主线程挂掉了 finalize 这个方法还没来得及执行,然后 Finalizer 也挂掉了
2.1 启动线程的两种方式
Java 里的程序天生就是多线程的,新启动线程的方法有:
Thread 类
实现接口 Runnable
实现接口 Callable 有返回值
/***
启动线程的几种方式*/public class NewThread {//继承自 Thread 类 private static class UseThread extends Thread{@Overridepublic void run() {super.run();System.out.println("UseThread:我运行了");}}//实现 runnable 接口 private static class UseRun implements Runnable{
@Overridepublic void run() {System.out.println("UseRun:我运行了");}}//实现 callable 接口,允许有返回值 private static class UseCall implements Callable<String>{@Overridepublic String call() throws Exception {System.out.println("UseCall:我运行了");return "CallResult";}}public static void main(String[] args) throws ExecutionException, InterruptedException {UseThread useThread=new UseThread();useThread.start();UseRun useRun=new UseRun();new Thread(useRun).start();UseCall useCall=new UseCall();FutureTask<String> futureTask=new FutureTask<>(useCall);new Thread(futureTask).start();System.out.println(futureTask.get());}}
Callable 和 Runnable 实现的方法其实可以看做一种方式,因为在启动线程的时候 Callable 是放到 FutureTask 里面的,我们去看看 FutureTask 是什么东西他是个泛型类,实现了 RunnableFuture 这个泛型接口,RunnableFuture 这个接口又继承了 Runnable,和 Future,所以我们可以把 Runnable 和 Callable 看成一种方式,Callable 这种方式是怎么拿到返回值的呢?
futureTask.get()拿到就是返回值,首先我们看一看 Future 这个接口都有什么方法
我们再去看实现
它会先判断这个任务执行的状态是否完成,如果没有完成它会进入等待完成这个方法
private int awaitDone(boolean timed, long nanos)throws InterruptedException {final long deadline = timed ? System.nanoTime() + nanos : 0L;WaitNode q = null;boolean queued = false;for (;;) {if (Thread.interrupted()) {removeWaiter(q);throw new InterruptedException();}
int s = state;if (s > COMPLETING) {if (q != null)q.thread = null;return s;}else if (s == COMPLETING) // cannot time out yetThread.yield();else if (q == null)q = new WaitNode();else if (!queued)queued = UNSAFE.compareAndSwapObject(this, waitersOffset,q.next = waiters, q);else if (timed) {nanos = deadline - System.nanoTime();if (nanos <= 0L) {removeWaiter(q);return state;}LockSupport.parkNanos(this, nanos);}elseLockSupport.park(this);}}
它会一直等待执行完成 完成之后它会 执行 report 方法,咱再看 report 方法
然后我们再去看看 Run 方法
public void run() {if (state != NEW ||!UNSAFE.compareAndSwapObject(this, runnerOffset,null, Thread.currentThread()))return;try {Callable<V> c = callable;if (c != null && state == NEW) {V result;boolean ran;try {result = c.call();ran = true;} catch (Throwable ex) {result = null;ran = false;setException(ex);}if (ran)set(result);}} finally {// runner must be non-null until state is settled to// prevent concurrent calls to run()runner = null;// state must be re-read after nulling runner to prevent// leaked interruptsint s = state;
评论