赞
踩
多线程是指在一个程序中同时执行多个独立的任务或操作。每个任务或操作都是由一个单独的线程来执行,而这些线程共享程序的资源和内存空间。与单线程相比,多线程可以提高程序的运行效率和响应速度,因为它可以充分利用 CPU 的多核处理能力,同时也可以避免某些操作阻塞其他操作的问题。
多线程是一种并发编程的技术,它允许程序在同一个进程中同时执行多个独立的任务或操作。每个任务都由一个单独的线程来执行,而这些线程共享程序的资源和内存空间。
多线程的基本原理是通过将程序分成多个子任务,并创建对应数量的线程来同时执行这些子任务。每个线程都有自己的堆栈、寄存器和指令计数器等状态信息,可以独立地运行代码。不同线程之间可以进行通信和协调,通过锁、信号量、条件变量等机制来实现数据同步和互斥访问。
多线程在操作系统级别实现,通过操作系统提供的API(如POSIX标准中提供的pthread库)进行创建、管理和控制。在高级编程语言中也提供了相应的库或框架来支持多线程编程,如Java中的Thread类、C#中的Task类等。
执行方式不同:单线程只能执行一个任务,而多线程可以同时执行多个任务。
程序性能不同:多线程可以充分利用CPU资源,提高程序运行效率,而单线程则无法充分利用CPU资源,导致程序运行速度变慢。
内存占用不同:多线程需要占用更多的内存空间和系统资源(如CPU时间片),因此对于内存有限或资源受限的应用场景,单线程更为适合。
编写难度不同:在编写过程中,多线程需要考虑到并发、数据安全等问题,需要对程序设计有一定了解和经验。而单线程相对来说比较简单易于编写。
错误处理方式不同:在单线程中如果出现异常错误会直接导致程序崩溃,在多线程中则需要使用特殊手段处理错误以保证程序稳定性。
线程是一个动态执行的过程,它也有一个从产生到死亡的过程。
下图显示了一个线程完整的生命周期。
新建状态:
使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
就绪状态:
当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
运行状态:
如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
阻塞状态:
如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
死亡状态:
一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。
Java 线程的优先级是一个整数,其取值范围是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。
默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(5)。
具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。
Java 提供了三种创建线程的方法:
单继承局限性
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("主线程:我在学习线程!"); } } }
结果(穿插进行)
一个对象可被多个线程使用
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接口可以看作是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; } }
接口-》真实角色-》中介-》操作
Java Lambda表达式是Java 8中最重要的新特性之一。
它们是一种可传递的匿名函数,可以作为参数传递给方法或存储在变量中,因此可以在需要的时候调用它们。
Lambda表达式的主要目的是简化Java代码,使其更易于阅读和编写。
Lambda表达式的语法非常简洁和清晰。它们由参数列表、箭头符号和方法体组成。参数列表指定传递给Lambda表达式的参数,箭头符号 “->” 分隔参数列表和方法体,方法体则包含Lambda表达式要执行的代码。
下面是一个简单的Lambda表达式示例:
(int x, int y) -> x + y
这个Lambda表达式接受两个整数参数 x 和 y,并返回它们的和。可以将这个Lambda表达式存储在一个变量中,例如:
IntBinaryOperator add = (int x, int y) -> x + y;
这个代码创建了一个名为add的变量,它的类型是IntBinaryOperator,它接受两个整数参数并返回一个整数结果。
该变量被初始化为一个Lambda表达式,该表达式实现了相同的功能,即将两个整数相加。
Lambda表达式的主要优点包括:
简化代码:Lambda表达式可以将冗长复杂的代码简化为几行简洁的代码。
可读性:Lambda表达式可以使代码更易于阅读和理解,因为它们更接近自然语言。
可传递性:Lambda表达式可以作为参数传递给方法或存储在变量中,使代码更具可重用性和灵活性。
并行处理:Lambda表达式可以与Stream API一起使用,使Java程序更容易地进行并行处理。
Lambda表达式是Java 8中最重要的新特性之一,它们为我们提供了一种更简单、更灵活、更易于使用的编程方式。
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()方法将结果输出到控制台。
new Thread(() -> {
// 执行后台任务
// ...
// 通知主线程任务已完成
}).start();
Lambda表达式可以作为事件监听器传递给GUI组件等,使事件处理更加简单和可读。
例如,假设我们有一个按钮,需要在用户单击它时执行某些操作。可以使用以下代码将Lambda表达式作为事件监听器传递给该按钮:
button.addActionListener(event -> {
// 处理按钮单击事件
// ...
});
这里,使用了Java中的ActionListener接口,并将一个Lambda表达式作为参数传递给它,该表达式将在用户单击按钮时执行。
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对象的年龄并返回一个整数值,以指示它们的排序顺序。
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()方法将过滤后的结果转换为一个新的列表。
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()方法将转换后的结果存储到一个新的列表中。
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。
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变量中。
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()方法,该表达式将处理查询结果集并输出到控制台上。
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()方法将它们加起来得到总和。在此过程中,计算将在多个线程上并行执行,从而提高了计算效率。
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()方法。该表达式将在用户单击按钮时执行,并输出一条消息到控制台上。
Lambda表达式可以用于编写更加简洁和可读的单元测试代码,使测试更加容易理解和维护。
例如,假设有一个函数,需要进行单元测试以确保其正确性。可以使用以下代码将Lambda表达式作为测试断言传递给JUnit测试框架:
@Test
public void testMyFunction() {
int result = myFunction(2, 3);
assertEquals(6, result, () -> "myFunction(2, 3) should return 6");
}
这里,使用了JUnit测试框架,并将一个Lambda表达式作为测试断言传递给assertEquals()方法。该表达式将在测试失败时计算并返回错误消息,以帮助定位和修复问题。
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中最重要的新特性之一。
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是平级的。
方法名 | 作用 |
---|---|
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方法并不释放资源
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时间片时间长度一样。
平均分配,一切平等。
有一些编程语言,线程调度模型采用的是这种方式。
实例方法:
常量:
常量名 | 备注 |
---|---|
static int MAX_PRIORITY | 最高优先级(10) |
static int MIN_PRIORITY | 最低优先级(1) |
static int NORM_PRIORITY | 默认优先级(5) |
方法:
方法名 | 作用 |
---|---|
int getPriority() | 获得线程优先级 |
void setPriority(int newPriority) | 设置线程优先级 |
优先级比较高的获取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(),当前线程会进入”阻塞状态
“。等待加入线程执行完!
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(); } }
什么时候数据在多线程并发的环境下会存在安全问题呢?★★★★★
满足三个条件:
满足以上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线程执行结束,两个线程之间发生了等待关系,这就是同步编程模型。
效率较低。线程排队执行。
同步就是排队。
线程同步机制的语法是:
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、实例变量都可以!因为这三个是线程共享!
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()); } }
结果:
都占有资源不放
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(); } }
结果
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(); } }
结果:
代码实现
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(); } }
结果
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(); } } }
结果
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接口的线程,有返回值哦"; } } }
结果:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。