多线程创建、开启的四种方式
- 继承Thread类
public static void main(String[] args) {
//创建继承Thread类的自定义对象
MyThread myThread = new MyThread();
//调用start方法开启线程
myThread.start();
}
自定义线程类继承Thread,重写run方法
class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}
}
- 实现Runnable接口
//创建自定义线程类
MyRunnable myRunnable = new MyRunnable();
//创建线程类并指定任务
Thread t1 = new Thread(myRunnable);
Thread t2 = new Thread(myRunnable);
//开启
t1.start();
t2.start();
//实现Runnable接口并重写run方法
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}
}
- 实现Callable接口
MyCallable myCallable = new MyCallable();
//由Callable<Integer>创建一个FutureTask<Integer>对象
FutureTask<Integer> task = new FutureTask<>(myCallable);
//由FutureTask<Integer>创建一个Thread对象:
Thread thread = new Thread(task);
thread.start();
//callable接口类,指定泛型
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
//可以抛出异常
//具有返回值
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
return null;
}
}
- 线程池
//从线程池中获取两个线程
ExecutorService es = Executors.newFixedThreadPool(2);
//提交任务,可以是Callable接口也可以是Runable接口
Future<Integer> future1 = es.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i < 50; i++) {
sum += i;
}
return sum;
}
});
泛型
实例泛型
在你这个类被创建成对象的时候被指定
静态泛型
不需要创建对象直接可以指定泛型
1、Java中Runnable和Callable有什么不同?
- 相同点
- 都是接口
- 都可以编写多线程程序
- 都采用start方法启动
- 不同点
- Runnable没有返回值;Callable可以返回执行结果,是个泛型,和Future、FutureTask配合可以用来获取异步执行的结果
- Callable接口的call()方法允许抛出异常;Runnable的run()方法异常只能在内部消化,不能往上继续抛
注:Callalbe接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。
2、线程共有几种状态?分别是?
- New 初始状态
- Runnable 可运行状态
- Blocked 阻塞状态
- Waiting 无限期等待
- TimedWaiting 有限期等待
- Terminate 终止状态
3、什么是线程池?为什么要使用它?
线程入容器,可设定线程分配的数量上限
将预先创建的线程对象存入池中,并重用线程池中额线程对象
避免频繁的创建和销毁
减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
案例
//从线程池中获取两个线程
ExecutorService es = Executors.newFixedThreadPool(2);
//提交任务
Future<Integer> future1 = es.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i < 50; i++) {
sum += i;
}
return sum;
}
});
Future<Integer> future2 = es.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 50; i <= 100; i++) {
sum += i;
}
return sum;
}
});
//获取返回值
Integer result1 = future1.get();
Integer result2 = future2.get();
System.out.println(result1 + result2);
需求
子线程执行完毕之后,主线程才执行
ExecutorService中的方法
1.shutdown()方法
启动有序关闭,其中先前提交的任务将被执行,但不会接受任何新任务。
2.isTerminated()
如果所有任务在关闭后完成,则返回 true 。
对以上的代码进行修改
线程提交任务之后
//结束继续提交
es.shutdown();
//判断子线程是否执行完毕
while (!es.isTerminated()) {
System.out.println("还没执行完");
}
4、什么是线程不安全?
多个线程并发访问临界资源(一次仅允许一个进程使用的资源),破坏原子操作
5、保证线程安全的方法
- 同步synchronized
- 锁Lock 创建lock的对象Lock lock = new ReentrantLock();
注意:lock.unlock必须在finally里释放。否则会造成死锁
6、Java中的ReentrantLock与ReentrantReadWriteLock有什么区别?
ReentrantLock:重入锁,lock接口的实现类
ReentrantReadWriteLock:读写锁,一种支持一写多度的同步锁,支持多次分配读锁,是多个读操作可以并发执行
互斥规则:
- 写-写:互斥,阻塞
- 读-写:互斥,读阻塞写,写阻塞读
- 读-读:不互斥,不阻塞
ReentrantReadWriteLock rw = new ReentrantReadWriteLock();
//获取读锁
ReadLock readLock = rw.readLock();
//获取写锁
WriteLock writeLock = rw.writeLock();
7、什么是ThreadLocal变量?实现原理是什么?
ThreadLocal里面是一个map,可以值存的是当前的ThreadLocal的地址
8、局部内部类访问外部类的局部变量必须加final
原因:两者生命周期可能不一致,内部类对象的生命周期会大于外部类的局部变量
9、如何获得一个线程安全的List集合?
Vector、Collections.synchronizedXXX(XXX)、CopyOnWriteArrayList、CopyOnWriteArraySet、ConcurrentHashMap
获取线程安全的list集合
List<Integer> list = Collections.synchronizedList(new ArrayList<>());
CopyOnWriteArrayList<String> list= new CopyOnWriteArrayList();
list.add("aaa");
其他的一样
10、有哪些线程安全集合的执行效率高?
CopyOnWriteArrayList、CopyOnWriteArraySet、ConcurrentHashMap
11、CopyOnWriteArrayList是实现原理是什么?
CopyOnWriteArraySet集合的底层就是使用的list,只不过调用的是list的addIfAbsent()方法,原理相同
写操作加了锁,读操作不加锁
所以说写写互斥,读写不互斥
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
//创建一个新的数组,用于写操作,避免读写操作发生不一致,读操作读到是旧数组
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
12、ConcurrentHashMap的实现原理是什么?
segment<key,value>[]
最高支持16个线程并发
ConcurrentHashMap将锁进行了更细粒度的划分
实行分段加锁,16个段有16把锁 JDK7,JDK8,CAS比较交换算法
13、ConcurrentLinkedQueue的实现原理是什么?
- 线程安全,可高效读写的队列,高并发下性能最好的队列
- CAS 比较交换算法,修改的方法包括三个核心参数(V,E,N)
- V 要更新的变量,E 预期值,N 新值
- 只有当V==E时,进行交换,V=N,否则表示更新过,去消当前的操作
AtomicInteger:线程安全
构造方法
//创建线程安全的AtomicInteger对象
AtomicInteger n = new AtomicInteger(0);
//从线程池中获取两个线程
ExecutorService es = Executors.newFixedThreadPool(2);
Runnable runnable = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//调用++方法
n.getAndAdd(1);
}
}
};