public class CustomQueue {
private BlockingQueue<Integer> queue;
public CustomQueue() {
// 初始化一个容量为1的阻塞队列
queue = new LinkedBlockingQueue<>(1);
}
public void put(int num) throws InterruptedException {
// 将数字放入队列
queue.put(num);
}
public int get() throws InterruptedException {
// 从队列中获取数字,如果队列为空,则阻塞等待
return queue.take();
}
public static void main(String[] args) {
CustomQueue customQueue = new CustomQueue();
// 生产者线程,向队列中放入数字
Thread producerThread = new Thread(() -> {
try {
customQueue.put(1);
System.out.println("Put 1 into the queue");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// 消费者线程,从队列中获取数字
Thread consumerThread = new Thread(() -> {
try {
int num = customQueue.get();
System.out.println("Get " + num + " from the queue");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
producerThread.start();
consumerThread.start();
}
}
运行结果:
############
在 Java 的 main
方法中,代码语句是按照顺序执行的,除非有特定的并发控制结构或方法调用导致了程序的并发执行或阻塞。
当涉及到多线程的并发操作或阻塞调用时,程序的执行顺序可能会受到影响。让我们更详细地讨论一下这些情况:
-
多线程的并发操作:
- 如果在
main
方法中创建并启动了多个线程,那么这些线程可能会并发地执行。这意味着这些线程的执行顺序可能是不确定的,取决于操作系统和 JVM 的调度策略。 - 如果这些线程共享了某些资源,比如共享内存或对象,而且没有正确的同步机制来保护这些资源,就可能导致竞态条件和线程安全问题。
- 如果在
-
阻塞调用:
- 阻塞调用会导致程序在执行到这些调用时暂停,并等待某些条件的发生。这些条件可能包括用户输入、文件读写、网络通信等。
- 典型的阻塞调用包括 I/O 操作(比如文件读写、网络通信)、线程睡眠(
Thread.sleep()
)、等待线程加入(Thread.join()
)等。 - 当程序执行到阻塞调用时,当前线程会暂停执行,直到相应的条件满足或等待时间结束。这会影响到后续代码的执行顺序。
考虑以下示例,其中演示了多线程并发操作和阻塞调用:
在这个示例中,main
方法中创建了一个新线程,并且在主线程中执行了一个阻塞调用(Thread.sleep(2000)
)。这导致主线程在睡眠期间暂停执行,同时新线程继续执行。因此,输出的顺序可能是不确定的,取决于线程调度和睡眠时间的相对情况。
public class Main {
public static void main(String[] args) {
System.out.println("Start of main");
// 创建并启动一个新线程
Thread thread = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("Thread: " + i);
try {
Thread.sleep(1000); // 线程睡眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
// 模拟阻塞调用:线程睡眠2秒
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("End of main");
}
}
线程的start()方法只是进入了就绪队列,到分配到时间片就是运行状态了。
线程的几种情况:########################
1.正常执行完结束
2.没执行完,没时间片了,重新排队进入就绪队列
3.没执行完,自我阻塞,休眠(等待IO操作完成、等待锁资源、等待其他线程的通知,满足条件后进入就绪状态)
队列,堆,栈数据结构的异同:
队列、堆和栈是三种常见的数据结构,它们在存储和组织数据时有着不同的特点和用途。让我们来比较它们的异同:
-
队列(Queue):
- 特点:队列是一种先进先出(FIFO)的数据结构,即先进入队列的元素会先被移除。
- 操作:队列通常支持两种主要操作:入队(enqueue),将元素添加到队列的末尾;出队(dequeue),从队列的头部移除一个元素。
- 应用:队列常用于需要按照顺序处理数据的场景,比如任务调度、消息传递等。
-
堆(Heap):
- 特点:堆是一种特殊的树形数据结构,具有以下特点:父节点的值总是大于或等于(最大堆)或小于或等于(最小堆)子节点的值。
- 操作:堆通常支持插入和删除操作,以及获取最大或最小值的操作。
- 应用:堆常被用作优先队列的实现,用于高效地找到最大或最小值,以及在堆排序等算法中。
-
栈(Stack):
- 特点:栈是一种后进先出(LIFO)的数据结构,即最后进入栈的元素会最先被移除。
- 操作:栈通常支持压入(push)和弹出(pop)操作,分别用于在栈顶添加和移除元素。
- 应用:栈常用于需要反向追溯的场景,比如函数调用、表达式求值、浏览器历史记录等。
异同点总结:
- 共同点:队列、堆和栈都是常见的数据结构,用于存储和组织数据,并提供相应的操作。
- 不同点:它们的特点和操作不同,适用于不同的场景。队列适用于先进先出的情况,堆适用于需要快速找到最大或最小值的情况,而栈适用于后进先出的情况。
总的来说,队列、堆和栈在数据存储和操作方面有着不同的优势和应用场景,选择合适的数据结构取决于具体的问题和需求。
栈和队列_停车场管理(栈和队列的应用)的空间复杂度-CSDN博客
缓冲区可以用什么数据结构?
缓冲区是一种用于临时存储数据的数据结构,常用于解决生产者-消费者问题或者在不同速度的系统之间进行数据交换。缓冲区的选择取决于具体的应用场景和需求,以下是一些常用的数据结构作为缓冲区的选择:
-
数组:数组是一种简单而高效的数据结构,可以作为缓冲区来存储一定量的数据。数组的主要优点是访问速度快,但其大小通常是固定的,不够灵活。
-
链表:链表是一种动态数据结构,可以灵活地添加和移除元素,因此也可以用作缓冲区。但链表的访问速度相对较慢,因为需要遍历整个链表来找到特定位置的元素。
-
队列:队列是一种先进先出(FIFO)的数据结构,非常适合用作缓冲区,特别是在生产者-消费者模型中。生产者可以将数据入队列,消费者则从队列中取出数据进行处理。
-
环形缓冲区(Circular Buffer):环形缓冲区是一种特殊的缓冲区,可以通过数组实现。它具有固定的大小,并且可以循环使用空间,避免了数据移动的开销。环形缓冲区常用于实时系统或者需要高效处理连续数据流的场景。
-
堆栈:堆栈是一种后进先出(LIFO)的数据结构,可以用作临时存储数据。但在大多数情况下,队列比堆栈更适合作为缓冲区,因为它们更符合生产者-消费者模型。
选择合适的缓冲区数据结构取决于需求,包括数据的访问模式、数据量的大小和性能要求等因素。
当客户端发送按键事件,用户按下某个按钮,就将相应的值放入队列中,然后消费者线程将队列中的值取出并打印。
MyGUI:
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class MyGUI extends JFrame {
private JButton button1, button2, button3;
CustomQueue customQueue;
public MyGUI() {
// 设置窗口标题
setTitle("GUI窗口");
customQueue = new CustomQueue(5);
// 创建按钮
button1 = new JButton("按钮1");
button2 = new JButton("按钮2");
button3 = new JButton("按钮3");
// 设置布局为流式布局
setLayout(new FlowLayout());
// 将按钮添加到窗口
add(button1);
add(button2);
add(button3);
// 添加按钮点击事件监听器
button1.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
try {
customQueue.put(1);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
});
button2.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
try {
customQueue.put(2);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
});
button3.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
try {
customQueue.put(3);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
});
// 设置窗口大小和关闭操作
setSize(300, 200);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null); // 窗口居中显示
setVisible(true); // 显示窗口
// 消费者线程,从队列中获取数字
Thread consumerThread = new Thread(() -> {
try {
while (true) {
int num = customQueue.get();
System.out.println("Get " + num + " from the queue");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
consumerThread.setName("consumer");
consumerThread.start();
}
public static void main(String[] args) {
// 创建窗口对象,运行程序时,jvm会创建一个主线程来运行main方法。
new MyGUI();//创建窗口的同时,创建了一个线程并且启动。
Thread.currentThread().setName("MAIN");
}
}
//主线程执行main方法,在创建窗口时又创建了一个消费者线程.
//无论android中还是pc客户端,主线程就是ui线程,子线程就是后台线程(用来执行耗时操作)!!!!!!!!!!!!!!!!!
//主线程执行main方法,在创建窗口时又创建了一个消费者线程.
//无论android中还是pc客户端,主线程就是ui线程,子线程就是后台线程(用来执行耗时操作)!!!!!!!!!!!!!!!!!
同步和异步是计算机编程中常用的两种处理方式:
同步(Synchronous):在同步操作中,任务按照顺序执行,每个任务都必须等待前一个任务完成后才能开始执行。在同步操作中,程序会阻塞(即暂停执行)直到操作完成。这意味着如果一个任务很耗时,整个程序可能会出现停滞或卡顿的情况。
异步(Asynchronous):在异步操作中,任务不需要等待前一个任务完成就可以开始执行。相反,程序会继续执行后续的任务,而不会被当前任务的执行阻塞。异步操作通常会在后台线程或者通过回调函数来执行,这样可以提高程序的响应性和并发性。
在实际编程中,异步操作通常用于处理那些可能耗时的任务,比如网络请求、文件读写、数据库查询等。通过将这些任务设置为异步操作,可以避免阻塞主线程,保持程序的流畅性和响应性。
总的来说,同步操作是按顺序执行的,会阻塞程序的执行;而异步操作可以并发执行,不会阻塞程序的执行。
也就是说并发意味着异步,顺序意味着同步!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
Android ANR:原理分析及解决办法 - 知乎 (zhihu.com)
android中的主线程,子线程:
activity
service
broadcastReceiver
当你启动一个新的Activity时,Android系统会在UI线程中执行相应的生命周期方法和界面更新操作。如果在Activity中执行了一些耗时操作,例如加载大量数据或进行网络请求,建议在子线程中执行,以避免阻塞主线程导致界面卡顿。
你可以在Activity中手动创建新的线程来执行耗时操作,或者使用异步任务(AsyncTask)、Handler、RxJava等方式来在后台线程中执行任务,然后在主线程中更新UI。这样可以确保在主线程中处理界面更新,同时避免阻塞主线程导致的ANR(Application Not Responding)错误。
启动一个服务(Service)不会在主线程中执行服务的代码逻辑。服务的生命周期方法(如onCreate()、onStartCommand()等)以及服务中的其他逻辑通常在主线程之外的后台线程中执行。这意味着服务的代码执行是在一个新的线程中进行的,而不会阻塞主线程。
默认情况下,服务会在主线程之外的工作线程中执行。这样可以确保服务可以在后台执行长时间运行的操作,而不会影响到应用程序的响应性能。然而,在服务中执行的代码仍然需要注意,尤其是避免执行耗时操作或阻塞线程。
如果你需要在服务中执行耗时操作,例如网络请求或大量数据处理,最好在服务内部创建新的线程或使用异步任务来执行这些操作,以免阻塞服务的工作线程。另外,你也可以考虑使用Android提供的后台处理机制,如IntentService或JobScheduler,来管理和执行后台任务,以便更好地管理资源和执行顺序。
广播接收器(BroadcastReceiver)的onReceive()方法默认在主线程(UI线程)中执行。这意呢确保广播接收器可以立即响应广播消息,但也意味着如果在onReceive()方法中执行耗时操作,会导致主线程阻塞,从而影响到应用的响应性能。
如果onReceive()方法中需要执行耗时操作,最好在其中启动一个新线程来执行,以免阻塞主线程。可以使用线程(Thread)、Handler、AsyncTask等方法来在后台线程中执行任务。或者,你也可以考虑使用IntentService或JobScheduler等机制来管理后台任务的执行,以便更好地控制资源的使用和执行顺序。
需要注意的是,由于onReceive()方法是在主线程中执行的,因此它执行的时间应该尽量短,以免影响到应用的性能和响应速度。
总结:主线程顺序执行,activity随便跳转,broadcastReceiver随便创建。在主线程中随便创建子线程/后台服务。一条原则:耗时的操作就放在子线程中去执行,采用这种异步的方式。
CustomQueue:
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class CustomQueue {
private BlockingQueue<Integer> queue;
public CustomQueue(int capacity) {
// 初始化一个容量为1的阻塞队列
queue = new LinkedBlockingQueue<>(capacity);
}
public void put(int num) throws InterruptedException {
// 将数字放入队列,如果队列已满,则循环等待直到成功放入
try {
queue.put(num);
System.out.println("queue添加了"+num);
} catch (InterruptedException e) {
e.printStackTrace();
// 重新尝试放入
}
}
public int get() throws InterruptedException {
// 从队列中获取数字,如果队列为空,则阻塞等待
return queue.take();
}
}