Java多线程基础

Java多线程基础,第1张

进程与线程

        进程是 *** 作系统进行资源分配的最小单位。资源包括,CPU、内存、磁盘 IO 等。进程又分为系统进程和用户进程。系统进程用于完成 *** 作系统的各种功能,也是处于运行状态下的 *** 作系统本身。用户进程就是所有由用户所启动的进程。进程之间是相互独立的。

        线程是进行CPU 调度的最小单位。线程必须依赖于进程而存在。线程自己基本上不拥有资源,只拥有一点在运行中必不可少的资源的,如程序计数器,一组寄存器和栈等。但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。

CPU核心数与线程数

        多核心技术,即单芯片多处理器( Chip Multiprocessors,简称 CMP),CMP技术最早是由美国斯坦福大学提出。思想是将大规模并行处理器中的SMP(对称多处理器)集成到一个芯片内,各个处理器并行的执行程序。这种依靠多个CPU同时运行程序是实现超高速计算的一个重要方向,称为并行处理。
        多线程:Simultaneous Multithreading,简称SMT。让同一个处理器上的多个线程同步执行并共享处理器的执行资源。
        超线程技术: *** 作系统是通过线程来执行任务的,增加核心数即是为了增加线程数。核心数与线程数一般是1:1 对应关系,如四核CPU即拥有四个线程。引入超线程技术使核心数与线程数形成 1:2 的关系。

CPU时间片轮转机制

        时间片轮转机制是一种最古老、最简单、最公平且使用最广的算法,又称 RR调度。每个线程被分配一个时间段,称作它的时间片,即是该线程允许被执行的时间。

        在时间片结束时,线程运行的CPU资源将被剥夺并分配给下一个线程。如果线程在时间片结束前阻塞或结束,则CPU会立即进行切换。调度所做的主要工作既是维护一张就绪线程列表,当线程被切换出来时,它将被移到队列的末尾。

        时间片轮转机制的主要缺点是存在调度的开销。从一个线程切换到另一个线程是需要时间的。例如需要保存和装入寄存器值及内存映像,更新各种表格和队列等。线程切换(也称为上下文切换)需要 5ms。例如时间片设为 20ms,则在20ms有效工作时间之后,CPU将花费 5ms进行切换。CPU时间的20%被浪费在了切换的开销上。

并行与并发

        并行:指任务能够同时执行;

        并发:指任务交替执行。并发与单位时间密切相关,即单位时间内并发量离开单位时间是没有意义的。 原则上一个 CPU 只能分配给一个线程,要让它同时运行多个线程,就必须使用并发技术,如“时间片轮转调度算法”。

并发编程

        通过引入多线程技术能够,充分利用CPU的资源,提高应用进程的响应,以及使编程能够向模块化、异步化和简单化方向发展。但是伴随多线程也产生了并发编程箱规的问题,如安全性与死锁、卡死等。

        安全性:由于同一进程的多线程是资源共享的。因此多线程可以可以访问同一个内存地址的变量。当多个线程同时执行写 *** 作,则可能出现对变量的线程不安全问题。如,一个线程修改了值,另一个线程将前一个线程的修改覆盖掉。

        死锁:为了解决线程的安全性问题,java引入了锁机制。死锁,则是因为不同的线程都在等待那些根本不会被释放的锁,从而导致工作无法向前推进。例如,线程1请求持有“A锁”并请求“B锁”,而此时线程2刚好持有“B锁”并请求“A锁”。现在线程A和B都在等待对方的锁,而无法向前推进。既是产生了死锁。

        卡死:线程数太多则会导致 *** 作系统因为创建而消耗大量资源,以及系统中存在太多线程,而使得CPU忙于切换,而导致“过渡切换”问题。最终造成系统的死机。

线程的 *** 作

线程启动:        

        线程的启动有两种方式:
        1、X extends Thread;,然后 X.start;
        2、X implements Runnable;然后交给 Thread 运行;

        Thread和Runnable辨析:Thread 是 Java 里对线程的抽象,而Runnable 只是对任务(业务逻辑)的抽象。Runnable需要封装到Thread才可以以单独线程的方式工作。

        start()方法和run()方法辨析:可以这么理解,通过new Thread()只是构建出一个Thread的实例,还没有 *** 作系统中的线程产生关联。只有在执行了start()方法,才算是真正意义上的启动线程。start()方法即是让线程进入就绪队列,进入就绪状态,等待分配 cpu资源。在得到cpu后开始调度执行,既是执行run方法。run()方法是业务逻辑的实现,性质上与任意一个类的任意一个方法是没有区别的。

线程终止:

        自然终止:run方法执行完成,或者抛出了一个异常让线程提前结束。

        stop终止:直接终结线程的执行。该API被标记为不建议使用的。主要原因是,该方法在终结一个线程时不会考虑线程资源的释放,通常会导致线程没有完成资源释放时就被结束,从而让程序会在不正确的状态下工作。

        其他的还有suspend() 、resume()也是不建议使用的,如suspend()在调用时,线程不会释放资源(如,锁资源),在占着资源情况下进入睡眠,容易导致死锁问题。由于这些方法可能带来的副作用,而被标记为“不建议使用”。

        中断终止:可以做到安全的终止线程。其他线程通过调用“线程A”的interrupt()方法对其进行中
断 *** 作。该方法既是修改“线程A”的“interrupt”标志。线程A通过检查该标志,决定是不是要退出运行。这是一种协作式的方式(还有一种“抢占式”方式),线程在检测到中断标志被修改后,也可以不予理会。良好的编程方式,建议设计良好的响应中断的方式。

        线程有两种方式检测中断标志,成员方法isInterrupted()以及静态的Thread.interrupted()。两者的区别是,静态方法Thread.interrupted()会在检测到终端标志被置位后清除该标志,即检测后中断标识位改写为 false。

        不建议在线程自定义标志位来判断并中止线程。因为在run方法里有阻塞调用时,会无法及时检测到标志改变,必须要等到线程从阻塞调用返回后,才能够检查。而使用中断标志位则不会有这个问题。因为,阻塞的方法,如 sleep、wait 等支持在阻塞时进行中断检查。如果一个线程处于了阻塞状态(线程调用了sleep、join、wait等),当中断标志被置位,这些阻塞方法就会抛出InterruptedException 异常,让中断置位的事件被及时感知。中断异常抛出后,线程的中断标示位也会立即清除,即重新设置为 false。另一方面,检查中断位的状态和检查自定义取消标志位没有区别,用中断位的状态可以减少声明一个变量。

        需要注意的一点是,处于死锁状态的线程无法被中断。

线程优先级

        Java 线程中,用一个成员变量priority来表示线程的优先级。priority的取值在1到10之间。数字越大优先级越高,默认优先级是5。优先级高的线程在 *** 作系统上将被分配更多的时间片。客户端可以通过setPriority()接口来设置线程优先级。通常来说,针对频繁阻塞(休眠频繁、I/O *** 作频繁)的线程设置较高优先级,而偏重计算(需要较多CPU资源)的线程则设置较低的优先级,防止处理器被独占。不过JVM 和 *** 作系统上,线程规划会存在差异,有的 *** 作系统会忽略用户对线程优先级的设定,而用自己的算法确定线程优先级。

守护线程

        守护(Daemon)线程是一种支持型线程。主要用于在后台调度进行一些支持性工作。比如垃圾回收线程就是Daemon线程。当Java 虚拟机中不存在非Daemon线程的时候,守护线程也就停止了,守护线程停止,Java 虚拟机也将退出。

        客户端可以通过调用Thread.setDaemon(true)接口,将线程设置为 Daemon 线程。

        另外,在Java 虚拟机退出时,Daemon 线程中的 finally 块并不一定会执行。因此,在构建Daemon 线程时,不能依靠 finally 块中的内容来确保执行关闭或清理资源的逻辑。

其他线程相关方法

        yield()方法:使当前线程让出CPU占有权,但让出的时间是不确定的,也是不可设定的。所以,调用该方法的线程,有可能在进入到就绪状态后又立刻被 *** 作系统再次选中,调起来马上执行。另外,调用该方法也不会释放锁资源。因此,建议在释放锁资源后再调用该方法。

        join()方法:把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行。如在线程B中调用了线程A的Join()方法,则会线程 A的方法,并等到线程A执行完毕后,才会继续
执行线程B的方法。

        

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/795585.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-06
下一篇 2022-05-06

发表评论

登录后才能评论

评论列表(0条)

保存