在前面的学习中发现我们的聊天室功能只能有一个客户端接入服务端中,第二个客户端想要接入服务端中必须要等待第一个客户端输入结束才能接入。
这很明显不符合实际应用的开发,现在我们就来学习Java中一个重要的知识,多线程来解决这个问题。我们先来简单的了解一下多线程。
多线程
概念
线程:
一个顺序的单一的程序执行流程就是一个线程。代码一句一句的有先后顺序的执行。
多线程:
多个单一顺序执行的流程并发运行。造成"感官上同时运行"的效果。
并发:
多个线程实际运行是走走停停的。线程调度程序会将CPU运行时间划分为若干个时间片段并 尽可能均匀的分配给每个线程,拿到时间片的线程被CPU执行这段时间。当超时后线程调度 程序会再次分配一个时间片段给一个线程使得CPU执行它。如此反复。由于CPU执行时间在 纳秒级别,我们感觉不到切换线程运行的过程。所以微观上走走停停,宏观上感觉一起运行 的现象成为并发运行!
用途:
当出现多个代码片段执行顺序有冲突时,希望它们各干各的时就应当放在不同线程上"同时"运 行 一个线程可以运行,但是多个线程可以更快时,可以使用多线程运行
线程的生命周期图
线程的创建
方式一
继承Thread并重写run方法 定义一个线程类,重写run方法,在其中定义线程要执行的任务(希望和其他线程并发执行的任务)。
注:启动该线程要调用该线程的start方法,而不是run方法!!
public class ThreadDemo1 {
public static void main(String[] args) {
Thread thread1 = new MyThread1();
Thread thread2 = new MyTread2();
thread1.start();
thread2.start();
}
}
class MyThread1 extends Thread {
@Override
public void run() {
for (int i = 0; i < 100000; i++) {
System.out.println("你是谁啊?");
}
}
}
class MyTread2 extends Thread {
@Override
public void run() {
for (int i = 0; i < 100000; i++) {
System.out.println("开门!查水表的!");
}
}
}
优缺点
优点:
简单:在于结构简单,便于匿名内部类形式创建。
缺点:
继承限制:直接继承线程,会导致不能在继承其他类去复用方法,这在实际开发中是非常不便的。
耦合度高:定义线程的同时重写了run方法,会导致线程与线程任务绑定在了一起,不利于线程的重用,提高耦合度。
方式二
实现Runnable接口单独定义线程任务
public class ThreadDemo2 {
public static void main(String[] args) {
Thread thread1 = new Thread(new MyRunnable1());
Thread thread2 = new Thread(new MyRunnable2());
thread1.start();
thread2.start();
}
}
class MyRunnable1 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("你是谁啊?");
}
}
}
class MyRunnable2 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("开门!查水表的!");
}
}
}
优缺点
优点:
解耦:实现了Runnable
接口的类只是定义了一个可以执行的任务,并不负责线程的执行。这样可以将业务逻辑代码与线程的执行机制分离开来,降低了代码的耦合度。
灵活性:通过实现Runnable
接口创建的线程对象可以被Thread
类或其他线程执行器(如ExecutorService
)来执行,这提供了更多的灵活性。
多重继承的替代:在Java中不支持多重继承,但通过实现接口的方式可以避开这个限制,使得类在继承其他类的同时还能通过实现接口来拥有多线程的能力。
缺点:
编写相对复杂:相对于直接继承Thread
类,实现Runnable
接口并手动创建Thread
实例的方式稍显复杂,因为需要显式地创建Thread
对象。