多线程
并行与并发:
- 并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。
- 并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。并发往往在场景中有公用的资源,那么针对这个公用的资源往往产生瓶颈,我们会用TPS或者QPS来反应这个系统的处理能力。
线程状态
sleep和wait:wait会释放锁,线程间交互;sleep会一直持有锁,用于暂停执行。
wait/notify必须存在于synchronized块中。并且,这三个关键字针对的是同一个监视器(某对象的监视器)。这意味着wait之后,其他线程可以进入同步块执行。完成阻塞队列
notify和notifyAll之间的主要区别在于notify方法只通知一个Thread,而且它不能保证哪个线程会被唤醒,这取决于线程调度器。notifyAll方法将通知在该监视器上等待的所有线程或锁定。
interrupt()方法: 作用是中断线程。
interrupted()方法:判断的是当前线程是否处于中断状态。是类的静态方法,同时会清除线程的中断状.
isInterrupted()方法:判断调用线程是否处于中断状态。
join:把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。
monitor:Java中的每个对象都有一个监视器,来监测并发代码的重入。在非多线程编码时该监视器不发挥作用,反之如果在synchronized 范围内,监视器发挥作用。
volatile:保证此变量对所有的线程的可见性,这里的“可见性”,当一个线程修改了这个变量的值,volatile 保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。但普通变量做不到这点,普通变量的值在线程间传递均需要通过主内存(详见:Java内存模型)来完成; 禁止指令重排序优化。有volatile修饰的变量,赋值后多执行了一个“load addl $0x0, (%esp)”操作,这个操作相当于一个内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前的位置),只有一个CPU访问内存时,并不需要内存屏障;(什么是指令重排序:是指CPU采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理)。
yield:Yield是一个静态的原生(native)方法
Yield告诉当前正在执行的线程把运行机会交给线程池中拥有相同优先级的线程。
Yield不能保证使得当前正在运行的线程迅速转换到可运行的状态
它仅能使一个线程从运行状态转到可运行状态,而不是等待或阻塞状态
线程实现方式:继承Thread类,Runnable接口,Callable接口。
ThreadPoolExecutor
- **corePoolSize:**核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;
- **maximumPoolSize:**线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;
- **keepAliveTime:**表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
- **workQueue:**一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:ArrayBlockingQueue;LinkedBlockingQueue;SynchronousQueue;
- handler:表示当拒绝处理任务时的策略,有以下四种取值:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
核心线程不足时,生成新的线程–>核心线程满后,加入阻塞队列–>队列满后–>创建新线程到最大线程数
Executors创建线程池
- newSingleThreadExecutor
创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。 - newFixedThreadPool
创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。 - newCachedThreadPool
创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。 - newScheduledThreadPool
创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
FutureTask除了实现了Future接口外还实现了Runnable接口(即可以通过Runnable接口实现线程,也可以通过Future取得线程执行完后的结果),因此FutureTask也可以直接提交给Executor执行。
future.isDone() //return true,false 无阻塞
future.get() // return 返回值,阻塞直到该线程运行结束
ExecutorCompletionService 管理线程池处理任务的返回结果
package format;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class test3 {
private static Zhangsan zs = new Zhangsan(100);
public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
//25068 sync
ExecutorService p = Executors.newFixedThreadPool(100);
ExecutorCompletionService<Object> s = new ExecutorCompletionService<>(p);
long start = System.currentTimeMillis();
for(int i = 1;i<1000;i++) {
s.submit(new MyThreadw(zs));
}
for(int i = 1;i<1000;i++) {
System.out.println("运行结果:"+s.take().get(1000,TimeUnit.MILLISECONDS));
}
p.shutdown();
long end = System.currentTimeMillis();
System.out.println("运行时间:"+(end-start));
}
}
class MyThreadw implements Callable<Object>{
private Zhangsan zs;
public MyThreadw(Zhangsan zs) {
this.zs = zs;
}
@SuppressWarnings("static-access")
@Override
public String call(){
System.out.println("线程开始运行:"+Thread.currentThread().getName()+"money="+zs.getMoney());
if(zs.getMoney()>0)
zs.setMoney(zs.getMoney()-1);
try {
Thread.currentThread().sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("线程结束运行:"+Thread.currentThread().getName()+";money="+zs.getMoney());
return Thread.currentThread().getName()+";zs剩余"+zs.getMoney();
}
}
class Zhangsan{
private int money;
public Zhangsan(int money) {
this.money = money;
}
public synchronized int getMoney() {
return money;
}
public synchronized void setMoney(int money) {
this.money = money;
}
}
Lock
lock()方法是平常使用得最多的一个方法,就是用来获取锁。如果锁已被其他线程获取,则进行等待。
tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。
tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。
lockInterruptibly()方法比较特殊,当通过这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。也就使说,当两个线程同时通过lock.lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁,而线程B只有在等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。
- ReentrantLock
- ReentrantReadWriteLock readLock、writeLock 一个用来获取读锁,一个用来获取写锁。