CopyOnWrite
- CopyOnWrite容器可以在并发场景中使用。CopyOnWriteArrayList的效率比ArrayList略有下降,空间利用率也下降了很多,但是CopyOnWriteArrayList是线程安全的。
- 集合容器是不能遍历的时候增删的(会报错),而用CopyOnWriteArrayList就不会报错,它的原理是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是可以对CopyOnWrite容器进行并发的读写,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
乐观锁
- 总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。
- 传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。
- 总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁。更多的时候用在数据库里。
Java内存模型
Java内存模型定义了一种多线程访问Java内存的规范。
(1)Java内存模型将内存分为了主内存和工作内存。类的状态,也就是类之间共享的变量,是存储在主内存中的,每次Java线程用到这些主内存中的变量的时候,会读一次主内存中的变量,并让这些内存在自己的工作内存中有一份拷贝,运行自己线程代码的时候,用到这些变量,操作的都是自己工作内存中的那一份。在线程代码执行完毕之后,会将最新的值更新到主内存中去
(2)定义了几个原子操作,用于操作主内存和工作内存中的变量
volatile关键字的作用
volatile关键字的作用主要有两个:
(1)使用volatile关键字修饰的变量,保证了其在多线程之间的可见性
原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
有序性:即程序执行的顺序按照代码的先后顺序执行。
可见性:指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值
当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。保证了每次读取到volatile变量,一定是最新的数据。
(2)代码底层执行不像我们看到的高级语言----Java程序这么简单,它的执行是Java代码-->字节码-->根据字节码执行对应的C/C++代码-->C/C++代码被编译成汇编语言-->和硬件电路交互,现实中,为了获取更好的性能JVM可能会对指令进行重排序,多线程下可能会出现一些安全的问题。使用volatile则会对禁止语义重排序,当然这也一定程度上降低了代码执行效率
从实践角度而言,volatile的一个重要作用就是和CAS结合,保证了原子性,详细的可以参见java.util.concurrent.atomic包下的类,比如AtomicInteger。
Synchronized和Volatile的比较
- Synchronized保证内存可见性和操作的原子性 ,保证不了有序性
- Volatile只能保证内存可见性
- Volatile不需要加锁,比Synchronized更轻量级,并不会阻塞线程(volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。)
- volatile标记的变量不会被编译器优化,而synchronized标记的变量可以被编译器优化(如编译器重排序的优化).
- volatile是变量修饰符,仅能用于变量,而synchronized是一个方法或块的修饰符。
- volatile本质是在告诉JVM当前变量在寄存器中的值是不确定的,使用前,需要先从主存中读取,因此可以实现可见性。而对n=n+1,n++等操作时,volatile关键字将失效,不能起到像synchronized一样的线程同步(原子性)的效果。
ThreadLocal
- 线程里可能有好几个对象,他们工作时可能需要共享一个资源,这个资源可以放在ThreadLocal里,在一个线程中修改不影响其他线程的使用。线程不安全的类在多线程中使用,如 SimpleDateFormat
- 简单说ThreadLocal就是一种以空间换时间的做法,在每个Thread里面维护了一个ThreadLocalMap,把数据进行隔离,数据不共享,自然就没有线程安全方面的问题了。
- 从本质来讲,就是每个线程都维护了一个map,而这个map的key就是threadLocal,而值就是我们set的那个值,每次线程在get的时候,都从自己的变量中取值,既然从自己的变量中取值,那肯定就不存在线程安全问题,总体来讲,ThreadLocal这个变量的状态根本没有发生变化,他仅仅是充当一个key的角色,另外提供给每一个线程一个初始值。如果允许的话,我们自己就能实现一个这样的功能,只不过恰好JDK就已经帮我们做了这个事情。
- ThreadLocal在Android中的应用,最典型的应用就是在android的messengeQueue-Looper模型中,Handler如何找到当前线程的Looper呢?我们平常直接在UI线程中new Handler()就可以了,里面就是mainLooper,但是Android怎么确定的UIx线程中new Handler()里面是mainLooper呢,答案就是通过将Looper作为ThreadLocal变量。
进程和线程的区别?多线程有什么好处?
进程:正在进行中的程序(直译)。
线程:就是进程中一个负责程序执行的控制单元(执行路径)
- 一个进程中可以多执行路径,称之为多线程,一个进程中至少要有一个线程。
- 开启多个线程是为了同时运行多部分代码。 每一个线程都有自己运行的内容。这个内容可以称为线程要执行的任务。
- 多线程好处:解决了多部分同时运行的问题。
- 什么时候使用多线程?当需要多部分代码同时执行的时候,可以使用。
线程池的好处
- 因为不需要每次处理复杂逻辑耗时操作都创建一个线程,比如加载网络,避免了线程的创建和销毁所带来的性能开销和消耗的时间能有效控制线程池的最大并发数,避免了大量线程间抢占资源而导致的阻塞现象
- 能够对线程进行简单的管理,并提供定时执行以及指定间隔循环执行等功能
- 避免频繁地创建和销毁线程,达到线程对象的重用。另外,使用线程池还可以根据项目灵活地控制并发的数目。
线程的sleep()方法和yield()方法、wait有什么区别?
- sleep()方法给其他线程运行机会时不考虑线程的优先级,因此可能会给低优先级的线程以运行的机会;yield()方法只会给相同优先级或更高优先级的线程以运行的机会; Thread.setPriority(Thread.MAX_PRIORITY);
- 线程执行sleep()方法后转入阻塞(blocked)状态,而执行yield()方法后转入就绪(ready)状态(yield() 使得线程放弃当前分得的 CPU 时间,但是不使线程阻塞,即线程仍处于可执行状态,随时可能再次分得 CPU 时间。调用 yield() 的效果等价于调度程序认为该线程已执行了足够的时间从而转到另一个线程.)
- sleep()方法声明抛出InterruptedException,而yield()方法没有声明任何异常
wait 和 sleep 区别?
sleep来自Thread类,和wait来自Object类
调用sleep()方法的过程中,线程不会释放对象锁。而 调用 wait 方法线程会释放对象锁
sleep(milliseconds)需要指定一个睡眠时间,时间一到会自动唤醒
请说出与线程同步以及线程调度相关的方法
wait:使一个线程处于等待(阻塞/冻结)状态,并且释放所持有的对象的锁;
sleep:使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理InterruptedException异常;
notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关;
notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;
全唤醒都判断效率不高,能不能只唤醒对方呢?
jdk1.5以后将同步和锁封装成了对象。并将操作锁的隐式方式定义到了该对象中,将隐式动作变成了显示动作。
为什么stop()方法被废弃而不被使用呢?原因是stop()方法太过于暴力,会强行把执行一半的线程终止。这样会就不会保证线程的资源正确释放,通常是没有给与线程完成资源释放工作的机会,因此会导致程序工作在不确定的状态下。
使用boolean类型的变量,来终止线程
public static class ChangeObjectThread extends Thread {
// 用于停止线程
private boolean stopMe = true;
public void stopMe() {
stopMe = false;
}
@Override
public void run() {
while (stopMe) {
synchronized (ThreadStopSafeBoolean.class) {
int v = (int) (System.currentTimeMillis() / 1000);
user.setId(v);
// to do sth
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
user.setName(String.valueOf(v));
}
// 让出CPU,给其他线程执行
Thread.yield();
}
}
}
synchronized关键字的用法?
当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法?
不能,一个对象的一个synchronized方法只能由一个线程访问。
线程安全问题产生的原因:
- 多个线程在操作共享的数据
- 操作共享数据的线程代码有多条
当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题的产生。
解决思路
就是将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程时不可以参与运算。必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算。
使用锁机制:synchronized 或 lock 对象
同步的好处:解决了线程的安全问题。
同步的弊端:相对降低了效率,因为同步外的线程的都会判断同步锁。
同步的前提:同步中必须有多个线程并使用同一个锁。
同步函数和同步代码块的区别:
同步函数的锁是固定的this。同步代码块的锁是任意的对象。建议使用同步代码块。
简述synchronized 和Lock的异同?
- Lock是Java 5以后引入的新的API,和关键字synchronized相比主要相同点:Lock 能完成synchronized所实现的所有功能;主要不同点:Lock有比synchronized更精确的线程语义和更好的性能,而且不强制性的要求一定要获得锁。synchronized会自动释放锁,而Lock一定要求程序员手工释放,并且最好在finally 块中释放(这是释放外部资源的最好的地方)。
- Lock接口: 出现替代了同步代码块或者同步函数。将同步的隐式锁操作变成现实锁操作。同时更为灵活。可以一个锁上加上多组监视器。
- lock():获取锁。unlock():释放锁,通常需要定义finally代码块中。
Condition接口
替代了Object中的wait notify notifyAll方法。
将这些监视器方法单独进行了封装,变成Condition监视器对象,可以任意锁进行组合。
编写多线程程序有几种实现方式?
一种是继承Thread类;另一种是实现Runnable接口。两种方式都要通过重写run()方法来定义线程的行为,推荐使用后者,因为Java中的继承是单继承,一个类有一个父类,如果继承了Thread类就无法再继承其他类了,显然使用Runnable接口更为灵活。Runnable不是线程,是线程里运行的代码
class Demo implements Runnable// extends Fu
//准备扩展Demo类的功能,让其中的内容可以作为线程的任务执行
{
public void run() {
show();
}
public void show() {
for (int x = 0; x < 20; x++) {
System.out.println(Thread.currentThread().getName() + "....." + x);
}
}
}
class ThreadDemo {
public static void main(String[] args) {
Demo d = new Demo();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
t2.start();
}
}
举例说明同步和异步。
- 所谓的同步就是指阻塞式操作,而异步就是非阻塞式操作。
- 如果数据将在线程间共享。例如正在写的数据以后可能被另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就是共享数据,必须进行同步存取。
- 当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往更有效率。
启动一个线程是调用run()还是start()方法?
启动一个线程是调用start()方法,使线程所代表的虚拟处理机处于可运行状态,这意味着它可以由JVM 调度并执行,这并不意味着线程就会立即运行。run()方法是线程启动后要进行回调(callback)的方法。
线程的基本状态以及状态之间的关系?
创建并运行线程:
- 新建状态(New Thread):在Java语言中使用new 操作符创建一个线程后,该线程仅仅是一个空对象,它具备类线程的一些特征,但此时系统没有为其分配资源,这时的线程处于创建状态。线程处于创建状态时,可通过Thread类的方法来设置各种属性,如线程的优先级(setPriority)、线程名(setName)和线程的类型(setDaemon)等。
- 就绪状态(Runnable):使用start()方法启动一个线程后,系统为该线程分配了除CPU外的所需资源,使该线程处于就绪状态。此外,如果某个线程执行了yield()方法,那么该线程会被暂时剥夺CPU资源,重新进入就绪状态。
运行状态(Running):Java运行系统通过调度选中一个处于就绪状态的线程,使其占有CPU并转为运行状态。此时,系统真正执行线程的run()方法。
可以通过Thread类的isAlive方法来判断线程是否处于就绪/运行状态:当线程处于就绪/运行状态时,isAlive返回true,当isAlive返回false时,可能线程处于阻塞状态,也可能处于停止状态。
- 阻塞和唤醒线程阻塞状态(Blocked):一个正在运行的线程因某些原因不能继续运行时,就进入阻塞 状态。这些原因包括:
a) 当执行了某个线程对象的sleep()等阻塞类型的方法时,该线程对象会被置入一个阻塞集内,等待超时而自动苏醒。
b) 当多个线程试图进入某个同步区域时,没能进入该同步区域的线程会被置入锁定集,直到获得该同步区域的锁,进入就绪状态。
c) 当线程执行了某个对象的wait()方法时,线程会被置入该对象的等待集中,知道执行了该对象的notify()方法wait()/notify()方法的执行要求线程首先获得该对象的锁。
- 死亡状态(Dead):线程在run()方法执行结束后进入死亡状态。此外,如果线程执行了interrupt()或stop()方法,那么它也会以异常退出的方式进入死亡状态。
多次start一个线程会怎么样
会抛出java.lang.IllegalThreadStateException 线程状态非法异常,一个线程是不能执行两次的,一但start()之后,结束了就不能再去调用,像你的T1一样,结束就没了,除非再new Thread(),要执行同样的代码,可以一直产生新的线程去调用
如何在两个线程之间共享数据
通过在线程之间共享对象就可以了,然后通过wait(让线程处于冻结状态,被wait的线程会被存储到线程池中 )/notify/notifyAll、await/signal/signalAll进行唤起和等待,比方说阻塞队列BlockingQueue就是为线程之间共享数据而设计的
为什么wait()方法和notify()/notifyAll()方法要在同步块中被调用
这是JDK强制的,wait()方法和notify()/notifyAll()方法在调用前都必须先获得对象的锁
wait()方法和notify()/notifyAll()方法在放弃对象监视器时有什么区别
wait()方法和notify()/notifyAll()方法在放弃对象监视器的时候的区别在于:wait()方法立即释放对象监视器(锁),notify()/notifyAll()方法则会等待线程剩余代码执行完毕才会放弃对象监视器。
一个线程如果出现了运行时异常会怎么样
如果这个异常没有被捕获的话,这个线程就停止执行了。另外重要的一点是:如果这个线程持有某个某个对象的监视器,那么这个对象监视器会被立即释放
同步方法和同步块,哪个是更好的选择
同步块,这意味着同步块之外的代码是异步执行的,这比同步整个方法更提升代码的效率。请知道一条原则:同步的范围越小越好。
死锁:
指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象。就是多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。
public class DeadlockTest {
public static void main(String[] args) {
String str1 = new String("资源1");
String str2 = new String("资源2");
new Thread(new Lock(str1, str2), "线程1").start();
new Thread(new Lock(str2, str1), "线程2").start();
}
}
class Lock implements Runnable {
private String str1;
private String str2;
public Lock(String str1, String str2) {
super();
this.str1 = str1;
this.str2 = str2;
}
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + "运行");
synchronized (str1) {
System.out.println(Thread.currentThread().getName() + "锁住"
+ str1);
Thread.sleep(1000);
synchronized (str2) {
// 执行不到这里
System.out.println(Thread.currentThread().getName()
+ "锁住" + str2);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
线程1运行
线程1锁住资源1
线程2运行
线程2锁住资源2
线程1运行线程1锁住资源1线程2运行线程2锁住资源2两个线程是同时执行的,线程1锁住了资源1,线程2锁住了资源2,线程1企图锁住资源2,但是资源2已经被线程2锁住了,线程2企图锁住资源1,但是资源1已经被线程1锁住了,然后就死锁了。
你的同步(锁)有我的同步,我的同步有你同步
要出现死锁问题需要满足以下条件
- 互斥条件:一个资源每次只能被一个线程使用。
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
解决方法
- 如果想要打破互斥条件,我们需要允许进程同时访问某些资源,这种方法受制于实际场景,不太容易实现条件;
- 打破不可抢占条件,这样需要允许进程强行从占有者那里夺取某些资源,或者简单一点理解,占有资源的进程不能再申请占有其他资源,必须释放手上的资源之后才能发起申请,这个其实也很难找到适用场景;
- 进程在运行前申请得到所有的资源,否则该进程不能进入准备执行状态。这个方法看似有点用处,但是它的缺点是可能导致资源利用率和进程并发性降低;
- 避免出现资源申请环路,即对资源事先分类编号,按号分配。这种方式可以有效提高资源的利用率和系统吞吐量,但是增加了系统开销,增大了进程对资源的占用时间。
线程池相关方法
- Java为我们提供了ExecutorService线程池来优化和管理线程的使用。
- Executors类是官方提供的一个工厂类,它里面封装好了众多功能不一样的线程池,从而使得我们创建线程池非常的简便,主要提供了如下五种功能不一样的线程池。他们的内部其实是通过:ThreadPoolExecutor,它实现了ExecutorService接口,并封装了一系列的api使得它具有线程池的特性,其中包括工作队列、核心线程数、最大线程数等。
- 既然线程池就是ThreadPoolExecutor,所以我们要创建一个线程池只需要new ThreadPoolExecutor(…);就可以创建一个线程池,而如果这样创建线程池的话,我们需要配置一堆东西,非常麻烦,官方也不推荐使用这种方法来创建线程池,而是推荐使用Executors的工厂方法来创建线程池。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {...}
线程池的参数
- 当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中
- 表示在线程池中最多能创建多少个线程
- 如果线程池没有要执行的任务 存活多久
- 存活时间的单位
- 如果 线程池里管理的线程都已经用了,剩下的任务 临时存到LinkedBlockingQueue对象中排队
- newSingleThreadExecutor:创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
- newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
- newCachedThreadPool:创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。 - newScheduledThreadPool:创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
- newSingleThreadExecutor:创建一个单线程的线程池。此线程池支持定时以及周期性执行任务的需求。
- 如果希望在服务器上使用线程池,强烈建议使用newFixedThreadPool方法来创建线程池,这样能获得更好的性能。
自定义线程池ThreadPoolExecutor
自定义ThreadManager类管理多线程,维护三类线程池,例如请求网络数据线程交由长时间任务线程池执行,访问数据库交由短时间任务线程池执行,图片下载任务将由单任务线程池执行
public class ThreadManager {
private ThreadManager() {
}
private static ThreadManager instance = new ThreadManager();
private ThreadPoolProxy longPool;
private ThreadPoolProxy shortPool;
public static ThreadManager getInstance() {
return instance;
}
// 联网比较耗时
// 开启线程数一般是cpu的核数*2+1
public synchronized ThreadPoolProxy createLongPool() {
if (longPool == null) {
longPool = new ThreadPoolProxy(5, 5, 5000L);
}
return longPool;
}
// 操作本地文件
public synchronized ThreadPoolProxy createShortPool() {
if(shortPool==null){
shortPool = new ThreadPoolProxy(3, 3, 5000L);
}
return shortPool;
}
public class ThreadPoolProxy {
private ThreadPoolExecutor pool;
private int corePoolSize;
private int maximumPoolSize;
private long time;
public ThreadPoolProxy(int corePoolSize, int maximumPoolSize, long time) {
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.time = time;
}
/**
* 执行任务
* @param runnable
*/
public void execute(Runnable runnable) {
if (pool == null) {
pool = new ThreadPoolExecutor(corePoolSize, maximumPoolSize,
time, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue(10));
}
pool.execute(runnable); // 调用线程池 执行异步任务
}
/**
* 取消任务
* @param runnable
*/
public void cancel(Runnable runnable) {
if (pool != null && !pool.isShutdown() && !pool.isTerminated()) {
pool.remove(runnable); // 取消异步任务
}
}
}
}
生产者消费者
多个线程在处理同一资源,但是任务却不同。
生产者和消费者在同一时间段内共用同一个存储空间,生产者向空间里存放数据,而消费者取用数据,如果不加以协调可能会出现以下情况:
存储空间已满,而生产者占用着它,消费者等着生产者让出空间从而去除产品,生产者等着消费者消费产品,从而向空间中添加产品。互相等待,从而发生死锁。
wait()和notify()方法的实现
缓冲区满和为空时都调用wait()方法等待,当生产者生产了一个产品或者消费者消费了一个产品之后会唤醒所有线程。
public class Test1 {
private static Integer count = 0;
private static final Integer FULL = 10;
private static String LOCK = "lock";
public static void main(String[] args) {
Test1 test1 = new Test1();
new Thread(test1.new Producer()).start();
new Thread(test1.new Consumer()).start();
new Thread(test1.new Producer()).start();
new Thread(test1.new Consumer()).start();
new Thread(test1.new Producer()).start();
new Thread(test1.new Consumer()).start();
}
class Producer implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (LOCK) {
while (count == FULL) {
try {
LOCK.wait();
} catch (Exception e) {
e.printStackTrace();
}
}
count++;
System.out.println(Thread.currentThread().getName() + "生产者生产,目前总共有" + count);
LOCK.notifyAll();
}
}
}
}
class Consumer implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (LOCK) {
while (count == 0) {
try {
LOCK.wait();
} catch (Exception e) {
}
}
count--;
System.out.println(Thread.currentThread().getName() + "消费者消费,目前总共有" + count);
LOCK.notifyAll();
}
}
}
}
}
Semaphore(信号量)是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源,在操作系统中是一个非常重要的问题,可以用来解决哲学家就餐问题。
五种线程池
newFixedThreadPool
创建一个固定线程数量的线程池,示例为:
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
for (int i = 1; i <= 10; i++) {
final int index = i;
fixedThreadPool.execute(new Runnable() {
@Override
public void run() {
String threadName = Thread.currentThread().getName();
Log.v("zxy", "线程:"+threadName+",正在执行第" + index + "个任务");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
newSingleThreadExecutor
创建一个只有一个线程的线程池,每次只能执行一个线程任务,多余的任务会保存到一个任务队列中,等待线程处理完再依次处理任务队列中的任务,示例为:
ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
for (int i = 1; i <= 10; i++) {
final int index = i;
singleThreadPool.execute(new Runnable() {
@Override
public void run() {
String threadName = Thread.currentThread().getName();
Log.v("zxy", "线程:"+threadName+",正在执行第" + index + "个任务");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
newCachedThreadPool
创建一个可以根据实际情况调整线程池中线程的数量的线程池,示例为:
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 1; i <= 10; i++) {
final int index = i;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
cachedThreadPool.execute(new Runnable() {
@Override
public void run() {
String threadName = Thread.currentThread().getName();
Log.v("zxy", "线程:" + threadName + ",正在执行第" + index + "个任务");
try {
long time = index * 500;
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
为了体现该线程池可以自动根据实现情况进行线程的重用,而不是一味的创建新的线程去处理任务,设置了每隔1s去提交一个新任务,这个新任务执行的时间也是动态变化的。
newScheduledThreadPool
创建一个可以定时或者周期性执行任务的线程池,示例为:
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
//延迟2秒后执行该任务
scheduledThreadPool.schedule(new Runnable() {
@Override
public void run() {
}
}, 2, TimeUnit.SECONDS);
//延迟1秒后,每隔2秒执行一次该任务
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
}
}, 1, 2, TimeUnit.SECONDS);
newSingleThreadScheduledExecutor
创建一个可以定时或者周期性执行任务的线程池,该线程池的线程数为1,示例为:
ScheduledExecutorService singleThreadScheduledPool = Executors.newSingleThreadScheduledExecutor();
//延迟1秒后,每隔2秒执行一次该任务
singleThreadScheduledPool.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
String threadName = Thread.currentThread().getName();
Log.v("zxy", "线程:" + threadName + ",正在执行");
}
},1,2,TimeUnit.SECONDS);