1. 线程的创建方式
在 Java 中,创建线程的方式主要有两种:继承 Thread
类和实现 Runnable
接口。
1.1 继承 Thread
类
通过继承 Thread
类并重写 run()
方法来创建线程。run()
方法是线程执行的逻辑体。
public class MyThread extends Thread {private String name;public MyThread(String name) {this.name = name;}@Overridepublic void run() {for (int i = 1; i <= 100; i++) {System.out.println(name + "下载了" + i + "%");}}
}
测试代码:
public class ThreadTest {public static void main(String[] args) {MyThread mt = new MyThread("肖申克的救赎");mt.start();MyThread mt1 = new MyThread("当幸福来敲门");mt1.start();}
}
1.2 实现 Runnable
接口
通过实现 Runnable
接口并实现 run()
方法来创建线程。这种方式更灵活,因为 Java 不支持多继承,但可以实现多个接口。
public class DownLoad implements Runnable {private String name;public DownLoad(String name) {this.name = name;}@Overridepublic void run() {for (int i = 1; i <= 100; i++) {System.out.println(name + "下载了" + i + "%");}}
}
测试代码:
public class ThreadTest {public static void main(String[] args) {Thread t = new Thread(new DownLoad("肖申克的救赎"));Thread t1 = new Thread(new DownLoad("当幸福来敲门"));t.start();t1.start();}
}
1.3实现 Callable
接口
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;/*** 使用 Callable 创建线程*/
public class DownloadCallable implements Callable<String> {private String name; // 电影名称public DownloadCallable(String name) {this.name = name;}@Overridepublic String call() throws Exception {for (int i = 1; i <= 100; i++) {System.out.println(name + " 下载进度:" + i + "%");Thread.sleep(50); // 模拟下载耗时}return name + " 下载完成!";}
}/*** 测试代码*/
public class ThreadTest {public static void main(String[] args) {// 创建 Callable 任务DownloadCallable task1 = new DownloadCallable("肖申克的救赎");DownloadCallable task2 = new DownloadCallable("当幸福来敲门");// 使用 FutureTask 包装 Callable 任务FutureTask<String> futureTask1 = new FutureTask<>(task1);FutureTask<String> futureTask2 = new FutureTask<>(task2);// 创建线程并启动Thread thread1 = new Thread(futureTask1);Thread thread2 = new Thread(futureTask2);thread1.start();thread2.start();// 获取线程执行结果try {String result1 = futureTask1.get(); // 阻塞,直到线程1执行完毕String result2 = futureTask2.get(); // 阻塞,直到线程2执行完毕System.out.println(result1);System.out.println(result2);} catch (InterruptedException | ExecutionException e) {e.printStackTrace();}}
}
2. 线程的执行原理
线程的并发执行是通过多个线程不断切换 CPU 资源来实现的。由于切换速度非常快,我们感知到的是多个线程在并发执行。
3. 线程的生命周期
线程的生命周期包括以下几个状态:
-
新建(New):线程被创建,但尚未启动。
-
准备就绪(Runnable):线程调用了
start()
方法,具备执行的资格,但尚未获得 CPU 资源。 -
运行(Running):线程获得 CPU 资源,正在执行。
-
阻塞(Blocked):线程因某些原因(如等待锁、I/O 操作)暂时停止执行。
-
销毁(Terminated):线程执行完毕或被强制终止。
4. 并发与同步
在多线程编程中,共享资源可能会导致数据不一致的问题。为了解决这个问题,可以使用 synchronized
关键字来实现同步。
1.同步代码块
public class SaleTicketThread extends Thread {private String name;static int tickets = 100; // 共享资源static Object obj = new Object(); // 锁对象public SaleTicketThread(String name) {this.name = name;}@Overridepublic void run() {while (true) {synchronized (obj) {if (tickets > 0) {System.out.println(name + "卖出座位是" + (tickets--) + "号");} else {break;}}try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(name + "卖票结束");}
}
测试代码:
public class ThreadTest {public static void main(String[] args) {SaleTicketThread t1 = new SaleTicketThread("窗口1");SaleTicketThread t2 = new SaleTicketThread("窗口2");SaleTicketThread t3 = new SaleTicketThread("窗口3");SaleTicketThread t4 = new SaleTicketThread("窗口4");t1.start();t2.start();t3.start();t4.start();}
}
2.同步方法对象上
public class SaleTicket implements Runnable {/*** 多个线程共享的100张票*/int tickets = 100;//创建一个锁对象,这个对象是多个线程对象共享的数据Object obj = new Object();@Overridepublic void run() {//卖票是持续的while (true){if(saleTickets()){break;}try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName()+"卖票结束");}/*public boolean saleTickets(){synchronized (obj){boolean isFinish = false;if(tickets > 0){System.out.println(Thread.currentThread().getName()+"卖出座位是"+(tickets--)+"号");}else{isFinish = true;}return isFinish;}}*//**** @return 如果一个对象方法上有synchronized的话那么锁的对象就是this*/public synchronized boolean saleTickets(){//synchronized (obj){boolean isFinish = false;if(tickets > 0){System.out.println(Thread.currentThread().getName()+"卖出座位是"+(tickets--)+"号");}else{isFinish = true;}return isFinish;//}}
}
3.同步类方法上,那么锁对象就是类的类对象
public class SaleTicketThread extends Thread {private String name;/*** 定义共享的数据100张票*/static int tickets = 100;//创建一个锁对象,这个对象是多个线程对象共享的数据static Object obj = new Object();public SaleTicketThread(String name) {super(name);this.name = name;}@Overridepublic void run() {//卖票是持续的while (true){if(saleTickets()){break;}try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(name+"卖票结束");}public static synchronized boolean saleTickets(){boolean isFinish = false;if(tickets > 0){System.out.println(Thread.currentThread().getName()+"卖出座位是"+(tickets--)+"号");}else{isFinish = true;}return isFinish;}
}//测试代码:
public class ThreadTest {public static void main(String[] args) {SaleTicketThread t1 = new SaleTicketThread("窗口1");SaleTicketThread t2 = new SaleTicketThread("窗口2");SaleTicketThread t3 = new SaleTicketThread("窗口3");SaleTicketThread t4 = new SaleTicketThread("窗口4");t1.start();t2.start();t3.start();t4.start();}
}
5. 单例模式
单例模式是一种设计模式,确保一个类只有一个实例,并提供一个全局访问点。
单例模式的好处:
-
确保系统中只有一个实例,避免重复创建对象。
-
提供对唯一实例的受控访问。
-
节约系统资源,提高性能。
6 休眠
在做服务器端的程序的时候都需要给一个休眠的时间,在没有synchronized代码块里面会让出cpu的资源。
public static void main(String[] args) { |
休眠在同步代码块内不会让出cpu
synchronized (ojb){ |
7.使用继承Thread,实现Runnable,实现Callable接口3种方法创建线程的区别
继承性
- 继承 Thread 类:Java 是单继承语言,一个类继承了 Thread 类就无法再继承其他类,限制了类的扩展性。
- 实现 Runnable 接口:实现 Runnable 接口的类还可以继承其他类,更符合面向对象中的 “聚合” 思想,能让多个线程共享一个 Runnable 实例,资源共享性更好。
- 实现 Callable 接口:同样实现 Callable 接口的类也可以继承其他类,在实现多任务处理的同时保持了类的继承灵活性。
任务执行结果返回
- 继承 Thread 类和实现 Runnable 接口:这两种方式中的 run () 方法没有返回值,无法直接获取线程执行后的结果,如果需要获取结果,需通过共享变量等间接方式实现。
- 实现 Callable 接口:其 call () 方法允许有返回值,能更方便地获取线程执行的结果,可用于需要获取线程计算结果并进行后续处理的场景。
异常处理
- 继承 Thread 类和实现 Runnable 接口:在 run () 方法中只能通过 try-catch 捕获处理内部异常,无法向外抛出。
- 实现 Callable 接口:call () 方法可以声明抛出异常,由调用者进行处理,让异常处理更灵活、更可控。
启动方式
- 继承 Thread 类:直接创建子类实例后调用 start () 方法启动线程。
- 实现 Runnable 接口和 Callable 接口:需要将实现类的实例作为参数传入 Thread 类的构造函数,再调用 start () 方法启动线程。