多线程编程
java多线程知识点
多线程编程
一、什么是多线程
多线程是指在一个程序中同时执行多个独立的任务或操作。每个任务或操作都是由一个单独的线程来执行,而这些线程共享程序的资源和内存空间。与单线程相比,多线程可以提高程序的运行效率和响应速度,因为它可以充分利用 CPU 的多核处理能力,同时也可以避免某些操作阻塞其他操作的问题。
1.1、多线程的概念和基本原理
多线程是一种并发编程的技术,它允许程序在同一个进程中同时执行多个独立的任务或操作。每个任务都由一个单独的线程来执行,而这些线程共享程序的资源和内存空间。
多线程的基本原理是通过将程序分成多个子任务,并创建对应数量的线程来同时执行这些子任务。每个线程都有自己的堆栈、寄存器和指令计数器等状态信息,可以独立地运行代码。不同线程之间可以进行通信和协调,通过锁、信号量、条件变量等机制来实现数据同步和互斥访问。
多线程在操作系统级别实现,通过操作系统提供的API(如POSIX标准中提供的pthread库)进行创建、管理和控制。在高级编程语言中也提供了相应的库或框架来支持多线程编程,如Java中的Thread类、C#中的Task类等。
1.2、多线程与单线程的区别
执行方式不同:单线程只能执行一个任务,而多线程可以同时执行多个任务。
程序性能不同:多线程可以充分利用CPU资源,提高程序运行效率,而单线程则无法充分利用CPU资源,导致程序运行速度变慢。
内存占用不同:多线程需要占用更多的内存空间和系统资源(如CPU时间片),因此对于内存有限或资源受限的应用场景,单线程更为适合。
编写难度不同:在编写过程中,多线程需要考虑到并发、数据安全等问题,需要对程序设计有一定了解和经验。而单线程相对来说比较简单易于编写。
错误处理方式不同:在单线程中如果出现异常错误会直接导致程序崩溃,在多线程中则需要使用特殊手段处理错误以保证程序稳定性。
二、Java多线程
2.1、Java多线程的生命周期
线程是一个动态执行的过程,它也有一个从产生到死亡的过程。
下图显示了一个线程完整的生命周期。
-
新建状态:
使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
-
就绪状态:
当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
-
运行状态:
如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
-
阻塞状态:
如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
- 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
- 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
- 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
-
死亡状态:
一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
2.2、Java多线程的优先级
每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。
Java 线程的优先级是一个整数,其取值范围是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。
默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(5)。
具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。
2.3、Java创建线程
线程创建方式
Java 提供了三种创建线程的方法:
- 通过实现 Runnable 接口;
- 通过继承 Thread 类本身;
- 通过 Callable 和 Future 创建线程。
通过继承 Thread 类本身来创建线程(不推荐)
单继承局限性
public class ThreadDemo extends Thread{
//重写Thread的run方法
@Override
public void run() {
int x=300;
while (x>0){
x--;
System.out.println("普线程:通过继承 Thread 类本身来创建线程");
}
}
//主线程
public static void main(String[] args) {
//开启线程 调用run方法
new ThreadDemo().start();
int x=300;
while (x>0){
x--;
System.out.println("主线程:我在学习线程!");
}
}
}
结果(穿插进行)
通过实现 Runnable 接口来创建线程(推荐)
一个对象可被多个线程使用
public class ThreadDemo1 implements Runnable{
//重写Thread的run方法
@Override
public void run() {
int x=300;
while (x>0){
x--;
System.out.println("普线程:通过实现 Runnable 接口来创建线程");
}
}
//主线程
public static void main(String[] args) {
//开启线程 调用run方法
new Thread(new ThreadDemo1()).start();
int x=300;
while (x>0){
x--;
System.out.println("主线程:我在学习线程!");
}
}
}
结果
初识并发问题
public class ThreadDemo2 implements Runnable{
//公共资源
private int num = 300;
//重写Thread的run方法
@Override
public void run() {
while (num>0){
//线程延时
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
num--;
System.out.println(Thread.currentThread().getName()+"拿到一张票,还剩:"+num+"张");
}
}
//主线程
public static void main(String[] args) {
//创建对象
ThreadDemo2 threadDemo2 = new ThreadDemo2();
//开启线程 调用run方法 可以取别名
new Thread(threadDemo2,"小红").start();
new Thread(threadDemo2,"小敏").start();
new Thread(threadDemo2,"小百").start();
new Thread(threadDemo2,"小希").start();
}
}
结果
发现线程并发问题
龟兔赛跑
public class Race implements Runnable{
//胜利者
private String winner=null;
//跑步
@Override
public void run() {
for (int i = 0;i <= 1000;i++){
if (winner==null){
//如果是兔子则每跑100步睡0.01毫秒
if (Thread.currentThread().getName().equals("兔子")&&i%500==0){
try {
Thread.sleep((long) 0.01);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//判断跑到一百的胜利者
if (ifWinner(i)){
System.out.println("胜利者是:"+winner);
break;
}
System.out.println(Thread.currentThread().getName()+"跑到了"+i+"米");
}
}
}
private Boolean ifWinner(int i){
if (i==1000){
winner=Thread.currentThread().getName();
return true;
}
return false;
}
}
结果:
通过 Callable 和 Future 创建线程
Callable接口可以看作是Runnable接口的补充,call方法带有返回值,并且可以抛出异常。
newFixedThreadPool线程池
//用Callable创建的线程在线程池中运行
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable c1=new MyCallable();
//MyCallable c2=new MyCallable();
ExecutorService service= Executors.newFixedThreadPool(2);
//线程池的方式可以两个线程可以采用同一个Callable对像,也可以一个线程采用一个Callable对像
Future<Integer> f1=service.submit(c1);//不需要FutureTask对象也不需要Thread对象
Future<Integer> f2=service.submit(c1);
Integer rs1=f1.get();
System.out.println(rs1);
Integer rs2=f2.get();
System.out.println(rs2);
//service.shutdown();//关闭线程池
}
}
class MyCallable implements Callable<Integer> {
public Integer call() throws Exception {
int i=0;
for(;i<100;i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
return i;
}
}
静态代理
接口-》真实角色-》中介-》操作
三、Lambda表达式
3.1概念介绍
Java Lambda表达式是Java 8中最重要的新特性之一。
它们是一种可传递的匿名函数,可以作为参数传递给方法或存储在变量中,因此可以在需要的时候调用它们。
Lambda表达式的主要目的是简化Java代码,使其更易于阅读和编写。
Lambda表达式的语法非常简洁和清晰。它们由参数列表、箭头符号和方法体组成。参数列表指定传递给Lambda表达式的参数,箭头符号 “->” 分隔参数列表和方法体,方法体则包含Lambda表达式要执行的代码。
3.2 简单示例
下面是一个简单的Lambda表达式示例:
(int x, int y) -> x + y
这个Lambda表达式接受两个整数参数 x 和 y,并返回它们的和。可以将这个Lambda表达式存储在一个变量中,例如:
IntBinaryOperator add = (int x, int y) -> x + y;
这个代码创建了一个名为add的变量,它的类型是IntBinaryOperator,它接受两个整数参数并返回一个整数结果。
该变量被初始化为一个Lambda表达式,该表达式实现了相同的功能,即将两个整数相加。
3.3 Lambda优点
Lambda表达式的主要优点包括:
简化代码:Lambda表达式可以将冗长复杂的代码简化为几行简洁的代码。
可读性:Lambda表达式可以使代码更易于阅读和理解,因为它们更接近自然语言。
可传递性:Lambda表达式可以作为参数传递给方法或存储在变量中,使代码更具可重用性和灵活性。
并行处理:Lambda表达式可以与Stream API一起使用,使Java程序更容易地进行并行处理。
Lambda表达式是Java 8中最重要的新特性之一,它们为我们提供了一种更简单、更灵活、更易于使用的编程方式。
3.4集合操作
Lambda表达式可以与Java 8的新集合操作方法(如stream()和forEach())一起使用,使集合的处理更加简单、灵活和易于读写。
例如,假设有一个字符串列表,想要对该列表中的所有元素进行大写转换并输出到控制台上,可以使用以下代码:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.stream().map(String::toUpperCase).forEach(System.out::println);
这里,使用了stream()方法将列表转换为一个流,然后使用map()方法将每个字符串转换为大写形式,最后使用forEach()方法将结果输出到控制台。
3.5多线程编程
new Thread(() -> {
// 执行后台任务
// ...
// 通知主线程任务已完成
}).start();
3.6 事件处理
Lambda表达式可以作为事件监听器传递给GUI组件等,使事件处理更加简单和可读。
例如,假设我们有一个按钮,需要在用户单击它时执行某些操作。可以使用以下代码将Lambda表达式作为事件监听器传递给该按钮:
button.addActionListener(event -> {
// 处理按钮单击事件
// ...
});
这里,使用了Java中的ActionListener接口,并将一个Lambda表达式作为参数传递给它,该表达式将在用户单击按钮时执行。
3.7 排序
Lambda表达式可以用于Java中的排序算法中,使排序更加灵活和可读。
例如,假设我们有一个Person对象的列表,需要按照年龄进行排序。可以使用以下代码将Lambda表达式作为排序算法的参数传递给Collections.sort()方法:
List<Person> people = Arrays.asList(new Person("Alice", 25), new Person("Bob", 30), new Person("Charlie", 20));
Collections.sort(people, (p1, p2) -> p1.getAge() - p2.getAge());
这里,我们使用了Java中的Collections类的sort()方法,并将一个Lambda表达式作为参数传递给它,该表达式将比较两个Person对象的年龄并返回一个整数值,以指示它们的排序顺序。
3.8 过滤
Lambda表达式可以用于过滤集合中的元素,使代码更加简单和可读。
例如,假设有一个整数列表,需要过滤掉其中的偶数。可以使用以下代码将Lambda表达式作为过滤器传递给Java中的stream()方法:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
List<Integer> oddNumbers = numbers.stream().filter(n -> n % 2 != 0).collect(Collectors.toList());
这里,使用了Java中的stream()方法将列表转换为一个流,然后使用filter()方法过滤掉其中的偶数,最后使用collect()方法将过滤后的结果转换为一个新的列表。
3.9 映射
Lambda表达式可以用于将一个集合中的元素映射到另一个集合中,使代码更加简单和可读。
例如,假设我们有一个字符串列表,需要将其中的每个字符串转换为大写形式并存储到另一个列表中。可以使用以下代码将Lambda表达式作为映射器传递给Java中的stream()方法:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
List<String> upperCaseNames = names.stream().map(String::toUpperCase).collect(Collectors.toList());
这里,我们使用了Java中的stream()方法将列表转换为一个流,然后使用map()方法将每个字符串转换为大写形式,最后使用collect()方法将转换后的结果存储到一个新的列表中。
3.10 聚合
Lambda表达式可以用于聚合集合中的元素,例如,计算集合中的元素之和、平均值、最大值、最小值等。
以下是一个计算列表中元素之和的示例:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream().reduce(0, (a, b) -> a + b);
System.out.println("Sum of numbers: " + sum);
这里,使用了Java中的stream()方法将列表转换为一个流,并使用reduce()方法计算流中元素的总和。reduce()方法接受两个参数:起始值和一个BinaryOperator类型的Lambda表达式。Lambda表达式将两个元素相加并返回它们的和。在这个例子中,将起始值设置为0,表示计算从0开始的累加和。
除了计算元素之和之外,还可以使用Lambda表达式计算元素的平均值、最大值、最小值等。以下是一个计算列表中元素平均值的示例:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
double average = numbers.stream().mapToInt(Integer::intValue).average().orElse(0.0);
System.out.println("Average of numbers: " + average);
首先使用mapToInt()方法将流中的元素转换为int类型,然后使用average()方法计算这些元素的平均值。如果列表为空,则orElse()方法返回默认值0.0。
3.11 函数式编程
Lambda表达式可以使Java更加接近函数式编程,使代码更加简洁和易于理解。
例如,假设有一个接口,其中包含一个抽象方法,需要在程序中实现该接口并调用该方法。可以使用以下代码将Lambda表达式作为接口实现传递给该方法:
interface MyInterface {
int doSomething(int x, int y);
}
MyInterface myLambda = (x, y) -> x + y;
int result = myLambda.doSomething(3, 4);
这里,定义了一个名为myLambda的变量,它的类型是MyInterface,它接受两个整数参数并返回它们的和。然后,我们调用myLambda的doSomething()方法,并传递两个整数参数,得到它们的和并将结果存储在result变量中。
3.12 数据库操作
Lambda表达式可以与Java中的数据库操作API(如JDBC和Hibernate)一起使用,使数据库操作更加简单和可读。
例如,假设有一个数据库表,需要查询其中的数据并输出到控制台上。可以使用以下代码将Lambda表达式作为查询操作的参数传递给数据库API:
try (Connection conn = DriverManager.getConnection(url, username, password);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM my_table")) {
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
System.out.println("id: " + id + ", name: " + name);
}
}
这里,使用了Java中的JDBC API,并将一个Lambda表达式作为查询操作的参数传递给executeQuery()方法,该表达式将处理查询结果集并输出到控制台上。
3.13 并行计算
Lambda表达式可以与Java中的并行计算API(如Java 8中的Parallel Streams和Fork/Join框架)一起使用,使计算更加高效和快速。
例如,假设有一个大型的整数列表,需要计算其中所有元素的平方和。可以使用以下代码将Lambda表达式作为计算器传递给Java 8中的Parallel Streams API:
List<Integer> numbers = new ArrayList<>();
for (int i = 1; i <= 1000000; i++) {
numbers.add(i);
}
long sum = numbers.parallelStream().mapToLong(i -> i * i).sum();
System.out.println("Sum of squares: " + sum);
这里,使用了Java 8中的Parallel Streams API,它将列表转换为一个并行流,并使用mapToLong()方法计算每个元素的平方值,最后使用sum()方法将它们加起来得到总和。在此过程中,计算将在多个线程上并行执行,从而提高了计算效率。
3.14 GUI编程
Lambda表达式可以与Java中的GUI编程框架(如JavaFX和Swing)一起使用,使GUI编程更加简单和可读。
例如,假设有一个JavaFX应用程序,需要在用户单击按钮时执行一些操作。可以使用以下代码将Lambda表达式作为事件处理器传递给JavaFX框架:
Button button = new Button("Click me!");
button.setOnAction(event -> System.out.println("Button clicked!"));
这里,使用了JavaFX框架,并将一个Lambda表达式作为按钮的事件处理器传递给setOnAction()方法。该表达式将在用户单击按钮时执行,并输出一条消息到控制台上。
3.15 测试
Lambda表达式可以用于编写更加简洁和可读的单元测试代码,使测试更加容易理解和维护。
例如,假设有一个函数,需要进行单元测试以确保其正确性。可以使用以下代码将Lambda表达式作为测试断言传递给JUnit测试框架:
@Test
public void testMyFunction() {
int result = myFunction(2, 3);
assertEquals(6, result, () -> "myFunction(2, 3) should return 6");
}
这里,使用了JUnit测试框架,并将一个Lambda表达式作为测试断言传递给assertEquals()方法。该表达式将在测试失败时计算并返回错误消息,以帮助定位和修复问题。
3.16 总结
Lambda表达式是Java 8中最强大和灵活的新特性之一,它可以用于各种不同的编程任务,使代码更加简单、灵活和易于读写。
Lambda表达式的语法非常简洁,通常由一个参数列表、一个箭头符号和一个表达式主体组成。
例如,以下是一个简单的Lambda表达式:
x -> x * x
该表达式接受一个参数x,将其平方并返回结果。
Lambda表达式可以用于各种不同的编程任务,包括函数式编程、集合处理、数据库操作、Web开发、并行计算、GUI编程、测试等。使用Lambda表达式可以使代码更加简单、灵活和易于读写,并帮助开发人员减少代码的冗余和重复。
除了Lambda表达式之外,Java 8还引入了其他重要的新特性,例如Stream API、接口默认方法、方法引用、Optional类型等。这些新特性一起使Java变得更加现代化、强大和易于使用。
总之,Lambda表达式是Java编程中不可或缺的一部分,它使Java变得更加现代化、灵活和强大,并且是Java 8中最重要的新特性之一。
四、线程状态
4.1、start启动线程
public class ThreadTest02 {
public static void main(String[] args) {
MyThread t = new MyThread();
// 启动线程
//t.run(); // 不会启动线程,不会分配新的分支栈。(这种方式就是单线程。)
t.start();
// 这里的代码还是运行在主线程中。
for(int i = 0; i < 1000; i++){
System.out.println("主线程--->" + i);
}
}
}
class MyThread extends Thread {
@Override
public void run() {
// 编写程序,这段程序运行在分支线程中(分支栈)。
for(int i = 0; i < 1000; i++){
System.out.println("分支线程--->" + i);
}
}
}
注意:
t.run() 不会启动线程,只是普通的调用方法而已。不会分配新的分支栈。(这种方式就是单线程。)
t.start() 方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。
这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开出来,start()方法就结束了。线程就启动成功了。
启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部(压栈)。
run方法在分支栈的栈底部,main方法在主栈的栈底部。run和main是平级的。
4.2、线程的sleep方法
方法名 | 作用 |
---|---|
static void sleep(long millis) | 让当前线程休眠millis秒 |
静态方法:Thread.sleep(1000);
参数是毫秒
作用: 让当前线程进入休眠,进入“阻塞状态”,放弃占有CPU时间片,让给其它线程使用。
这行代码出现在A线程中,A线程就会进入休眠。
这行代码出现在B线程中,B线程就会进入休眠。
Thread.sleep()方法,可以做到这种效果:
间隔特定的时间,去执行一段特定的代码,每隔多久执行一次。
public class ThreadTest06 {
public static void main(String[] args) {
//每打印一个数字睡1s
for(int i = 0; i < 10; i++){
System.out.println(Thread.currentThread().getName() + "--->" + i);
// 睡眠1秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程中断sleep()的方法
方法名 | 作用 |
---|---|
void interrupt() | 终止线程的睡眠 |
public class ThreadTest08 {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable2());
t.setName("t");
t.start();
// 希望5秒之后,t线程醒来(5秒之后主线程手里的活儿干完了。)
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 终断t线程的睡眠(这种终断睡眠的方式依靠了java的异常处理机制。)
t.interrupt();
}
}
class MyRunnable2 implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "---> begin");
try {
// 睡眠1年
Thread.sleep(1000 * 60 * 60 * 24 * 365);
} catch (InterruptedException e) {
e.printStackTrace();
}
//1年之后才会执行这里
System.out.println(Thread.currentThread().getName() + "---> end");
}
注意:
sleep方法并不释放资源
4.3 、终止线程的执行
强行终止(不推荐使用)
stop()方法
public class ThreadTest09 {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable3());
t.setName("t");
t.start();
// 模拟5秒
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 5秒之后强行终止t线程
t.stop(); // 已过时(不建议使用。)
}
}
class MyRunnable3 implements Runnable {
@Override
public void run() {
for(int i = 0; i < 10; i++){
System.out.println(Thread.currentThread().getName() + "--->" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
注意:
这种方式存在很大的缺点:容易丢失数据。
因为这种方式是直接将线程杀死了,线程没有保存的数据将会丢失。不建议使用。
合理结束
定义一个终止标识符
public class ThreadTest10 {
public static void main(String[] args) {
MyRunable4 r = new MyRunable4();
Thread t = new Thread(r);
t.setName("t");
t.start();
// 模拟5秒
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 终止线程
// 你想要什么时候终止t的执行,那么你把标记修改为false,就结束了。
r.run = false;
}
}
class MyRunable4 implements Runnable {
// 打一个布尔标记
boolean run = true;
@Override
public void run() {
for (int i = 0; i < 10; i++){
if(run){
System.out.println(Thread.currentThread().getName() + "--->" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
// return就结束了,你在结束之前还有什么没保存的。
// 在这里可以保存呀。
//save....
//终止当前线程
return;
}
}
}
}
为什么if()语句要在循环里面?
由于一个线程一直运行此程序,要是if判断在外面只会在启动线程时判断并不会结束,因此需要每次循环判断一下标记。
补充小知识:线程调度(了解)
1.常见的线程调度模型有哪些?
(1)抢占式调度模型:
那个线程的优先级比较高,抢到的CPU时间片的概率就高一些/多一些。
java采用的就是抢占式调度模型。
(2)均分式调度模型:
平均分配CPU时间片。每个线程占有的CPU时间片时间长度一样。
平均分配,一切平等。
有一些编程语言,线程调度模型采用的是这种方式。
4.4、线程调度
优先级:
实例方法:
常量:
常量名 | 备注 |
---|---|
static int MAX_PRIORITY | 最高优先级(10) |
static int MIN_PRIORITY | 最低优先级(1) |
static int NORM_PRIORITY | 默认优先级(5) |
方法:
方法名 | 作用 |
---|---|
int getPriority() | 获得线程优先级 |
void setPriority(int newPriority) | 设置线程优先级 |
- 最低优先级1
- 默认优先级是5,main是5
- 最高优先级10
优先级比较高的获取CPU时间片可能会多一些。(但也不完全是,大概率是多的。)
public class ThreadTest11 {
public static void main(String[] args) {
System.out.println("最高优先级:" + Thread.MAX_PRIORITY);//最高优先级:10
System.out.println("最低优先级:" + Thread.MIN_PRIORITY);//最低优先级:1
System.out.println("默认优先级:" + Thread.NORM_PRIORITY);//默认优先级:5
// main线程的默认优先级是:5
System.out.println(hread.currentThread().getName() + "线程的默认优先级是:" + currentThread.getPriority());
Thread t = new Thread(new MyRunnable5());
t.setPriority(10);
t.setName("t");
t.start();
// 优先级较高的,只是抢到的CPU时间片相对多一些。
// 大概率方向更偏向于优先级比较高的。
for(int i = 0; i < 10000; i++){
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
}
}
class MyRunnable5 implements Runnable {
@Override
public void run() {
for(int i = 0; i < 10000; i++){
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
}
}
礼让线程:
静态方法:
方法名 | 作用 |
---|---|
static void yield() | 让位方法,当前线程暂停,回到就绪状态,让给其它线程。 |
public class ThreadTest12 {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable6());
t.setName("t");
t.start();
for(int i = 1; i <= 10000; i++) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}
class MyRunnable6 implements Runnable {
@Override
public void run() {
for(int i = 1; i <= 10000; i++) {
//每100个让位一次。
if(i % 100 == 0){
Thread.yield(); // 当前线程暂停一下,让给主线程。
}
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}
yield()方法不是阻塞方法。让当前线程让位,让给其它线程使用。
yield()方法的执行会让当前线程从“运行状态”回到“就绪状态”。
注意:在回到就绪之后,有可能还会再次抢到。
插队线程:
实例方法:
方法名 | 作用 |
---|---|
void join() | 将一个线程合并到当前线程中,当前线程受阻塞,加入的线程执行直到结束 |
void join(long millis) | 接上条,等待该线程终止的时间最长为 millis 毫秒 |
void join(long millis, int nanos) | 接第一条,等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒 |
public class ThreadTest13 {
public static void main(String[] args) {
System.out.println("main begin");
Thread t = new Thread(new MyRunnable7());
t.setName("t");
t.start();
//合并线程
try {
t.join(); // t合并到当前线程中,当前线程受阻塞,t线程执行直到结束。
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main over");
}
}
class MyRunnable7 implements Runnable {
@Override
public void run() {
for(int i = 0; i < 10000; i++){
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}
注意: 一个线程.join(),当前线程会进入”阻塞状态
“。等待加入线程执行完!
4.5、守护线程与用户线程
thread.setDaemon(true);开启守护线程,默认为flase,守护线程只有在所有用户线程结束才会结束
public class Daemon {
static class You implements Runnable{
@Override
public void run() {
for (int i=0;i<300;i++){
System.out.println("又活了一天");
}
System.out.println("GoodBye Word!");
}
}
static class God implements Runnable{
@Override
public void run() {
while (true){
System.out.println("God 永远与你同在 ");
}
}
}
public static void main(String[] args) {
You you = new You();
God god = new God();
Thread thread = new Thread(god);
//设置为守护线程 默认为flase为用户线程
thread.setDaemon(true);
thread.start();
new Thread(you).start();
}
}
五、线程安全
5.1、数据的安全问题
什么时候数据在多线程并发的环境下会存在安全问题呢?★★★★★
满足三个条件:
- 条件1:多线程并发。
- 条件2:有共享数据。
- 条件3:共享数据有修改的行为。
满足以上3个条件之后,就会存在线程安全问题
class TestDemo{
//账户
static class Account{
int meany;
public int getMeany() {
return meany;
}
public void setMeany(int meany) {
this.meany = meany;
}
}
//银行
static class Bank implements Runnable{
Account account = null;
int xMeany = 0;
public Bank(Account account) {
this.account = account;
}
public int getxMeany() {
return xMeany;
}
public void setxMeany(int xMeany) {
this.xMeany = xMeany;
}
@Override
public void run() {
try {
//取款操作
withdrawMoney(xMeany);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//取钱操作
public void withdrawMoney(int xMeany) throws InterruptedException {
if (account.meany-xMeany>=0){
//放大问题的发生性
Thread.sleep(100);
account.meany-=xMeany;
System.out.println(Thread.currentThread().getName()+"取出了"+xMeany+"元");
}
System.out.println("余额剩余:"+account.getMeany());
}
}
public static void main(String[] args) throws InterruptedException {
//一个银行账户
Account account = new Account();
account.setMeany(100);
//一个银行对象
Bank bank = new Bank(account);
bank.setxMeany(50);
//三个线程共同取钱
new Thread(bank,"boy").start();
new Thread(bank,"YOU").start();
new Thread(bank,"girl").start();
}
}
结果
怎么解决线程安全问题呢?
当多线程并发的环境下,有共享数据,并且这个数据还会被修改,此时就存在线程安全问题,怎么解决这个问题?
线程排队执行。(不能并发)。用排队执行解决线程安全问题。
这种机制被称为:线程同步机制。
专业术语叫做:线程同步,实际上就是线程不能并发了,线程必须排队执行。
线程同步就是线程排队了,线程排队了就会 牺牲一部分效率 ,没办法,数据安全第一位,只有数据安全了,我们才可以谈效率。数据不安全,没有效率的事儿。
4.两个专业术语:
异步编程模型:
线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,谁也不需要等谁,这种编程模型叫做:异步编程模型。
其实就是:多线程并发(效率较高。)
异步就是并发。
同步编程模型:
线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行结束,或者说在t2线程执行的时候,必须等待t1线程执行结束,两个线程之间发生了等待关系,这就是同步编程模型。
效率较低。线程排队执行。
同步就是排队。
5.2、synchronized-线程同步
线程同步机制的语法是:
synchronized(){
// 线程同步代码块。
}
重点:
synchronized后面小括号() 中传的这个“数据”是相当关键的。这个数据必须是 多线程共享 的数据。才能达到多线程排队。
class TestDemo{
//账户
static class Account{
int meany;
public int getMeany() {
return meany;
}
public void setMeany(int meany) {
this.meany = meany;
}
}
//银行
static class Bank implements Runnable{
Account account = null;
int xMeany = 0;
public Bank(Account account) {
this.account = account;
}
public int getxMeany() {
return xMeany;
}
public void setxMeany(int xMeany) {
this.xMeany = xMeany;
}
@Override
public synchronized void run() {
try {
//取款操作
withdrawMoney(xMeany);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//取钱操作
public void withdrawMoney(int xMeany) throws InterruptedException {
if (account.meany-xMeany>=0){
//放大问题的发生性
Thread.sleep(100);
account.meany-=xMeany;
System.out.println(Thread.currentThread().getName()+"取出了"+xMeany+"元");
}
System.out.println("余额剩余:"+account.getMeany());
}
}
public static void main(String[] args) throws InterruptedException {
//一个银行账户
Account account = new Account();
account.setMeany(100);
//一个银行对象
Bank bank = new Bank(account);
bank.setxMeany(50);
//三个线程共同取钱
new Thread(bank,"boy").start();
new Thread(bank,"YOU").start();
new Thread(bank,"girl").start();
}
}
结果
()中写什么?
那要看你想让哪些线程同步。
假设t1、t2、t3、t4、t5,有5个线程,你只希望t1 t2 t3排队,t4 t5不需要排队。怎么办?
你一定要在()中写一个t1 t2 t3共享的对象。而这个对象对于t4 t5来说不是共享的。
这里的共享对象是:账户对象。
账户对象是共享的,那么this就是账户对象!!!
()不一定是this,这里只要是多线程共享的那个对象就行。
注意:
在java语言中,任何一个对象都有“一把锁”,其实这把锁就是标记。(只是把它叫做锁。)
100个对象,100把锁。1个对象1把锁。
class TestDemo{
//账户
static class Account{
int meany;
public int getMeany() {
return meany;
}
public void setMeany(int meany) {
this.meany = meany;
}
}
//银行
static class Bank implements Runnable{
Account account = null;
int xMeany = 0;
public Bank(Account account) {
this.account = account;
}
public int getxMeany() {
return xMeany;
}
public void setxMeany(int xMeany) {
this.xMeany = xMeany;
}
@Override
public void run() {
try {
//取款操作
withdrawMoney(xMeany);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//取钱操作
public void withdrawMoney(int xMeany) throws InterruptedException {
//锁的是资源的删改
synchronized (account){
if (account.meany-xMeany>=0){
//放大问题的发生性
Thread.sleep(100);
account.meany-=xMeany;
System.out.println(Thread.currentThread().getName()+"取出了"+xMeany+"元");
}
System.out.println("余额剩余:"+account.getMeany());
}
}
}
public static void main(String[] args) throws InterruptedException {
//一个银行账户
Account account = new Account();
account.setMeany(100);
//一个银行对象
Bank bank = new Bank(account);
bank.setxMeany(50);
//三个线程共同取钱
new Thread(bank,"boy").start();
new Thread(bank,"YOU").start();
new Thread(bank,"girl").start();
}
}
结果
以下代码的执行原理?(★★★★★)
1、假设t1和t2线程并发,开始执行以下代码的时候,肯定有一个先一个后。
2、假设t1先执行了,遇到了synchronized,这个时候自动找“后面共享对象”的对象锁,
找到之后,并占有这把锁,然后执行同步代码块中的程序,在程序执行过程中一直都是
占有这把锁的。直到同步代码块代码结束,这把锁才会释放。
3、假设t1已经占有这把锁,此时t2也遇到synchronized关键字,也会去占有后面
共享对象的这把锁,结果这把锁被t1占有,t2只能在同步代码块外面等待t1的结束,
直到t1把同步代码块执行结束了,t1会归还这把锁,此时t2终于等到这把锁,然后
t2占有这把锁之后,进入同步代码块执行程序。
4、这样就达到了线程排队执行。
重中之重:
这个共享对象一定要选好了。这个共享对象一定是你需要排队
执行的这些线程对象所共享的。
class Account {
private String actno;
private double balance; //实例变量。
//对象
Object o= new Object(); // 实例变量。(Account对象是多线程共享的,Account对象中的实例变量obj也是共享的。)
public Account() {
}
public Account(String actno, double balance) {
this.actno = actno;
this.balance = balance;
}
public String getActno() {
return actno;
}
public void setActno(String actno) {
this.actno = actno;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
//取款的方法
public void withdraw(double money){
/**
* 以下可以共享,金额不会出错
* 以下这几行代码必须是线程排队的,不能并发。
* 一个线程把这里的代码全部执行结束之后,另一个线程才能进来。
*/
synchronized(this) {
//synchronized(actno) {
//synchronized(o) {
/**
* 以下不共享,金额会出错
*/
/*Object obj = new Object();
synchronized(obj) { // 这样编写就不安全了。因为obj2不是共享对象。
synchronized(null) {//编译不通过
String s = null;
synchronized(s) {//java.lang.NullPointerException*/
double before = this.getBalance();
double after = before - money;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBalance(after);
//}
}
}
class AccountThread extends Thread {
// 两个线程必须共享同一个账户对象。
private Account act;
// 通过构造方法传递过来账户对象
public AccountThread(Account act) {
this.act = act;
}
public void run(){
double money = 5000;
act.withdraw(money);
System.out.println(Thread.currentThread().getName() + "对"+act.getActno()+"取款"+money+"成功,余额" + act.getBalance());
}
}
public class Test {
public static void main(String[] args) {
// 创建账户对象(只创建1个)
Account act = new Account("act-001", 10000);
// 创建两个线程,共享同一个对象
Thread t1 = new AccountThread(act);
Thread t2 = new AccountThread(act);
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
}
以上代码锁this、实例变量都可以!因为这三个是线程共享!
5.3、补充:安全与非安全集合
List 不安全集合与CopyOnWriteArrayList安全集合
public class ListDemo {
public static void main(String[] args) throws InterruptedException {
//不安全的集合
List<String> arrayList = new ArrayList<>();
//10万线程对一个集合同时操作
for (int i = 0;i<100000;i++){
new Thread(()->{
arrayList.add(Thread.currentThread().getName());
}).start();
}
Thread.sleep(20000);
System.out.println(arrayList.size());
//安全的集合
CopyOnWriteArrayList<String> arrayList1 = new CopyOnWriteArrayList<>();
//10万线程对一个集合同时操作
for (int i = 0;i<100000;i++){
new Thread(()->{
arrayList1.add(Thread.currentThread().getName());
}).start();
}
Thread.sleep(20000);
System.out.println(arrayList1.size());
}
}
结果:
5.4、发生死锁
都占有资源不放
public class DeadLockDemo {
//口红
static class LipStick{}
//镜子
static class MirRor{}
//化妆
static class MakeUP implements Runnable{
//静态资源 是共享的
static LipStick lipStick = new LipStick();
static MirRor MirRor = new MirRor();
//选择
int chose = 0;
String name = null;
MakeUP(int chose,String name){
this.chose=chose;
this.name=name;
}
@Override
public void run() {
if (chose==0){
synchronized (this.lipStick){
System.out.println(this.name+"拿到口红了");
synchronized (this.MirRor){
System.out.println(this.name+"拿到镜子了");
}
}
}else {
synchronized (this.MirRor){
System.out.println(this.name+"拿到镜子了");
synchronized (this.lipStick){
System.out.println(this.name+"拿到口红了");
}
}
}
}
}
public static void main(String[] args) {
MakeUP makeUP = new MakeUP(0,"白雪公主");
MakeUP makeUP1 = new MakeUP(1,"恶毒皇后");
new Thread(makeUP).start();
new Thread(makeUP1).start();
}
}
结果
占有资源释放
public class DeadLockDemo {
//口红
static class LipStick{}
//镜子
static class MirRor{}
//化妆
static class MakeUP implements Runnable{
//静态资源 是共享的
static LipStick lipStick = new LipStick();
static MirRor MirRor = new MirRor();
//选择
int chose = 0;
String name = null;
MakeUP(int chose,String name){
this.chose=chose;
this.name=name;
}
@Override
public void run() {
if (chose==0){
synchronized (this.lipStick){
System.out.println(this.name+"拿到口红了");
}
synchronized (this.MirRor){
System.out.println(this.name+"拿到镜子了");
}
}else {
synchronized (this.MirRor){
System.out.println(this.name+"拿到镜子了");
}
synchronized (this.lipStick){
System.out.println(this.name+"拿到口红了");
}
}
}
}
public static void main(String[] args) {
MakeUP makeUP = new MakeUP(0,"白雪公主");
MakeUP makeUP1 = new MakeUP(1,"恶毒皇后");
new Thread(makeUP).start();
new Thread(makeUP1).start();
}
}
结果
5.5、Lock(可重入锁)
原:
public class LockDemo implements Runnable{
int num = 10;
@Override
public void run() {
while (num>=0){
try {
Thread.sleep(10); //放大问题发生性
System.out.println(num--);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
LockDemo lockDemo = new LockDemo();
new Thread(lockDemo).start();
new Thread(lockDemo).start();
new Thread(lockDemo).start();
}
}
结果:
使用lock:
代码实现
public class LockDemo implements Runnable{
int num = 10;
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
try {
//锁住
lock.lock();
if (num>=0){
Thread.sleep(100); //放大问题发生性
System.out.println(Thread.currentThread().getName()+":"+num--);
}
}catch (Exception e){
e.printStackTrace();
}finally {
//开锁
lock.unlock();
}
}
}
public static void main(String[] args) {
LockDemo lockDemo = new LockDemo();
new Thread(lockDemo,"1").start();
new Thread(lockDemo,"2").start();
new Thread(lockDemo,"3").start();
}
}
结果
5.6、Lock与synchronized的对比
六、生产者消费者问题
6.1、信号法
wait():为等待,释放资源
notify(XX):唤醒指定线程
notifyAll():唤醒其他所有线程
public class TubeDemo {
public static void main(String[] args) {
TV tv = new TV();
Spectator spectator = new Spectator(tv);
Actors actors = new Actors(tv);
spectator.start();
actors.start();
}
//观众
static class Spectator extends Thread{
TV tv = null;
Spectator(TV tv){
this.tv=tv;
}
@Override
public void run() {
try {
this.watch();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//观看
public void watch() throws InterruptedException {
for (int i = 0; i < 20; i++) {
this.tv.doWatch();
}
}
}
//演员
static class Actors extends Thread{
TV tv = null;
Actors(TV tv){
this.tv=tv;
}
@Override
public void run() {
try {
this.play();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void play() throws InterruptedException {
for (int i = 0; i < 20; i++) {
if (i%2==0){
this.tv.doPlay("抖音记录美好生活!");
}else {
this.tv.doPlay("我和你有个约会");
}
}
}
}
//节目
static class TV{
//标志符 真为表演 假为观看
Boolean flag = true;
//表演的节目
String vice = null;
//计数器
int num = 0;
int num1 = 0;
//表演
public synchronized void doPlay(String vice) throws InterruptedException {
if (this.flag==false){
this.wait();//wait是等待,会释放资源,不像sleep,这里次序很重要,线程唤醒会从wait处开始执行
}
//通知演员表演
this.vice = vice;
num++;
System.out.println("演员表演了:"+vice+"第"+num+"次");
this.flag = !this.flag;
this.notify();
}
//观看
public synchronized void doWatch() throws InterruptedException {
if (this.flag==true){
this.wait();
}
//通知观众观看
num1++;
System.out.println("观众观看了:"+vice+"第"+num1+"次");
this.flag = !this.flag;
this.notify();
}
}
}
结果
七、线程池
7.1、线程池
7.2、使用线程
public class ThreadPool {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建线程服务 newFixedThreadPool表示创建多少条线程 ExecutorService是接口
ExecutorService service = Executors.newFixedThreadPool(10);
//连接执行
service.execute(new Thread1());
service.execute(new Thread1());
service.execute(new Thread1());
//连接执行Callable接口线程
Future<String> submit = service.submit(new Thread2());
//获取返回值
System.out.println(submit.get());
//连接关闭
service.shutdown();
}
static class Thread1 implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"创建了");
}
}
static class Thread2 implements Callable<String>{
@Override
public String call() throws Exception {
System.out.println(Thread.currentThread().getName()+"创建了");
return "我是实现Callable接口的线程,有返回值哦";
}
}
}
结果:
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)