线程安全如何实现
线程安全问题的产生本质上是因为多个线程在并发条件下对同一个共享资源的争抢,因此有两种方向来保证线程安全:
- 限制线程对资源的并发访问:这个方向的主要方式是加锁(阻塞),volatile(非阻塞)。
- 将该资源设置为线程独占的:这个方向主要实现方式是TreadLocal。
线程安全问题的产生本质上是因为多个线程在并发条件下对同一个共享资源的争抢,因此有两种方向来保证线程安全:
主要方向有两种:一是自旋,二是阻塞(加锁或信号量)
三个方法各自死循环,直到计数器对 3 取余是相应的数字。为了避免浪费资源,在自旋失败时让出 CPU 时间
public class PrintABC {
private volatile int counter = 0;
public void printA() {
while (true) {
while (counter % 3 != 0) {
Thread.yield();
}
System.out.print("A");
counter++;
}
}
public void printB() {
while (true) {
while (counter % 3 != 1) {
Thread.yield();
}
System.out.print("B");
counter++;
}
}
public void printC() {
while (true) {
while (counter % 3 != 2) {
Thread.yield();
}
System.out.print("C");
counter++;
}
}
public static void main(String[] args) {
PrintABC printABC = new PrintABC();
new Thread(()-> printABC.printA()).start();
new Thread(()-> printABC.printB()).start();
new Thread(()-> printABC.printC()).start();
}
}
PriorityQueue 是 Java 中的一个基于优先级堆的优先队列实现,它能够在 O(log n) 的时间复杂度内实现元素的插入和删除操作,并且能够自动维护队列中元素的优先级顺序。
通俗来说,PriorityQueue 就是一个队列,但是它不是先进先出的,而是按照元素优先级进行排序的。当你往 PriorityQueue 中插入一个元素时,它会自动根据元素的优先级将其插入到合适的位置。当你从 PriorityQueue 中删除一个元素时,它会自动将优先级最高的元素出队。
常用方法
不可变性
final修饰,每次修改都创建新字符串到常量池,相当于值引用
StringBuilder与StringBuffer
Buffer加了synchronized锁,因此常用Builder,并在并发下配合ThreadLocal
字符串常量池
String s = new String("ABC");
: 先在字符串常量池中创建对象“ABC”,然后再在堆上创建对象“ABC”
Java 中的 Finally 关键一般与try一起使用,在程序进入try块之后,无论程序是因为异常而中止或其它方式返回终止的,finally块的内容一定会被执行 。因此我们可以在 finally 代码块中执行关闭连接、关闭文件和释放线程的的操作。
public class Solution {
public int testFinally() {
int i=0;
try {
return i++;//先返回再加,因此返回0
} finally {
++i;//try中已经加了,这里再加一次
System.out.println("Finally: " + i);
}
}
public static void main(String[] args) {
Solution solution = new Solution();
System.out.println("Main: "+solution.testFinally());
}
}
//接口默认都是abstract的方法,因此也是默认abstract的,隐藏不写
public abstract interface TestInterface {
//变量默认都是public static final的,隐藏不写
public static final int a = 17;
}
创建线程有 3 种方式,一种是直接继承 Thread,一种是实现 Runnable 接口,另外一种是实现 Callable 接口。前 2 种方式都有一个缺陷:在执行完任务之后无法获取执行结果。
如果需要获取执行结果,就必须通过共享变量或者线程通信的方式来达到目的,这样使用起来就比较麻烦。
Java 1.5 提供了 Callable、Future、FutureTask,它们可以在任务执行完后得到执行结果。
Callable接口定义了一个 call()
方法,返回的类型是一个 V 类型的范型。在之前的例子中,我们使用String来作为范型的具体类型。