- 原子性:操作或多个操作要么全部执行且不被打断,要么都不执行。这保证了线程在执行操作时不会被其他线程干扰。
- 可见性:当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看到修改的值。这通过volatile关键字、 synchronized和Lock等机制实现。
- 有序性:程序执行的顺序按照代码的先后顺序执行。Java内存模型允许指令重排序,但提供了volatile等机制来保证一定的有序性。
进程
进程的概念
进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。进程是一个具有独立功能的程序关于某个数据集合的一次运行活动,它可以申请和拥有系统资源,是一个动态的概念,是一个活动的实体。进程不只是程序的代码,还包括当前的活动,通过程序计数器的值和处理寄存器的内容来表示。
在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
进程的状态:
- 运行状态:进程正在CPU上运行,占用处理器资源。
- 就绪状态:进程已具备运行条件,等待系统分配处理器资源以便运行。
- 等待状态(也称为阻塞状态或睡眠状态):进程因等待某个事件(如I/O操作完成)而无法运行,即使获得CPU控制权也无法执行。
此外,根据不同的系统和描述,进程还可能包括新建状态和终止状态等。新建状态表示进程刚被创建但尚未就绪,终止状态则表示进程已结束运行但系统尚未回收其资源。
进程状态之间的转换:
- 就绪到运行:当进程调度程序为处于就绪状态的进程分配了处理机后,该进程便由就绪状态转变为执行状态。
- 运行到就绪:处于执行状态的进程在其执行过程中,因分配给它的时间片已用完或更高优先级的进程需要运行,而不得不让出处理机,于是进程从执行状态转变为就绪状态。
- 运行到阻塞:正在执行的进程因等待某种事件发生(如I/O操作完成)而无法继续执行时,便从执行状态转变为阻塞状态。
- 阻塞到就绪:处于阻塞状态的进程,若其等待的事件已经发生(如I/O操作完成),则进程由阻塞状态转变为就绪状态。
进程转换中会遇到问题:
- 进程切换开销大:进程切换需要反复进入内核态,置换状态信息,当进程数较多时,大部分系统资源可能被进程切换所消耗。
- 死锁问题:多个进程因竞争资源而造成一种僵局,若无外力作用,这些进程都将无法向前推进。
- 同步与互斥问题:多个进程在访问共享资源时,需要确保数据的一致性和防止数据被破坏,需要采用同步机制如互斥锁、信号量等。
- 进程通信问题:进程间需要通信和共享数据时,需要采用进程间通信(IPC)机制,如管道、消息队列、共享内存等。
进程示例代码:
以下是一个简单的Java进程示例代码,它创建了一个echo
进程来执行一个外部命令cmd /c echo
(比如命令在Unix/Linux系统上,或者命令在Windows系统上):
public class ProcessExample {public static void main(String[] args) {try {// 在Unix/Linux系统上使用// Process process = Runtime.getRuntime().exec("echo Hello, World!");// 在Windows系统上使用Process process = Runtime.getRuntime().exec("cmd /c echo Hello, World!");// 读取进程的输出java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.InputStreamReader(process.getInputStream()));String line;while ((line = reader.readLine()) != null) {System.out.println(line);}// 等待进程结束int exitVal = process.waitFor();if (exitVal == 0) {System.out.println("Success!");} else {// 有错误发生System.out.println("Something went wrong");}} catch (Exception e) {e.printStackTrace();}}
}
这段代码首先通过方法创建了一个进程来执行一个外部命令Runtime.getRuntime().exec()
。然后,它读取了这个进程的输出,并将其打印到控制台上。最后,它等待进程结束,并检查进程的退出值来确定命令是否成功执行。
请注意,在实际应用中,你可能需要处理更多的错误情况,并且确保正确地关闭所有打开的资源,比如。此外,当执行外部命令时,一定要小心,避免安全风险。
进程与线程的区别:
- 资源拥有:进程是资源分配的基本单位,拥有独立的内存空间和系统资源;线程是CPU调度的基本单位,基本上不拥有系统资源,只拥有一点运行中必不可少的资源,且共享其所属进程的资源。
- 并发性:进程间并发执行需要较大的开销,而同一进程内的多个线程间并发执行开销较小,因此线程能更高效地提高并发性。
- 系统开销:创建或撤销进程时,系统需要分配或回收资源,开销较大;而线程切换只需保存和设置少量寄存器的内容,开销较小。
- 独立性:进程间相互独立,一个进程崩溃不会影响其他进程;而一个线程崩溃可能导致整个进程崩溃。
- 执行过程:进程拥有独立的执行序列,而线程必须依存于应用程序中,由应用程序提供多个线程执行控制。
线程
线程的基本概念
线程是操作系统调度的最小单位,也是程序执行的最小单元。在Java中, 线程是Thread
类的实例, 每个线程都有自己的堆栈和程序计数器。Java语言支持多线程, 允许程序同时执行多个任务, 从而提高程序的并发性和响应性。
线程的创建方式
Java中创建线程主要有以下几种方式:
1. 继承Thread
类 :
- 创建一个继承自
Thread
类的子类。 - 重写
run()
方法,在其中定义线程要执行的任务。 - 创建线程对象,并调用
start()
方法启动线程。注意,不能直接调用run()
方法,因为这样会使得run()
方法在当前线程中执行,而不是在新的线程中执行。
示例代码:
public class MyThread extends Thread {@Overridepublic void run() {System.out.println("Thread is running...");}public static void main(String[] args) {MyThread myThread = new MyThread();myThread.start(); // 启动线程}
}
2. 实现Runnable
接口 :
- 创建一个实现了
Runnable
接口的类。 - 实现
run()
方法,在其中定义线程要执行的任务。 - 创建
Thread
对象,并将实现了Runnable
接口的对象作为参数传递给Thread
类的构造函数。 - 调用方法启动线程
start()
。
示例代码:
public class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println("Thread is running...");}public static void main(String[] args) {Thread thread = new Thread(new MyRunnable());thread.start(); // 启动线程}
}
3. 实现
Callable
接口 ( Java 5及以上版本) :在Java 5及以上版本中,
Callable
接口类似于Runnable
接口, 但它可以返回一个结果, 并且可以抛出异常。与Runnable
不同,Callable
接口通常与FutureTask
类一起使用,FutureTask
类实现了Future
接口,并提供了启动和取消计算、查询计算是否完成以及获取计算结果的方法。以下是一个简单的例子,展示了如何实现
Callable
接口,并使用FutureTask
来启动和管理线程:import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask;// 实现Callable接口 public class CallableTask implements Callable<Integer> {private int number;public CallableTask(int number) {this.number = number;}@Overridepublic Integer call() throws Exception {// 模拟一些计算Thread.sleep(1000);return number * 2;} }// 创建并启动线程,使用Callable和FutureTask public class CallableExample {public static void main(String[] args) {// 创建CallableTask对象CallableTask callableTask = new CallableTask(10);// 将CallableTask对象包装在FutureTask对象中FutureTask<Integer> futureTask = new FutureTask<>(callableTask);// 创建并启动线程Thread thread = new Thread(futureTask);thread.start();try {// 等待线程执行完成,并获取结果Integer result = futureTask.get();System.out.println("线程返回的结果: " + result);} catch (InterruptedException | ExecutionException e) {e.printStackTrace();}} }
在这个例子中,
CallableTask
类实现了Callable
接口,并重写了call
方法。call
方法包含了线程执行的任务代码,并返回一个整数结果。在CallableExample
类的main
方法中,我们创建了一个CallableTask
对象,并将其包装在对象中。然后,我们创建了一个FutureTask
线程,将对象作为参数传递给FutureTask
线程的构造函数,并启动FutureTask
线程。最后,我们通过调用对象的get
方法来等待线程执行完成,并获取结果。
4. 使用匿名内部类 :
- 可以通过匿名内部类的方式简化线程创建的代码,适用于线程执行的任务较为简单的情况。
使用匿名内部类创建并启动线程的示例:
public class AnonymousInnerClassThreadExample {public static void main(String[] args) {// 使用匿名内部类创建并启动线程Thread thread = new Thread(new Runnable() {@Overridepublic void run() {// 在这里编写线程的任务代码System.out.println("匿名内部类线程正在运行...");}});// 启动线程thread.start();}
}
在这个例子中,我们没有创建一个单独的类来实现Runnable
接口。相反,我们在创建Thread
对象时,直接提供了一个Runnable
接口的匿名实现。这种方式使得代码更加简洁,特别适用于线程任务较为简单的情况。
5. 使用Lambda表达式 ( Java 8及以上版本) :
- Java 8引入了Lambda表达式, 可以进一步简化线程创建的代码。
示例代码:
public class LambdaThreadExample {public static void main(String[] args) {// 使用Lambda表达式创建并启动线程new Thread(() -> {// 在这里编写线程的任务代码System.out.println("Lambda表达式线程正在运行...");}).start();}
}
在这个例子中, 我们直接使用Lambda表达式() -> { /* 任务代码 */ }
作为Thread
类的构造函数的参数。Lambda表达式是Runnable
接口的一个实现, 它覆盖了run
方法。这种方式使得代码更加简洁,并且避免了创建额外的类或匿名内部类。
线程的运行
线程被创建并调用start()
方法后,并不立即执行run()
方法中的代码。线程会进入就绪状态,等待CPU的调度。当线程获得CPU时间片后,它才会开始执行run()
方法中的代码,进入运行状态。线程在执行过程中可能会因为各种原因(如等待I/O操作完成、等待锁等)进入阻塞状态,等待条件满足后再次进入就绪状态,等待CPU调度执行。最终,线程执行完run()
方法中的代码或因为异常而终止,进入死亡状态。
使用接口创建和启动线程
下面是一个简单的Java线程示例代码, 使用Runnable
接口来创建和启动线程:
// 实现Runnable接口
class MyThread implements Runnable {public void run() {// 在这里编写线程要执行的任务for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getName() + " 正在运行,i=" + i);try {// 线程休眠一段时间Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}public class ThreadExample {public static void main(String[] args) {// 创建MyThread对象MyThread myThread = new MyThread();// 创建线程对象,将MyThread作为目标对象传递给Thread对象Thread thread = new Thread(myThread);// 启动线程thread.start();// 也可以这样创建并启动线程(匿名内部类方式)new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getName() + " 匿名内部类方式正在运行,i=" + i);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}).start();}
}
在这个示例中,MyThread
类实现了Runnable
接口,并重写了run
方法以定义线程要执行的任务。在main
方法中,我们创建了MyThread
的实例,并将其作为目标对象传递给Thread
对象。然后,我们调用Thread
对象的start
方法来启动线程。
此外,示例中还展示了如何使用匿名内部类的方式来创建并启动线程,这种方式不需要显式地创建一个实现Runnable
接口的类。
注意事项
- 线程一旦启动,就不能再次启动。尝试对已经启动的线程调用
start()
方法会抛出异常IllegalThreadStateException
。 - 直接调用线程的
run()
方法并不会创建新的线程,而是在当前线程中同步执行run()
方法中的代码。 - 在多线程编程中,需要注意线程同步和互斥问题,以避免数据不一致和竞态条件的发生。