多进程与多线程的区别:每个进程拥有自己的一整套变量,而线程则共享数据
线程:Thread(Runnable target) //构造一个新的线程,用于调用给定目标的run()方法
void start() //启动线程
Runnable r=()→{ task code};//用lambda表达式建立一个实例
Thread t=new Thread(r);
t.start();
线程状态:New(新创建)
Runnable(可运行)一旦调用start方法,线程就处于可运行状态,一个可运行的线程可能正在运行也可能没有运行
Blocked(被阻塞)当一个线程试图获取一个内部对象锁,而该锁被其他线程持有,则该线程进入阻塞状态
Waiting(等待)调用Object.wait方法或Thread.join方法
Timed waiting(计时等待)调用Thread.sleep方法
Terminated(被终止)run方法正常退出;因没有捕获的异常终止了run方法
线程优先级:每一个线程有一个优先级,默认下,一个线程继承它的父线程的优先级
setPriority(int newPriority)可以设置优先级
守护线程:为其他线程提供服务,若只剩下守护线程,虚拟机就退出了
t.setDaemon(true);//将线程设置为守护线程
未捕获异常处理器:在线程中无法捕获异常,通常将异常传递到一个用于未捕获异常的处理器;该处理器必须属于一个实现了Thread.UncaughtExceptionHandler接口的类
同步:防止代码块受并发访问的干扰
有两种方法:1)lock/Condition private Lock bankLock=new ReentrantLock();//创建重入锁对象
private Condition sufficientFunds=bankLock.newCondition();//创建条件对象:用于管那些已经获得了一个锁却不能做有用工作的线程
bankLock.lock();
try{
while(accounts[from]<amount)
sufficientFunds.await();//线程被阻塞,并放弃了锁
....
sufficientFunds.signalAll();//重新激活因这一条件而等待的线程
}
finally{
bankLock.unlock();
}
2)Synchronized声明 每一个对象有一个内部锁,并且该锁有一个内部条件
public synchronized void method(){ }
/ synchronized(obj){ }
对于这两种方法使用的建议:最好既不用lock/Condition,也不用Synchronized,而采用java.util.concurrent包中的一种机制
Volatile域:为实例域的同步访问提供了一个免锁机制,但volatile变量不能提供原子性。
原子性:对数据的操作是一个独立的、不可分割的整体,即一次操作是一个连续不可中断的过程,数据不会执行一半的时候被其他线程所修改。
死锁的四个必要条件:1)互斥条件:即某个资源在一段时间内只能由一个进程占有,不能同时被两个或两个以上的进程占有。
2)不可抢占条件:进程所获得的资源在未使用完毕之前,资源申请者不能强行的从资源占有手中夺取资 源,而只能由该资源的占有者进程自行释放。
3)占有且申请条件:进程至少已经占有一个资源但又申请新的资源。
4)循环等待条件:存在一个进程等待序列{P1...Pn},其中P1等待P2所占有的某一资源,...,Pn等待P1
预防死锁即破坏四个必要条件之一。
阻塞队列:常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程,当队列为空时,获取元素的线程会等待队列变为非空,当队列满时,存储元素的队列会等待队列可用。
void put(E element);//添加元素,在必要时阻塞
E take();//移除并返回元素,必要时阻塞
常见的阻塞队列:ArrayBlockingQueue,LinkedBlockingQueue,PriorityBlockingQueue...。java.util.concurrent包提供了线程安全的集合:ConcurrentHashMap/ConcurrentSkipListMap/ConcurrentSkipListSet/ConcurrentLinkedQueue
Callable和Future:提供有返回值且能抛出异常的异步方法。通过Callable的call()产生结果,Future的get()拿到结果。
FutureTask包装器可以将Callable转换成Future和Runnable。
Callable<Integer> myComputation=...;
FutureTask<Integer> task=new FutureTask<Integer>(myComputation);
Thread t=new Thread(task);
t.start();
...
Integer result=task.get();
线程池:当程序中创建了大量的生命周期很短的线程,应该使用线程池,一个线程池中包含许多准备运行的空闲线程。将Runnable对象交给线程池,会有一个线程调用run方法,当run方法退出时,线程不会死亡,而是池中准备为下一个请求提供服务。它能减少并发线程的数目。
ExecutorService pool=Executors.newCachedThreadPool();//调用Executors中的静态方法创建线程池
MatchCounter counter=new MatchCounter(new File(directory),keyword,pool);//MatchCounter实现了Callable接口的类
Future<Integer> result=pool.submit(counter);//调用submit提交Runnable或Callable对象,返回Future对象
pool.shutdown();//当不再提交任何任务时,关闭
Fork-Join框架:用于并行执行任务的框架,将大任务分割成若干小任务,最终汇总每个小任务结果后得到大任务结果的框架。
Counter counter=new Counter(numbers,0,numbers.length,x-> x>0.5);//Counter继承了RecursiveTask<Integer>
ForkJoinPool pool=new ForkJoinPool();
pool.invoke(counter);
System.out.println(counter.join());