欢迎访问悦橙教程(wld5.com),关注java教程。悦橙教程  java问答|  每日更新
页面导航 : > > 文章正文

java,

来源: javaer 分享于  点击 9797 次 点评:5

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基础

相关文章

    暂无相关文章
相关栏目:

用户点评