Java 线程
线程使程序能够通过同时执行多个任务而更有效地运行。
线程可用于在不中断主程序的情况下在后台执行复杂的任务。
创建线程
有两种创建线程的方式。
- 扩展Thread类
可以通过扩展Thread类并覆盖其run()方法来创建线程:
public class MyThread extends Thread {public void run() {System.out.println("This code is running in a thread");}
}
- 实现Runnable接口
另一种创建线程的方式是实现Runnable接口:
public class MyRunnable implements Runnable {public void run() {System.out.println("This code is running in a thread");}
}
运行线程
- 扩展Thread类
如果类扩展Thread类,则可以通过创建类的实例并调用其start()方法来运行线程:
public class Main {public static void main(String[] args) {MyThread myThread = new MyThread();myThread.start();System.out.println("This code is outside of the thread");}
}
- 实现Runnable接口
如果类实现了Runnable接口,则可以通过将类的实例传递给Thread对象的构造函数,然后调用线程的start()方法来运行线程:
public class Main {public static void main(String[] args) {MyRunnable myRunnable = new MyRunnable();Thread thread = new Thread(myRunnable);thread.start();System.out.println("This code is outside of the thread");}
}
区分“扩展”和“实现”线程
主要区别在于,当一个类扩展Thread类时,无法扩展任何其他类,但通过实现Runnable接口,可以扩展另一个类,例如:
class MyClass extends OtherClass implements Runnable
并发问题
因为线程与程序的其他部分同时运行,所以无法知道代码将按照什么顺序运行。当线程和主程序同时读取和写入相同的变量时,值是不可预测的。由此导致的问题称为并发问题。
示例
一个变量amount值不可预测的代码示例:
public class Main extends Thread {public static int amount = 0;public static void main(String[] args) {Main thread = new Main();thread.start();System.out.println(amount);amount++;System.out.println(amount);}public void run() {amount++;}
}
为避免并发问题,最好尽可能少地在线程之间共享属性。如果需要共享属性,一种可能的解决方案是在使用线程可以更改的任何属性之前,使用线程的isAlive()方法检查线程是否已完成运行。
示例
使用isAlive()防止并发问题:
public class Main extends Thread {public static int amount = 0;public static void main(String[] args) {Main thread = new Main();thread.start();// 等待线程完成while (thread.isAlive()) {System.out.println("Waiting...");}// 更新amount并打印其值System.out.println("Main program: " + amount);amount++;System.out.println("Main program: " + amount);}public void run() {amount++;}
}
线程池
线程池是一种管理线程的资源。它允许您创建并维护一组可重用的线程。使用线程池可以提高应用程序的性能和效率。
线程安全
线程安全是指多个线程可以访问和修改数据而不导致数据损坏。使数据线程安全的一种方法是使用同步。同步是一种机制,它允许线程一次一个地访问共享数据。
常见的线程安全问题
- 竞态条件:当多个线程同时访问共享数据并尝试对其进行更改时,就会发生竞态条件。这可能导致数据损坏。
- 原子性:原子操作是指不可分割的操作。当多个线程尝试同时执行原子操作时,可能会导致数据损坏。
- 可见性:当一个线程对共享数据进行更改时,其他线程必须能够看到这些更改。
避免线程安全问题
- 使用同步
- 使用不可变对象
- 使用原子操作
Java Lambda表达式
Lambda表达式简介
Lambda表达式是在Java 8中引入的。Lambda表达式是一小段代码块,它接受参数并返回一个值。Lambda表达式类似于方法,但它们不需要名称,并且可以直接在方法体中实现。
Lambda表达式的语法
最简单的Lambda表达式包含一个参数和一个表达式:
参数 -> 表达式
要使用多个参数,请将它们放在括号中:
(参数1, 参数2) -> 表达式
表达式是有限制的。它们必须立即返回一个值,并且不能包含变量、赋值或if或for等语句。为了执行更复杂的操作,可以使用带有花括号的代码块。如果Lambda表达式需要返回一个值,那么代码块应该有一个return语句。
(参数1, 参数2) -> { 代码块 }
Lambda表达式的使用
Lambda表达式通常作为参数传递给函数。在以下示例中,Lambda表达式作为参数传递给ArrayList的forEach()方法,以打印列表中的每个项:
import java.util.ArrayList;public class Main {public static void main(String[] args) {ArrayList<Integer> numbers = new ArrayList<>();numbers.add(5);numbers.add(9);numbers.add(8);numbers.add(1);numbers.forEach((n) -> { System.out.println(n); });}
}
Lambda表达式的存储
如果变量的类型是仅具有一个方法的接口,那么Lambda表达式可以存储在变量中。Lambda表达式应该具有与该方法相同数量的参数和相同的返回类型。Java内置了许多这类接口,如Consumer接口(在java.util包中),它被列表使用。
import java.util.ArrayList;
import java.util.function.Consumer;public class Main {public static void main(String[] args) {ArrayList<Integer> numbers = new ArrayList<>();numbers.add(5);numbers.add(9);numbers.add(8);numbers.add(1);Consumer<Integer> method = (n) -> { System.out.println(n); };numbers.forEach(method);}
}
Lambda表达式作为方法参数
要在方法中使用Lambda表达式,该方法应该有一个以单一方法接口作为其类型的参数。调用接口的方法将运行Lambda表达式。
interface StringFunction {String run(String str);
}public class Main {public static void main(String[] args) {StringFunction exclaim = (s) -> s + "!";StringFunction ask = (s) -> s + "?";printFormatted("Hello", exclaim);printFormatted("Hello", ask);}public static void printFormatted(String str, StringFunction format) {String result = format.run(str);System.out.println(result);}
}
Lambda表达式的优势
- 简化代码
- 提高可读性
- 增强代码的表达力
Lambda
表达式是 Java 8
中引入的一项强大功能,可以简化代码并提高可读性。它们是函数式编程的重要组成部分,可以用于各种任务,例如数据处理、事件处理和流处理。
最后
为了方便其他设备和平台的小伙伴观看往期文章:
微信公众号搜索:Let us Coding
,关注后即可获取最新文章推送
看完如果觉得有帮助,欢迎 点赞、收藏、关注