做java web开发,一直以来比较依赖java框架和oracle数据库的功能。因为一般遇到高并发的情况并不多,企业内软件多半用户数不多,即使偶尔遇到,也都在oracle数据库中处理了。
对java的多线程开发,一直以来只有一个简单的概念,没有深入使用理解过,希望这次利用网上的内容,能够回顾一下。
多线程相关概念
进程:一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。比如在Windows系统中,一个运行的exe就是一个进程。
线程:进程中的一个执行流程,一个进程中可以运行多个线程。比如java.exe进程中可以运行很多线程。线程总是属于某个进程,进程中的多个线程共享进程的内存。
操作系统中的进程是资源的组织单位。进程有一个包含了程序内容和数据的地址空间,以及其它的资源,包括打开的文件、子进程和信号处理器等。不同进程的地址空间是互相隔离的。而线程表示的是程序的执行流程,是CPU调度的基本单位。线程有自己的程序计数器、寄存器、栈和帧等。引入线程的动机在于操作系统中阻塞式I/O的存在。当一个线程所执行的I/O被阻塞的时候,同一进程中的其它线程可以使用CPU来进行计算。这样的话,就提高了应用的执行效率。线程的概念在主流的操作系统和编程语言中都得到了支持。
线程与进程的区别:
-
地址空间: 进程内的一个执行单元;进程至少有一个线程,它们共享进程的地址空间;而进程有自己独立的地址空间;
-
资源拥有: 进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源
-
线程是处理器调度的基本单位,但进程不是.
-
二者均可并发执行.
线程相关的状态以及转换
线程共有下面4种状态:
-
新建状态(New):新创建了一个线程对象,当你用new创建一个线程时,该线程尚未运行。
-
就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
-
运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
-
阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM把该线程放入锁。
其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
-
死亡状态(Dead):
由于run方法的正常退出而自然死亡;
没有捕获到的异常事件终止了run方法的执行,从而导致线程突然死亡
状态之间的转换可以用下面的两张图来展示
多线程实现方法
-
继承Thread类
public class TestThread extends Thread { @Override public void run() { super.run(); for (int i = 0; i < 1000; i++) { System.out.println(“T-“ + i);
try { Thread.sleep(800); } catch (InterruptedException e) { e.printStackTrace(); } } } }
-
实现Runnable接口
public class TestRunnable implements Runnable {
@Override public void run() { for (int i = 0; i < 1000; i++) { System.out.println("R-" + i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { Thread runnable = new Thread(new TestRunnable()); Thread thread = new TestThread(); thread.start(); runnable.start(); } }
运行结果:
线程的分类
- 守护线程(daemon threads)
守护线程通常为普通线程提供服务,它们通常有一个无限循环,等待服务请求或执行线程的任务,在没有其他的非守护线程运行时,守护线程将停止工作。即使你不创建任何线程,Java应用程序在默认情况下创建多个线程。他们大多是守护线程,主要用于处理任务,如垃圾收集或JMX。
守护线程的特点:
-
优先级非常低
-
只有当同一个程序的任何其他线程正在运行时执行。
-
当线程只剩下守护线程的时候,JVM就会退出.但是如果还有其他的任意一个用户线程还在,JVM就不会退出.
- 非守护线程 (non-daemon threads)或普通线程或用户线程(user threads)
用户线程和守护线程两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。 因为没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了
在JAVA中通过调方法Thread.setDaemon(true)将线程转为守护线程。但需要注意的有:
-
但必须在调Thread.start()方法之前,因为一旦线程正在运行,你不能修改它的守护进程的状态。
-
在Daemon线程中产生的新线程也是Daemon的
-
守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断。
代码示例:
public class DaemonThread{ public static void main(String[] args) { Thread thread = new Thread(new ThreadRunnable()); thread.setDaemon(true); thread.start(); try { Thread.sleep(750); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Main Thread ending"); }}class ThreadRunnable implements Runnable{ @Override public void run() { int count = 0; while (true) { System.out.println("Hello from Worker " + count++); try { Thread.sleep(350); } catch (InterruptedException e) { e.printStackTrace(); } } }}
运行结果:
Hello from Worker 0Hello from Worker 1Hello from Worker 2Main Thread ending
从运行结果可以看到,随着主线程的结果,本来是无限循环的子线程,也跟着结束了,说明守护线程不会影响程序的结束。
线程状态的转换
线程的状态转换,主要是由sleep()、wait()、yeid()、join()等几个方法来实现(stop()、interrupt()等方法貌似都有一定的问题,不是很推荐使用)。
sleep方法与wait方法的区别:
sleep方法是静态方法,wait方法是非静态方法。sleep方法在时间到后会自己“醒来”,但wait不能,必须由其它线程通过notify(All)方法让它“醒来”。sleep方法通常用在不需要等待资源情况下的阻塞,像等待线程、数据库连接的情况一般用wait。
sleep/wait与yeld方法的区别:
调用sleep或wait方法后,线程即进入block状态,而调用yeld方法后,线程进入runnable状态。
wait与join方法的区别:
wait方法体现了线程之间的互斥关系,而join方法体现了线程之间的同步关系。wait方法必须由其它线程来解锁,而join方法不需要,只要被等待线程执行完毕,当前线程自动变为就绪。join方法的一个用途就是让子线程在完成业务逻辑执行之前,主线程一直等待直到所有子线程执行完毕。