线程:
线程的基本概念
- 线程是程序执行的基本单元,每个线程都有自己的执行路径。
- 多线程允许在一个程序中同时运行多个线程,使得任务能够并发处理。
- 主线程:当Java程序启动时,JVM会创建一个默认的主线程来执行main方法。除此之外,开发者还可以创建并启动其他线程。
通过线程,可以让程序同时执行多个任务,提高程序的并发性能和响应速度
创建线程的方式
在Java中,创建线程有两种主要方式:
方式一:继承Thread类
通过继承Thread
类,并重写run
方法来定义线程执行的任务,然后通过调用start
方法启动线程。
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread is running.");
}
}
public class Main {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start(); // 启动线程
}
}
方式二:实现Runnable接口
通过实现Runnable
接口,并实现run
方法来定义线程执行的任务,然后将该实现类的实例传递给Thread
对象,并调用start
方法启动线程。
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Thread is running.");
}
}
public class Main {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start(); // 启动线程
}
}
3. 线程的生命周期
线程在其生命周期中通常经历以下几个状态:
- 新建(New):线程对象被创建,但未启动。
- 就绪(Runnable):线程已经启动,等待CPU时间片。
- 运行(Running):线程获得CPU时间片,正在执行任务。
- 阻塞(Blocked):线程等待某个条件(如资源释放)满足时被阻塞。
- 终止(Terminated):线程完成执行或因异常退出。
4. 线程的控制方法
Java提供了一些控制线程的方法:
start()
:启动线程,调用run
方法。run()
:线程执行的任务代码。sleep(long millis)
:让当前线程睡眠指定的毫秒数。join()
:等待线程终止。interrupt()
:中断线程。isAlive()
:检查线程是否还在运行。
5. 线程的同步
为了防止多个线程同时访问共享资源导致的数据不一致问题,Java提供了同步机制。可以使用synchronized
关键字来同步方法或代码块。
6. 线程池
线程池是一种管理多个线程的技术,用于减少线程的创建和销毁所带来的开销。Java提供了Executor
框架来简化线程池的使用。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
executor.execute(new Runnable() {
@Override
public void run() {
System.out.println("Thread is running.");
}
});
}
executor.shutdown(); // 关闭线程池
}
}
extends
关键字
extends
关键字用于类的继承。当一个类继承另一个类时,它使用extends
关键字。这意味着 子类(也称为派生类)继承了父类(也称为基类)的属性和方法。子类可以使用父类的成员 (字段和方法),也可以重写父类的方法来提供自己的实现。
特点:
- 单继承:Java中一个类只能继承一个直接父类。这是因为Java不支持多重继承。
- 继承实现:子类可以重用父类的代码,同时可以增加新的方法和字段,或者重写父类的方法。
class Animal { void eat() { System.out.println("This animal eats food."); } } class Dog extends Animal { void bark() { System.out.println("The dog barks."); } @Override void eat() { System.out.println("The dog eats bones."); } } public class Main { public static void main(String[] args) { Dog dog = new Dog(); dog.eat(); // Output: The dog eats bones. dog.bark(); // Output: The dog barks. } }
在这个示例中,
Dog
类继承了Animal
类,并且重写了eat
方法,同时添加了一个新的方法bark
。implements
关键字implements
关键字用于接口的实现。当一个类实现一个接口时,它使用implements
关键字。接口是一种特殊的类,里面只包含抽象方法(从Java 8开始,接口也可以包含默认方法和静态方法)。实现接口的类必须提供接口中所有抽象方法的具体实现。特点:
- 多重实现:一个类可以实现多个接口,弥补了Java单继承的局限。
- 契约式设计:接口定义了一组方法,类通过实现这些方法来保证其行为符合接口的契约。
interface Animal { void eat(); } interface Pet { void play(); } class Dog implements Animal, Pet { @Override public void eat() { System.out.println("The dog eats bones."); } @Override public void play() { System.out.println("The dog plays with a ball."); } } public class Main { public static void main(String[] args) { Dog dog = new Dog(); dog.eat(); // Output: The dog eats bones. dog.play(); // Output: The dog plays with a ball. } }
在这个示例中,
Dog
类实现了Animal
和Pet
两个接口,并提供了这两个接口中方法的具体实现。
extends
和implements
的主要区别
-
继承类型:
extends
:用于类与类之间的继承,一个类只能继承一个父类。implements
:用于类与接口之间的实现,一个类可以实现多个接口。
-
代码复用:
extends
:子类继承父类的属性和方法,可以直接使用或重写。implements
:实现接口的类必须提供接口中所有方法的实现。
-
设计目的:
extends
:表示一种“is-a”的关系,子类是父类的一种特殊类型。implements
:表示一种“can-do”的关系,类实现了接口,具备了接口规定的行为。
Thread和Runnable实现线程的区别
在Java中,实现多线程有两种常见的方式:继承Thread类和实现Runnable接口。本文将通过一个简单的示例代码来讲解这两种方式的区别以及各自的特点。
继承Thread类
继承Thread类是实现线程的一种方式。通过继承Thread类,可以直接重写Thread类的run方法来定义线程执行的任务。以下是示例代码的一部分,展示了如何通过继承Thread类来创建一个线程:
class PrimeThread extends Thread {
long minPrime;
PrimeThread(long minPrime) {
this.minPrime = minPrime;
}
@Override
public void run() {
// compute primes larger than minPrime
for (; ; ) {
System.out.println("PrimeThread");
}
}
}
class Main {
public static void main(String[] args) {
PrimeThread p = new PrimeThread(143);
p.start();
}
}
在这个例子中,PrimeThread类继承自Thread类,并重写了run方法。在main方法中,通过调用p.start()
来启动线程。start()
方法会调用Thread类的run方法,从而在新的线程中执行我们定义的任务。
实现Runnable接口
实现Runnable接口是另一种实现线程的方式。Runnable接口只有一个run
方法,需要实现该方法来定义线程执行的任务。以下是示例代码的另一部分,展示了如何通过实现Runnable接口来创建一个线程:
class PrimeRun implements Runnable {
long minPrime;
PrimeRun(long minPrime) {
this.minPrime = minPrime;
}
@Override
public void run() {
// compute primes larger than minPrime
for (; ; ) {
System.out.println("PrimeRun");
}
}
}
class Main {
public static void main(String[] args) {
PrimeRun p = new PrimeRun(143);
new Thread(p).start();
}
}
在这个例子中,PrimeRun类实现了Runnable接口,并实现了run方法。在main方法中,通过创建一个Thread对象并将PrimeRun对象作为参数传递给Thread构造函数,然后调用start()
方法来启动线程。
两者的主要区别
-
类继承关系:
- 继承Thread类的类不能再继承其他类(Java是单继承的)。
- 实现Runnable接口的类可以继续继承其他类。
-
代码设计和灵活性:
- 继承Thread类更适合于需要扩展Thread类并重写其他方法的情况。
- 实现Runnable接口更灵活,可以将Runnable对象传递给多个线程,实现相同任务的多线程执行。
-
资源共享:
- 使用Runnable接口实现的多线程,更容易实现资源共享。例如,多个线程可以共享同一个Runnable对象的实例变量。