JUC
1. 线程的创建和使用
初始化线程的4种方式:
- 继承Thread
- 实现Runnable接口
- 实现Callable接口+FutureTask(可以拿到返回结果,可以处理异常)
- 线程池
1.1 继承Thread类
/**
* 多线程的创建 方式一:
* 1. 继承Thread类
* 2. 重写Thread类run()方法 ---> 将此线程执行的操作声明在run()方法中
* 3. 创建Thread子类的对象
* 4. 通过对象调用start()方法
*
* 例子:遍历100以内的偶数
*
*/
public class JavaTest01 extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i % 2 == 0 ){
System.out.println(i);
}
}
}
public static void main(String[] args) {
// 创建Thread子类的对象
JavaTest01 javaTest01 = new JavaTest01();
// 通过对象调用start()方法
javaTest01.start();
System.out.println("hello");
}
}
1.2 实现Runnable接口
/**
* 多线程的创建 方式二: 实现Runnable接口
* 1. 创建一个实现了Runnable接口的类
* 2. 实现类实现Runnable接口中的方法:run()
* 3. 创建实现类的对象
* 4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
* 5. 通过Thread类的对象调用start()
*
* 例子:遍历100以内的偶数
*
*/
//1. 创建一个实现了Runnable接口的类
class Mthread implements Runnable{
//2. 实现类实现Runnable接口中的方法:run()
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i % 2 == 0 ){
System.out.println(i);
}
}
}
}
class ThreadTest{
public static void main(String[] args) {
//创建实现类的对象
Mthread mthread = new Mthread();
//将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
Thread t1 = new Thread(mthread);
Thread t2 = new Thread(mthread);
//通过Thread类的对象调用start()
t1.start();
t2.start();
}
}
1.3 实现Callable接口
/**
* 多线程的创建 方式三: 实现Callable接口
* 1. 创建一个实现了Callable接口的类
* 2. 实现类实现Callable接口中的方法:call()
* 3. 创建实现类的对象
* 4. 借助Future接口开启线程
*
* 例子:遍历100以内的偶数,返回所有偶数的和
*
*/
// 1. 创建一个实现了Callable接口的类
class Callthread implements Callable<Integer> {
// 2. 实现类实现Callable接口中的方法:call()
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(i);
sum += i;
}
}
return sum;
}
}
class CallThreadTest {
public static void main(String[] args) {
// 3. 创建实现类的对象
Callthread cThread = new Callthread();
// 4. 借助Future接口开启线程
FutureTask<Integer> futureTask = new FutureTask<Integer>(cThread);
new Thread(futureTask, "A").start();
try {
//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值
System.out.println(futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
1.4 线程池
/**
* 方式四:线程池
*
*/
class NumberThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() +": "+ i);
}
}
}
}
class NumberThread1 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 != 0) {
System.out.println(Thread.currentThread().getName() +": "+ i);
}
}
}
}
public class ThreadPoolTest {
public static void main(String[] args) {
//1. 提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
//2. 执行指定的线程的操作,需要提供实现Runnable接口或Callable接口实现类的对象
service.execute(new NumberThread());
service.execute(new NumberThread1());
//关闭连接池
service.shutdown();
}
}
1.4.1 概要
java中的线程池是通过Executor框架实现的,该框架中用到了Executor、Executors、ExecutorService、ThreadPoolExecutor这几个类。
1.4.2 使用线程池的好处
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中的线程,不需要每次都重写创建)
- 便于线程管理
1.4.3 线程池相关API
- ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
- void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable
- Future submit(Callable task):执行任务,有返回值,一般用来执行Callable
- void shutdown:关闭连接池
- Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
- Executors.newFixedThreadPool(n):创建一个可重用固定线程数的线程池
- Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
- Executors.newSingleThreadExecutor():创建一个只有一个线程的线程池
- Executors.newScheduledThreadPoll(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期的执行
2. JUC辅助类
2.1 CountDownLatch 倒计数计数器
package javatest;
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
//倒计时6
CountDownLatch cl = new CountDownLatch(6);
for (int i = 0; i < 6; i++) {
new Thread(() ->{
System.out.println(Thread.currentThread().getName() + "\t离开教室");
//每走一个线程,倒计时减1
cl.countDown();
}, String.valueOf(i)).start();
}
//主线程等到计数器从6减到0之后才继续执行主线程
cl.await();
System.out.println(Thread.currentThread().getName() + "\t班长关门走人");
}
// 错误写法,线程顺序错乱
public static void closeDoor(){
for (int i = 0; i < 6; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t离开教室");
}, String.valueOf(i)).start();
}
System.out.println(Thread.currentThread().getName() + "\t班长关门走人");
}
}
2.2 CyclicBarrier 正计数计数器
package javatest;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
public static void main(String[] args) {
//计数到7才执行此操作
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
System.out.println(Thread.currentThread().getName() + "召唤神龙");
});
for (int i = 0; i < 7; i++) {
int tempInt = i;
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t收集到第:" + tempInt + "颗神龙");
try {
//每执行一个线程都属计数器加1
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}, String.valueOf(i)).start();;
}
}
}
2.3 SemaphoreDemo
- 用于多个共享资源的互斥使用
- 用于并发线程数的控制
package javatest;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3);// 模拟资源类,表示有三个空车位
for (int i = 0; i < 6; i++) {
new Thread(() -> {
try {
//acquire数字减一
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "\t抢占了车位");
//停三秒钟
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + "\t离开了车位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//车子离开车位,车位+1
semaphore.release();
}
}, String.valueOf(i)).start();;
}
}
}
3. Thread类的常用方法
- start() : 启动线程,并执行对象的run方法
- run() : 线程在被调度时执行的操作
- getName() : 返回线程的名称
- setName(String name) : 设置该线程的名称
- static Thread currentThread() : 返回当前线程。在Thread子类中就是this,通常用于主线程和Runnable实现类,使用方法:Thread.currentThread().setName(“线程一”);
- yield() : 暂停当前正在执行的线程对象,并执行其他线程(有可能在很短的时间后又分配到了这个线程)
- join() : 优先执行调用join()方法的线程,等join()线程执行完毕再继续执行原来的线程
- sleep(long millisec) : 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
- interrupt() : 中断线程
- setPriority(int priority) : 更改线程的优先级
- setDaemon(boolean on) : 将该线程标记为守护线程或用户线程
3.1 线程的调度
- 同优先级线程组成先进先出队列(先到先服务),使用时间片策略
- 对高优先级,使用优先调度的抢占式策略
3.2 线程的优先级
获取和设置当前线程的优先级:
getPriority();
setPriority(int p);
4. 线程的安全问题
问题出现原因:当某个线程执行操作的过程中,尚未操作完成时,其他线程参与进来,也开始执行操作。
解决思路:当一个线程A在执行操作时,要想办法不让其他线程参与进来,等A执行完之后,再让其他线程执行操作。即使线程A阻塞也不能改变。
解决方法:synchronized(同步方法、同步代码块)和lock
4.1 Synchronized和lock方式的区别
- synchronized机制在执行完相应的同步代码后,会自动的释放同步监视器
- lock需要手动的启动同步lock(),结束同步的时候也需要手动实现unlock();
4.2 死锁问题
- 不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
- 出现死锁后,不会出现异常,不会出现提示,只是所有线程都在阻塞状态,无法进行。
4.3 如何避免出现死锁问题
- 加锁顺序(线程按照一定的顺序加锁)
- 尽量减少同步资源的定义
- 尽可能少的进行嵌套
- 加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)
- 死锁检测
4.4 读写锁ReadWriteLock
- ReadWriteLock管理一组锁,一个是只读的锁,一个是写锁。
package javatest;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
class MyCatch {//模拟缓存
private volatile Map<String, String> map = new HashMap<>();
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public void put(String key, String value) {
//写操作加写锁
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "开始写入:" + key);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "写入完成:" + key);
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock();
}
}
public void get(String key) {
//读操作加读锁
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "开始读取:" + key);
String result = map.get(key);
System.out.println(Thread.currentThread().getName() + "读取完成:" + result);
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
}
}
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCatch myCatch = new MyCatch();
//五个线程同时写入
for (int i = 0; i < 5; i++) {
final int tempInt = i;
new Thread(() -> {
myCatch.put(tempInt+"", tempInt+"");
}, String.valueOf(i)).start();
}
//五个线程同时读取
for (int i = 0; i < 5; i++) {
final int tempInt = i;
new Thread(() -> {
myCatch.get(tempInt+"");
}, String.valueOf(i)).start();
}
}
}
4.5 阻塞队列BlockingQueue
-
常用阻塞队列
- ArrayBlockingQueue:由数组结构组成的有界阻塞队列
- LinkedBlockingQueue:由链表结构组成的有界(但大小默认值为Integer.MAX.VALUE)阻塞队列
-
阻塞队列是一个在队列基础上又支持了两个附加操作的队列。
2个附加操作:
支持阻塞的插入方法:队列满时,队列会阻塞插入元素的线程,直到队列不满。
支持阻塞的移除方法:队列空时,获取元素的线程会等待队列变为非空。
-
阻塞队列常用方法
- 插入
- add(e):队列满了后再插入会抛出异常
- offer(e):队列满了后再插入会返回null值
- put(e):队列满了后再插入会阻塞
- offer(e, time, unit):队列满了后再插入会超时,unit - 时间单位,例如TimeUnit.SECONDS
- 移除
- remove():会抛出异常
- poll():会返回null值
- take():会阻塞
- poll(time, unit):会超时
- 插入
5. 线程的通信
通信要点:判断、干活、通知
例题:有两个线程操作一个初始值为0的变量,一个线程+1,一个线程-1,要求实现10轮过后变量的值依旧是0
package javatest;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 线程通信举例:有两个线程,操作一个初始值为0的变量,一个线程+1,一个线程-1,要求实现10轮过后变量的值依旧是0.
*/
class NumberDemo {// 资源类
private int number = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void increment() throws Exception {
try {
lock.lock();
// 1. 判断
while(number != 0) {
// this.wait();以前的wait()被await()替换了
condition.await();
}
// 2. 干活
number++;
System.out.println(Thread.currentThread().getName() + "\t" + number);
// 3. 通知
// this.notifyAll();以前的notifyAll()被signalAll()替换了
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void decrement() throws Exception {
try {
lock.lock();
// 1. 判断
while(number == 0) {
// this.wait();以前的wait()被await()替换了
condition.await();
}
// 2. 干活
number--;
System.out.println(Thread.currentThread().getName() + "\t" + number);
// 3. 通知
// this.notifyAll();以前的notifyAll()被signalAll()替换了
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class ThreadConn {
public static void main(String[] args) {
NumberDemo num = new NumberDemo();
//用Lambda表达式代替匿名内部类
new Thread(() -> {
for (int i = 0; i <= 9; i++) {
try {
num.increment();
} catch (Exception e) {
e.printStackTrace();
}
}
}, "线程A" ).start();
//用Lambda表达式代替匿名内部类
new Thread(() -> {
for (int i = 0; i <= 9; i++) {
try {
num.decrement();
} catch (Exception e) {
e.printStackTrace();
}
}
}, "线程B" ).start();
//用Lambda表达式代替匿名内部类
new Thread(() -> {
for (int i = 0; i <= 9; i++) {
try {
num.increment();
} catch (Exception e) {
e.printStackTrace();
}
}
}, "线程C" ).start();
//用Lambda表达式代替匿名内部类
new Thread(() -> {
for (int i = 0; i <= 9; i++) {
try {
num.decrement();
} catch (Exception e) {
e.printStackTrace();
}
}
}, "线程D" ).start();
}
}
5.1 防止虚假唤醒
虚假唤醒出现的原因:线程被唤醒后,没有被重新循环判断,直接执行操作,所以出现了虚假唤醒的现象。
解决方法:多线程的判断中,只能用while,不能用if!!!用if的话不能循环判断,while可以循环判断。
5.2 精确通知,顺序访问
例子: 多线程之间按顺序访问,实现A->B->C,三个线程启动,要求如下:
AA打印5此,BB打印10次,CC打印15次,循环10轮
package javatest;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 多线程之间按顺序访问,实现A->B->C,三个线程启动,要求如下:
* AA打印5此,BB打印10次,CC打印15次,循环10轮
*/
class ShareResource {
private int number = 1; // 1:A 2:B 3:C
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
public void print5() {
try {
lock.lock();
// 1. 判断
while (number != 1) {
// 不是A线程,awit
condition1.await();
}
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
number = 2;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void print10() {
try {
lock.lock();
while (number != 2) {
// 不是B线程,awit
condition2.await();
}
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
number = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void print15() {
try {
lock.lock();
while (number != 3) {
// 不是C线程,awit
condition3.await();
}
for (int i = 0; i < 15; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
number = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class ThreadOrderAccess {
public static void main(String[] args) {
ShareResource sr = new ShareResource();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
sr.print5();
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
sr.print10();
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
sr.print15();
}
}, "C").start();
}
}
Volatile关键字
面试题
1、什么是进程和线程?
- 进程是后台运行的一个程序
- 在进程中独立运行的子任务
2、什么是线程安全和线程不安全?
3、什么是自旋锁和互斥锁?
4、什么是Java内存模型?
5、什么是CAS?
6、什么是乐观锁和悲观锁?
7、什么是AQS?
8、什么是原子操作?在Java Concurrency API中有哪些原子类(atomic classes)?
9、什么是Executors框架?
10、什么是阻塞队列?如何使用阻塞队列来实现生产者-消费者模型?
-
阻塞队列是一个在队列基础上又支持了两个附加操作的队列。
2个附加操作:
支持阻塞的插入方法:队列满时,队列会阻塞插入元素的线程,直到队列不满。
支持阻塞的移除方法:队列空时,获取元素的线程会等待队列变为非空。
阻塞队列常用方法
- 插入
- add(e):队列满了后再插入会抛出异常
- offer(e):队列满了后再插入会返回null值
- put(e):队列满了后再插入会阻塞
- offer(e, time, unit):队列满了后再插入会超时,unit - 时间单位,例如TimeUnit.SECONDS
- 移除
- remove():会抛出异常
- poll():会返回null值
- take():会阻塞
- poll(time, unit):会超时
11、什么是Callable和Future?
-
Callable 接口类似于加强版的Runnable,被线程执行后,可以带有返回值,这个返回值可以被 Future 拿到,也就是说,Future 可以拿到异步执行任务的返回值。
-
Future 接口表示异步任务,是还没有完成的任务给出的未来结果。所以说 Callable用于产生结果,Future 用于获取结果。
12、什么是FutureTask?
- 是 Future和Runnable接口的实现,将在并行调用中执行。
13、什么是同步容器和并发容器的实现?
14、什么是多线程?优缺点?
优点:
- 提高应用程序的响应
- 提高计算机系统CPU的利用率
- 改善程序结构,将既长又复杂的进程分为多个线程,独立运行,利于理解和修改
缺点:
- 比单线程更耗内存
15、什么是多线程的上下文切换?
16、ThreadLocal的设计理念与作用?
17、ThreadPool(线程池)用法与优势?
18、Concurrent包里的其他东西:ArrayBlockingQueue、CountDownLatch等等。
- ArrayBlockingQueue 是一个阻塞式的队列,继承自AbstractBlockingQueue,间接的实现了Queue接口和Collection接口。底层以数组的形式保存数据(实际上可看作一个循环数组)。
19、synchronized和ReentrantLock的区别?
-
synchronized是锁住方法内所有的代码,Lock能缩小锁的范围,提高系统的并发性
-
synchronized机制在执行完相应的同步代码后,会自动的释放同步监视器
-
lock需要手动的启动同步lock(),结束同步的时候也需要手动实现unlock();
20、Semaphore有什么作用?
21、Java Concurrency API中的Lock接口(Lock interface)是什么?对比同步它有什么优势?
22、Hashtable的size()方法中明明只有一条语句”return count”,为什么还要做同步?
23、ConcurrentHashMap的并发度是什么?
24、ReentrantReadWriteLock读写锁的使用?
25、CyclicBarrier和CountDownLatch的用法及区别?
26、LockSupport工具?
27、Condition接口及其实现原理?
28、Fork/Join框架的理解?
29、wait()和sleep()的区别?
- 声明位置不同,Thread类中声明sleep()。Object类中声明wait()
- 调用的要求不同:sleep()可以在任何需要的场景下调用,wait()必须使用在同步代码块或同步方法中
- 如果两个方法都使用在同步代码块或方法中,sleep()不会释放锁,wait()会释放锁,比如线程sleep(1000),在sleep的时间里其他线程不能继续执行操作,因为没有释放锁,而wait的时候其他线程能继续执行,因为wait()会释放锁
30、线程的五个状态(五种状态,创建、就绪、运行、阻塞和死亡)?
线程的生命周期包括五个状态:新建、就绪、运行、阻塞、销毁。
- 新建:就是刚使用new方法,new出来的线程;
- 就绪:就是调用的线程的start()方法后,这时候线程处于等待CPU分配资源阶段,谁先抢的CPU资源,谁开始执行;
- 运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run方法定义了线程的操作和功能;
- 阻塞:在运行状态的时候,可能因为某些原因导致运行状态的线程变成了阻塞状态,比如sleep()、wait()之后线程就处于了阻塞状态,这个时候需要其他机制将处于阻塞状态的线程唤醒,比如调用notify或者notifyAll()方法。唤醒的线程不会立刻执行run方法,它们要再次等待CPU分配资源进入运行状态;
- 销毁:如果线程正常执行完毕后或线程被提前强制性的终止或出现异常导致结束,那么线程就要被销毁,释放资源;
31、start()方法和run()方法的区别?
-
run(); 只是调用了一个普通方法,并没有启动另一个线程,程序还是会按照顺序执行相应的代码。
-
start(); 则表示,重新开启一个线程,不必等待其他线程运行完,只要得到cup就可以运行该线程。
32、Runnable接口和Callable接口的区别?
- call()可以有返回值
- call()可以抛出异常,被外面的操作捕获,获取异常信息
- Callable支持泛型
33、volatile关键字的作用?
34、Java中如何获取到线程dump文件?
35、线程和进程有什么区别?
36、线程实现的方式有几种(四种)?
- 方式一:继承Thread类,重写run方法
- 方式二:实现Runnable接口,实现run方法
- 方式三:实现Callnable接口,实现call方法
- 方式四:利用ExecutorService线程池的方式创建线程
37、高并发、任务执行时间短的业务怎样使用线程池?并发不高、任务执行时间长的业务怎样使用线程池?并发高、业务执行时间长的业务怎样使用线程池?
38、如果你提交任务时,线程池队列已满,这时会发生什么?
39、锁的等级:方法锁、对象锁、类锁?
40、如果同步块内的线程抛出异常会发生什么?
41、并发编程(concurrency)并行编程(parallellism)有什么区别?
- 并发,指的是多个事情,在同一时间段内同时发生了。 并发的多个任务之间是互相抢占资源的。
- 并行,指的是多个事情,在同一时间点上同时发生了。并行的多个任务之间是不互相抢占资源的。
42、如何保证多线程下 i++ 结果正确?
- 加锁
43、一个线程如果出现了运行时异常会怎么样?
- 如果异常没有被捕获该线程将会停止执行。
44、如何在两个线程之间共享数据?
45、生产者消费者模型的作用是什么?
46、怎么唤醒一个阻塞的线程?
- notify()
47、Java中用到的线程调度算法是什么
- 同优先级线程组成先进先出队列(先到先服务),使用时间片策略
- 对高优先级,使用优先调度的抢占式策略
48、单例模式的线程安全性?
- 懒汉式:双重锁解决线程安全问题,双重锁就是在同步代码块外层加一个if判断。
if(instance == null){
synchronized(Bank.class){
if(instance == null){
instance = new Bank();
}
}
}
return instance;
49、线程类的构造方法、静态块是被哪个线程调用的?
- 线程类的构造方法、静态块是被 new这个线程类所在的线程所调用的,而 run 方法里面的代码才是被线程自身所调用的。
50、同步方法和同步块,哪个是更好的选择?
- 用同步块,同步方法完全取决于你的业务逻辑。同步方法相当于锁了this,范围是整个方法。当你的业务不需要这么做或者不能这么做时,那就用同步块锁适当的对像。
51、 Join()的执行原理
- wait()和notify()。
52、 举例说明集合类是不安全的(Vector是线程安全的,ArrayList是线程不安全的),如何解决?
public class NotSafeDeomo {
public static void main(String[] args) {
// List<String> list = new ArrayList<>();
List<String> list = new CopyOnWriteArrayList<String>;
for (int i = 0; i < 5; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 4));
System.out.println(list);
}, String.valueOf(i)).start();
}
}
}
解决方法
- 用Vector,虽然能保证数据一致性,但是访问性能会下降,所以不推荐
- 把线程不安全的ArrayList通过Collections工具类转化为线程安全的Collections.synchronized(new ArraysList<>());推荐使用
- 用JUC的CopyOnWriteArrayList
- 注意:CopyOnWriteArrayList只适用于读多写少的场景,因为每次写操作都会复制一个副本,性能很差。
53、 多线程中常见的异常
- ConcurrentModificationException