java,
java,
一. Java多线程
1.什么是线程?
线程是操作系统能够进行调度的最小单位.一个进程包含多个线程,每个线程执行不同的任务.不同的进程使用不同的内存,同一个进程中的线程共享一片内存空间.
2.为什么需要线程?
一般我们启动一个程序就是启动一个进程,程序执行过程中由于程序控制范围之外的某些条件导致程序堵塞,程序此时只能中止;使用线程时,我们在该进程中创建多个线程,每个线程执行不同的任务,当由于某些外在条件导致某个线程堵塞时,其他线程仍然可以执行,不会导致程序中止.java中的线程机制是抢占式的,调度机会周期性的中断线程,将上下文切换到另外一个线程继续执行,使得每个线程都有时间去执行任务.
3.如何使用线程?
1)创建任务,实现runnable或者callable接口
class Cnt extends Thread{
public void run(){
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
};
System.out.println("Cnt thread is running!");
}
}
class Cnt2 implements Runnable{
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Cnt2 thread is running!");
}
}
2)将任务添加到线程中执行;
一种方法是显示创建Thread,调用start()方法创建线程执行任务;
public class ThreadTest {
public static void main(String[] args){
Cnt2 cnt2=new Cnt2();
new Thread(cnt2).start();
System.out.println("Main Method!");
}
}
一种方法是使用executor来创建CachedThreadPool/FixedThreadPool/SingleThreadPool来执行任务;
public class ThreadTest {
public static void main(String[] args){
Cnt2 cnt2=new Cnt2();
ExecutorService exec= Executors.newCachedThreadPool();
exec.execute(cnt2);
exec.shutdown();
System.out.println("Main Method!");
}
}
3)创建有返回结果的任务需要实现Callable<T>接口,返回Future<T>结果
import java.util.ArrayList;
import java.util.concurrent.*;
public class CallableTest {
public static void main(String[] args){
ExecutorService exec= Executors.newCachedThreadPool();
ArrayList<Future<String>> res=new ArrayList<>();
for(int i=0;i<10;i++){
res.add(exec.submit(new TaskWithResult()));
}
for(Future<String> fs:res){
try {
System.out.println(fs.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}finally {
if(!exec.isShutdown()){
exec.shutdown();
}
}
}
}
}
class TaskWithResult implements Callable<String> {
private static int id=0;
private final int cnt=id++;
@Override
public String call() throws Exception {
return "TaksWithResult-"+cnt;
}
}
4.多线程互斥Mutext
保证多线程的互斥性有两种方法:synchronized内置锁或ReentrantLock显示锁;
为什么需要ReentrantLock?
1)RentrantLock使用时可以显示锁定和释放锁,在释放锁时可以有其他的控制;
2)Lock.tryLock()可以尝试获取锁,当获取不到锁时可以执行其他的操作,而不是一直等待;
代码如下:
public class Task implements Runnable{
private Lock lock=new ReentrantLock();
private int counter=0;
@Override
public void run() {
boolean captured=false;
captured=lock.tryLock();
try{
if(captured){
counter++;
System.out.println(counter+" "+captured);
}else{
System.out.println("Not captured!");
}
}finally{
if(captured) lock.unlock();
}
}
}
public class BasicThread{
public static void main(String[] args){
Task t=new Task();
for(int i=0;i<10;i++){
new Thread(t).start();
}
}
}
输出:
1 true
Not captured!
2 true
3 true
Not captured!
4 true
5 true
6 true
7 true
8 true
可见,10个线程中有两个没有获取到锁,此时仍然会执行其后的语句
5.Volatile关键字
Volatile关键字用来修饰成员变量,作用是保证所修饰成员的可见性.可见性指在一个线程中修改的值,在其他线程中立即可以看到修改后的值.除了Volatile关键字,Synchronized和ReentrantLock也可以保证同步方法或同步块中内容的可见性.
private int i1; public int geti1(){return i1;}
volatile private i1; public int geti1{return i1;}
private int i1; public synchronized int geti1{return i1;}
第一种方法中获取的是当前线程中的副本,不一定是最新值;
第二种方法中获取的是主存中的值,因为volatile修饰的变量不会存在副本;
第三种方法中获取的是当前线程中的副本,但是synchronized关键字保证了主存和副本的同步;
private int i1; public void seti1(int v){ i1=v;}
volatile private i1; public void seti1(int v){ i1=v;}
private int i1; public void synchronized seti1(int v){ i1=v;}
第一种方法更新当前线程中的副本,其他线程不一定能立刻读取最新值;
第二种方法更新主存中的值,其他线程可以立马读取到;
第三种方法更新当前线程中的副本,但是synchronized关键字保证了将最新值更新到主存中,同时从主存读取最新值.
Volatile不能保证互斥,如下代码中,当有多个线程执行时,列表中元素的数量可能会超过100
public class VolatileTest implements Runnable{
volatile private int count=0;
ArrayList<Integer> list=new ArrayList<Integer>();
@Override
public void run() {
if(count<1000){
list.add(1);
count++;
}
}
}
6.线程协作
1)wait() notify() notifyAll()
2)condition
7.什么是死锁?如何避免死锁?
死锁指多个线程因竞争资源导致互相等待的僵局,若无外力作用,这些线程都无法继续执行.例如A线程锁定资源1,等待资源2,B线程锁定资源2,等待资源1,此时二者造成死锁;
避免死锁的技术:
1)按照相同的顺序加锁;
2)线程加锁时加上一定的时限,超过时限放弃对锁的请求,并释放占有的锁;
3)死锁检测,每当一个线程获得锁时,会在线程和锁相关的数据结构中进行记录,当线程申请锁时,也会进行相应的记录.当某线程申请锁失败时,会遍历锁的关系图,查看该锁是否已经被某线程占用,同时该线程正在申请的锁被自己占用,如果有便发生了死锁.有时,可能锁的关系图比较复杂,涉及多个线程造成死锁.
当检测到死锁发生时,一个可行的方法是释放所有锁,回退,等待一段随机的时间后重试;一个更好的方法是给这些线程设置优先级(或者随机设置优先级),让一个或几个线程回退,其他的线程继续等待;
8.什么是活锁?如何避免?
活锁指任务的执行没有被阻塞,由于某些条件没有满足,导致一直重复尝试,失败,尝试,失败.两个线程发生某些条件的碰撞之后重新执行,再次尝试后依然发生碰撞,长此下去就会发生活锁.
解决活锁的方法:
当检测到冲突时,暂停随机的一段时间后重试,将减小再次冲突的可能性.
9.什么是竞态条件?
如果程序运行顺序的改变会影响最终结果,这就是一个竞态条件.
在多线程环境下经常会存在竞态条件,如
public class ObjFactory {
private Obj instance;
public Obj getInstance(){
if(instance == null){
instance = new Obj();
}
return instance;
}
}
如果线程A,B同时执行getInstance方法,可能出现线程A B两次创建instance的情况;
解决方法:采用同步,创建同步方法或同步块
10.线程池的应用场景?优点?java线程池有哪几种以及相应的应用场景?线程池如何调优?
1)应用场景
需要处理的任务数量多,且单个任务的处理时间短;
2)优点
当任务数量多时,频繁的创建和销毁线程将会耗费系统的资源,另外jvm中存在创建过多的活动线程也会消耗大量的内存.线程池中的线程执行任务结束后可以重用而不是销毁,减小线程创建和销毁的开销,另外线程池可以设置线程数量的阈值,线程数量超过阈值时,后续任务将一直等待.防止产生过多的线程;
3)Java中的线程使用Executors创建,下图展示了线程池的结构
- newCachedThreadPool 根据需要创建新的线程,以前创建的空闲线程可以重用,如果没有可用的则重新创建.该线程池没有设置线程数量的上限,使用时一定要小心;
- newFixedThreadPool 固定线程数量的线程池,当任务数量超过线程数量时,将在线程池队列中等待
- newScheduledThreadPool 创建一个线程池,可以在给定的时间延迟后执行任务或者定期执行
- newSingleThreadPool 使用单个worker线程来执行任务,多个任务按照提交的顺序依次执行
4)线程池调优
- 不要将同步等待其他结果的任务排队.这些任务会长时间占用线程,导致无线程可用,长时间等待的任务需要设置最长等待时间,超时需要重新排队
- 合理设置线程池的大小
11.原子类
在java.util.concurrent.atomic包中,有AtomicInteger,AtomicLong等类,使用原子类可以代替synchronized或者显示Lock锁
public class Counter1{
private int counter;
public synchronized int add(){
counter++;
}
}
使用原子类时如下
public class Counter2{
private AtomicInteger counter=new AtomicInteger(0);
public void add(){
counter.incrementAndGet();
}
}
二者实现的效果是一样的,但是使用Atomic原子类的性能更好,原因是原子类内部通过JNI(Java native interface,java语言提供的与其他语言进行通讯的接口)的方式使用了硬件支持的CAS指令.
二. Java语法基础
1.Java语言三大特性
1)数据抽象:将想法从具体实例中抽象出来,根据功能创建类而不是细节;
2)继承:使得对象可以从基类获取对象和方法;
3)多态:使得多个类具有相同的接口,一个多态类型的操作可以应用到不同的类型的值上面;
2.容器
Java容器的作用是保存对象,实际上保存的是对象的引用,可以大致分为两类:
一个独立元素的序列,这些序列服从一条或者多条准则.List按顺序存储元素,Set无序但是不能有重复的元素,Queue按照排队规则来确定元素的顺序;
一组成对的键值对元素,允许用键来查找值.
容器类的关系图如下
3) List
- ArrayList底层基于数组实现,随机访问的速度快 ,但插入和删除速度慢,需要移动前后的元素,非同步实现;
- Vector底层基于数组实现,同步实现,线程安全;
- LinkedList底层基于链表实现,因此访问的速度慢,但是插入和删除的速度快,
4) Set
Set是一个集合,其中不能有重复元素.
5) Queue
Queue是先进先出的集合,有offer,peek,pool等方法;
6) Map
Map用于存储键值对
java5以上中提供了ConcurrentHashmap的实现,用于并发场景;
7) Arrays
静态类,用于操作array,常用方法 List<T> Arrays.asList(T… element)
List<String> stooges=Arrays.asList(“Larry”,”Moe”,”Curly”);
8) Collections
静态类,用于操作Collection,常用方法 boolean Colllections.addAll(Collection<? Super T> c, T… elements)
Collections.addAll(flavors,”Rocket”,”Rain”)
三. JVM基础
相关文章
- 暂无相关文章
用户点评