• 【面试】线程


    1、线程的生命周期?

    1)新建状态(New):当线程对象创建后,即进入了新建状态。如:Thread t = new Thread();

    2)就绪状态(Runnable):当调用对象的start()方法(t.start()),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程就会立即执行;

    3)运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到执行状态;

    4)阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入到阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:

      (1)等待阻塞:运行状态的线程执行wait()方法,使本线程进入到等待阻塞的状态;

      (2)同步阻塞:线程在获取synchronized同步锁失败(因为锁被其他线程占用),会进入到同步阻塞;

      (3)其他阻塞:通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时,join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态;

    5)死亡状态(Dead):线程执行完或因异常退出了run()方法,该线程结束生命周期。

     

    2、线程的常用函数?

    1)yield(线程让步):暂停当前正在执行的线程对象,把执行机会让给相同或更高优先级的线程,但接下来CPU调度就绪状态中的某个线程具有一定的随机性,因此,有可能出现,A线程调用了yeild()方法之后,接下来CPU仍然调度了A线程的情况;

    2)join(线程加入):让一个线程等待另一个线程执行完成才继续执行。如在A线程的执行体内调用B线程的join()方法,则A线程被阻塞,直到B线程执行完成,A线程才能继续执行。

    3)sleep(线程睡眠):让当前的正在执行的线程暂停指定时间,并进入阻塞状态。在其睡觉的时间里,该线程未处于就绪状态,因此不会获得执行的机会。即使此时系统中没有任何其他可执行的线程,处于sleep()方法的线程也不会执行。因此sleep()方法常用来暂停线程执行。

     

    3、关于线程通信:wait()/notify()/notifyAll()?

    1)wait():导致当前线程等待并使其进入到等待阻塞状态。直到其他线程调用该同步锁对象的notify()或notifyAll()方法来唤醒此线程;

    2)notify():唤醒在此同步锁对象上等待的单个线程,如果有多个线程都在此同步锁对象上等待,则会任意选择其中某个线程进行唤醒操作,只有在当前线程放弃对同步锁对象的锁定,才可能执行被唤醒的线程;

    3)notifyAll():唤醒在此同步锁对象上等待的所有线程,只有在当前线程放弃对同步锁对象的锁定,才可能执行被唤醒的线程。

    这三个方法主要都是用于多线程中,但实际上都是Object类的本地方法。因此,理论上,任何Object对象都可以作为这三个方法的主调,在实际的多线程开发中,只有同步锁对象调用这三个方法,才能完成多线程间的通信。

    注意:

    (1)wait()方法执行后,当前线程立即进入到等待阻塞状态,其后面的代码不会执行;

    (2)notify()/notifyAll()方法执行后,将唤醒此同步锁对象上的(任意一个/所有)线程对象,但是此时还并没有释放同步锁对象,也就是说,如果notify()/notifyAll()后面还有代码,还会继续执行,直到当前线程执行完毕才会释放同步锁对象;

    (3)notify()/notifyAll()执行后,如果右面有sleep()方法,则会使当前线程进入到阻塞状态,但是同步对象锁没有释放,依然自己保留,那么一定时间后还会继续执行此线程,接下来如(2);

    (4)wait()/notify()/notifyAll()完成线程间的通信或协作都是基于不同对象锁的,因此,如果是不同的同步对象锁将失去意义,同时,同步对象锁最好是与共享资源对象保持一一对应关系;

    (5)当wait线程唤醒后并执行时,是这接着上次执行到的wait()方法代码后面继续往下执行的。

     

    4、synchronized关键字作用域?

    1)是某个对象实例内,synchronized.aMethod(){}可以防止多个线程同时访问这个对象的synchronized()方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中一个synchronized方法,其他线程不能同时访问这个对象中任何一个synchronized方法)。但是,不同对象实例的synchronized方法是不相干扰的。也就是说,其他线程照样可以同时访问相同类的另一个对象实例中的synchronized方法;

    2)是某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static方法,它可以对类的所有对象实例起作用。

    注意:

      (1)除了方法前使用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源进行互斥访问。用法是synchronized (this){/*区块*/},它的作用域是当前对象;

      (2)synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){}在继承类中并不是 synchronized f(){},而是变成f(){}。继承类需要你显示的指定它的某个方法为synchronized方法。

     

    5、synchronized和Lock的不同?

    1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;

    2)synchronized会自动释放锁(a.线程执行完同步代码会释放锁;b.线程执行过程中发生异常会释放锁),lock需要在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;

    3)Lock可以让等待锁的线程中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;

    4)通过Lock的isLocked()可以知道有没有成功获取锁,而synchronized却无法办到;

    5)Lock可以提高多个线程进行读写的操作;

    在性能上说,如果竞争资源不激烈。两者的性能是差不多的,而当竞争资源非常激烈的时候(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。

     

    7、乐观锁与悲观锁?

    https://www.cnblogs.com/Rain1203/p/10999499.html

    需掌握内容:

    1)简单介绍下什么是乐观锁、悲观锁?

    2)悲观锁的体现?传统关系型数据库,如行锁、表锁、读锁、写锁;synchronized。

    3)乐观锁的一种实现方式:CAS(Compare and Swap 比较并交换)

    4)CAS缺点?

     

    8、阻塞队列?

    https://www.cnblogs.com/tjudzj/p/4454490.html

    队列模式:

    1)先进先出(FIFO):先插入队列的元素也最先出队列,类似于排队的功能。从某种程度上来说这种队列也体现了一种公平性;

    2)后进先出(LIFO):后插入队列的元素最先出队列,这种队列优先处理最近发生的事情。

    常见的阻塞队列?

    1)ArrayBlockingQueue:基于数组的阻塞队列(ArrayBlockingQueue在生产者放入数据和消费者获取数据,都是共用一个锁对象,意味着两种两种无法真正并行运行);

    2)LinkedBlockingQueue:基于链表的阻塞队列(生产者端和消费者端分别采用了独立的锁来控制同步,这意味着在高并发的情况下生产者和消费者可以并行操作队列中的数据);

    3)DelayQueue:只有指定其延迟时间到了,才能够从队列中获取该元素(插入无限制,生产者永远不会被阻塞,消费者才会被阻塞);

    4)PriorityBlockingQueue:基于优先级的阻塞队列(优先级的判断通过构造函数传入的Compator对象来决定,不会阻塞生产者,只会阻塞消费者);

    5)SynchronousQueue:一种无缓冲的等待队列(消费者直接去找生产者,没有缓冲区)。

     

    9、死锁?

     

    public class TestDeadLock implements Runnable{  
    
    	   public int flag = 1;  
    	   static Object o1 = new Object(), o2 = new Object();  
    
    	    public static void main(String[] argv){  
    	        TestDeadLock td1 = new TestDeadLock();  
    	        TestDeadLock td2 = new TestDeadLock();  
    	        td1.flag = 1; 
    	        td2.flag = 0;  
    	        Thread t1 = new Thread(td1);  
    	        Thread t2 = new Thread(td2);  
    	        t1.start();  
    	        t2.start();  
    
    	    }  
    
    	    public void run(){  
    
    	        System.out.println("flag = "+ flag);  
    	        if(flag == 1){  
    	            synchronized (o1){  
    	                try{  
    	                    Thread.sleep(500);  
    	                }catch(Exception e){  
    	                    e.printStackTrace();  
    	                }  
    
    	                synchronized(o2){  
    	                    System.out.println("1");  
    	                }  
    	            }  
    	        }  
    
    	        if(flag == 0){  
    	            synchronized(o2){  
    	                try{  
    	                    Thread.sleep(500);  
    	                }catch(Exception e){  
    	                    e.printStackTrace();  
    	                }  
    	                synchronized(o1){  
    	                    System.out.println("0");  
    	                }  
    	            }  
    	        }  
    	    }  
    
    	}  
    

    解决方案:

    让所有的线程按照同样的顺序获得一组锁。这种方法消除了X和Y的拥有者分别等待对方的资源的问题。

    将多个锁组成一组并放到同一个锁下。前面Java线程死锁的例子中,可以创建一个银器对象的锁,于是在获得刀或叉之前都必须获得这个银器的锁。

    什么是死锁?

    是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。

     

    产生死锁的原因?

    1)竞争资源;

    2)进程间推进顺序非法

     

    死锁产生的四个必要条件?

    1)互斥条件:进程要求对所分配的资源进行排他性控制,即在一段时间内某资源仅为一进程所占用;

    2)请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放;

    3)不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放;

    4)环路等待条件:在发生死锁时,必然存在一个进程--资源的环形链。

     

    在系统中已经出现死锁后,应该及时检测到死锁的发生,并采取适当的措施来解决死锁。目前处理死锁的方法可归结为以下四种:

    预防死锁:

    这是一种比较简单和直观的事先预防方法。通过设置某些限制条件,去破坏产生死锁的四个必要条件中的一个或几个,来预防死锁。预防死锁是一种比较容易实现的方法,已被广泛使用。但是由于所施加的限制条件往往太严格,可能会导致系统资源利用率和系统吞吐量降低。

    避免死锁:

    该方法同样是属于事先预防的策略,但不需要事先采取各种限制措施去破坏死锁产生的四个必要条件,而是在资源的动态分配过程中,用某种方法去防止系统进入不安全状态,从而避免发生死锁(安全状态、银行家算法)。

    检测死锁:

    此方法允许系统在运行过程中发生死锁,但可以通过系统设置的检测,及时检测出死锁的发生,确定与死锁有关的进程和资源,然后采取适当措施,从系统中将已发生的死锁清除掉。

    解除死锁:

    是与检测死锁相配套的一种措施。当检测到系统中已发生死锁的时候,须将进程从死锁状态解除。常用的措施是撤销或挂起一些进程,以便回收一些资源,再将这些资源分配给已处于阻塞状态的进程,使之转为就绪状态,以继续运行。死锁的检测和解除措施,有可能使系统获得较好的资源利用率和吞吐量,但在实现上难度也最大。(资源剥夺法、撤销进程法、进程回退法)

     

    https://blog.csdn.net/hd12370/article/details/82814348

     

    10、生产者、消费者模型?

    https://www.cnblogs.com/moongeek/p/7631447.html

    如何解决一个生产者与消费者的问题?

    1)wait() 与 notify() 方法;

    2)Lock与Condition机制;

    3)BlockingQueue阻塞队列。

     

     

    11、线程池

    https://blog.csdn.net/u011479540/article/details/51867886

    https://www.cnblogs.com/sachen/p/7401959.html

    常见的线程池?

    1)newCachedThreadPool:可缓存线程池。先查看池中有没有以前建立的线程,如果有,就直接使用;如果没有就建一个新的线程加入池中,缓存型线程池通常用于执行一些生存期很短的异步型任务;

    2)newFixedThreadPool:可重用固定个数的线程池。以共享的无界队列方式来运行这些线程;

    3)newScheduledThreadPool:定长线程池。支持定时及周期性任务执行;

    4)newSignleThreadPool:单线程线程池。只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFo, LIFO, 优先级)执行。

     

    newFixedThreadPool使用了哪个阻塞队列?

    new LinkedBlockingQueue<Runnable>():无界阻塞队列。

    创建可容纳固定数量线程的池子,每个线程的存活时间是无限的,当池子满了就不再添加新的线程;如果池中所有的线程均在繁忙状态,那么新任务会进入阻塞队列(无界的阻塞队列)。

    适用于执行长期的任务,性能好。

     

    线程池策略?

    JDK主要提供了四种饱和策略供选择。四种策略都作为静态内部类在ThreadPoolExecutor中进行实现。

    1)AbortPolicy中止策略:对拒绝任务抛弃处理,并抛出异常;

    2)DiscardPolicy抛弃策略:对拒绝任务直接无声抛弃,没有异常信息;

    3)DiscardOldestPolicy抛弃旧任务策略:对拒绝任务不抛弃,而是抛弃队列里等待最久的一个线程,然后把拒绝任务加到队列;

    4)CallerRunsPolicy调用者运行:既不抛弃任务也不抛出异常,直接运行任务的run()方法,换言之 将任务回退给调用者来直接运行。

     

     

    12、线程池线程数设置为多大比较合适:项目为计算型还是IO密集型

    https://blog.csdn.net/u013070853/article/details/49304099

     

    13、信号量Semaphore的使用?

    https://www.cnblogs.com/fingerboy/p/5359252.html

    可以维护某个共享资源被同时访问的次数,即 维护当前访问某一共享资源的线程个数,并提供了同步机制。例如,控制某一文件允许的并发访问数量。

     

    14、计数器?

    https://blog.csdn.net/shihuacai/article/details/8856370

    一个同步辅助类。一个线程(或者多个),等待另外N个线程完成某个事情之后才能执行。

    CountDownLatch最重要的方法是countDown() 和 await() ,前者主要是倒数一次,后者是等待倒数到0,如果没有倒数到0,会一直阻塞等待。

     

    15、callable和runnable?

    https://www.cnblogs.com/frinder6/p/5507082.html

    相同点:

    1)都是接口;

    2)都可以用来编写多线程;

    3)都需要调用Thread.start()启动线程;

    不同点:

    1)两种最大的不同点是:实现Callable接口的任务线程能返回执行结果;而实现Runnable接口的任务线程不能返回结果;

    2)Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继承上抛。

    注意点:Callable接口支持返回执行结果,此时需要调用FutureTask.get()方法实现,此方法会阻塞主线程直到获取到结果;当不调用此方法时,主线程不会阻塞。

     

    16、如何终止一个线程?

    https://blog.csdn.net/wxwzy738/article/details/8516253

    Thread.interrupt()方法并不会中断一个正在运行的线程,所以一般我们使用 共享变量(定义成volatile) 发出信号,告诉线程必须停止正在运行的任务。线程需要周期性的核查这一变量,然后有序的终止任务。但如果线程被阻塞,便不能核查共享变量。此时有两种情况:

    一、如果线程被Object.wait() , Thread.join() , Thread.sleep() 三种方式阻塞,那么正确的停止方法是:先设置共享变量,并调用interrupt() 方法;

    二、如果线程在 IO 操作进行时被阻塞,即调用阻塞该线程的套接字的close()方法。如果线程被IO操作阻塞,该线程将接受到一个SocketException异常,这与使用interrupt()方法引起的InterruptException异常被抛出非常相似。

     

    17、使用stop存在的问题?

    https://blog.csdn.net/kingzma/article/details/45739963

    根据SUN的官方文档得知,Thread.stop()方法是不安全的,stop()方法可能会发生下面两件事:

    1)即刻抛出ThreadDeath异常,在线程的run()方法内,包括cath或finally语句块中;

    2)释放该线程所持有的所有的锁。

     

    那么我们如何正确停止线程呢?

    1)使用violate boolean 变量来标识线程是否停止;

    2)停止线程时,需要调用停止线程的interrupt()方法,因为线程有可能在wait()或sleep(),提高停止线程的及时性;

    3)对于blocking IO的处理,尽量使用InterruptibleChannel来替代blocking IO。

     

    18、IO 最详解?

    https://blog.csdn.net/yczz/article/details/38761237

     

    并发:

    19、concurrent包下有哪些类?

    CountDownLanch、信号量、Lock、线程池、并发集合类

     

    20、三种分布式锁?

    https://blog.csdn.net/wuzhiwei549/article/details/80692278

     

    21、为什么我们调用start()方法时会执行run()方法,为什么不直接调用run()方法?

    new 一个Thread,线程进入新建状态;调用start()方法,会启动一个线程并使线程进入就绪状态,当分配到时间片后就可以开始运行了。start()方法会执行线程的相应准备工作,然后自动执行run()方法的内容,这是真正的多线程工作。而直接执行run()方法,会把run()方法当做一个main线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。

    总结:调用start()方法可启动线程并使线程进入到就绪状态,而run()方法只是Thread的一个普通方法调用,还是在主线程里执行。

     

    22、双重检验锁方式实现单例模式的原理?

    package thread;
    /*双重校验锁实现对象单例模式*/
    
    public class Singleton {
    	private volatile static Singleton singleton;
    	
    	private Singleton() {
    		
    	}
    	
    	public static Singleton getInstance() {
    		//先判断对象是否已经实例过,没有实例化才进入加锁代码
    		if(singleton == null) {
    			//类对象加锁
    			synchronized(Singleton.class) {
    				if(singleton == null) {
    					singleton = new Singleton();
    				}
    			}
    		}
    		return singleton;
    	}
    }
    

    注:为什么signleton要使用volatile关键字修饰呢?

    singleton = new Singleton(); 这段代码其实是分为三步执行的:

    1)为singleton分配内存空间;

    2)初始化singleton;

    3)将singleton指向分配的内存地址。

    但是由于JVM具有指令重排的特性,执行顺序可能变成1->3-2>。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程T1执行了1和3,此时T2调用了getInstance()后发现singleton不为空,因此返回了singleton,但此时singleton还未被初始化。

    使用volatile可以禁止JVM的指令重排,保证在多线程环境下也能正常运行。

     

    23、介绍下volatile关键字?

    https://www.cnblogs.com/monkeysayhi/p/7654460.html

     

    24、说说synchronized关键字和volatile关键字的区别?

    1)volatile关键字是线程同步的轻量级实现,所以volatile性能肯定要比synchronized关键字要好。但是volatile只能用于变量而synchronized可以修饰方法及代码块,实际开发中synchronized用的比较多;

    2)多线程访问volatile不会发生阻塞,而synchronized可能会发生阻塞;

    3)volatile关键字能保证数据的可见性,但不能保证数据的原子性;synchronized关键字两者都能保证;

    4)volatile关键字主要用于解决变量在多个线程之间的可见性,而synchronized关键字解决的是多个线程之间访问资源的同步性。

     

    25、介绍下ThreadLocal?

    ThreadLocal类主要解决的是让每个线程绑定自己的值,可以将ThreadLocal类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。

    如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的本地副本,这也是ThreadLocal变量名的由来。他们可以使用get()和set()方法来获取默认值或将其值更改为当前线程所存的副本的值,从而避免了线程安全问题。

    举个栗子:

    public class ThreadLocalExample implements Runnable{
    
    	//SimpleDateFormat不是线程安全的,所以每个线程都要有自己独立的副本
    	private static final ThreadLocal<SimpleDateFormat> formatter = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMdd HHmm"));
    	
    	public static void main(String[] args) throws InterruptedException {
    		ThreadLocalExample obj = new ThreadLocalExample();
    		for(int i = 0;i < 10; i++) {
    			Thread t = new Thread(obj,""+i);
    			Thread.sleep(new Random().nextInt(1000));
    			t.start();
    		}
    	}
    	
    	@Override
    	public void run() {
    		System.out.println("Thread Name = "+Thread.currentThread().getName()+" default Formatter = "+formatter.get().toPattern());
    		
    		try {
    			Thread.sleep(new Random().nextInt(1000));
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		
    		formatter.set(new SimpleDateFormat());
    		System.out.println("Thread Name = "+Thread.currentThread().getName()+" formatter = "+formatter.get().toPattern());
    	}
    
    }
    

     输出结果如下:

    Thread Name = 0 default Formatter = yyyyMMdd HHmm
    Thread Name = 1 default Formatter = yyyyMMdd HHmm
    Thread Name = 1 formatter = yy-M-d ah:mm
    Thread Name = 0 formatter = yy-M-d ah:mm
    Thread Name = 2 default Formatter = yyyyMMdd HHmm
    Thread Name = 2 formatter = yy-M-d ah:mm
    Thread Name = 3 default Formatter = yyyyMMdd HHmm
    Thread Name = 3 formatter = yy-M-d ah:mm
    Thread Name = 4 default Formatter = yyyyMMdd HHmm
    Thread Name = 5 default Formatter = yyyyMMdd HHmm
    Thread Name = 5 formatter = yy-M-d ah:mm
    Thread Name = 4 formatter = yy-M-d ah:mm
    Thread Name = 6 default Formatter = yyyyMMdd HHmm
    Thread Name = 7 default Formatter = yyyyMMdd HHmm
    Thread Name = 6 formatter = yy-M-d ah:mm
    Thread Name = 7 formatter = yy-M-d ah:mm
    Thread Name = 8 default Formatter = yyyyMMdd HHmm
    Thread Name = 8 formatter = yy-M-d ah:mm
    Thread Name = 9 default Formatter = yyyyMMdd HHmm
    Thread Name = 9 formatter = yy-M-d ah:mm
    

     从输出代码可以看出,Thread-0已经改变了formatter的值,但Thread-2默认格式仍与初始值相同,其他线程也一样。

     

    26、ThreadLocal内存泄漏问题?

    ThreadLocalMap中使用的key为ThreadLocal的弱引用,而value使用的是强引用。所以,如果ThreadLocal没有被外部强引用的情况下,在垃圾回收的时候,key会被清理掉,而value不会被清理掉。这样一来,ThreadLocalMap中就会出现key为null的Entry。假如我们不做任何措施的话,value永远不会被GC回收,这个时候就可能产生内存泄漏。ThreadLocalMap实现中已经考虑了这种情况,在调用set()、get()、remove()方法的时候,会清理掉key为null的记录。使用完ThreadLocal后,最好手动调用remove()。

     

    关于线程池:

    1、为什么要使用线程池?

    1)降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗;

    2)提交响应速度:当任务到达时,任务可以不需要等待线程创建就能立即执行;

    3)提高线程的可管理性:线程时稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

     

    2、实现Runnable和Callable接口的区别?

    如果想让线程池执行任务的话需要实现Runnable或Callable接口。Runnable或Callable接口的实现类都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行。两者的区别在于Runnable接口不会返回结果但是Callable接口可以返回结果。

     

    3、执行execute()方法和submit()方法的区别是什么呢?

    1)execute()方法用于提交不需要返回值的任务,所以无法判断任务十分被线程池执行成功与否;

    2)submit()方法用于提交需要返回值的任务,线程池会返回一个Future类型的对象,通过这个Future对象可以判断任务是否执行成功。

     

    4、线程池的创建方式?

     

    关于Atomic原子类:

    原子类说简单点就是具有原子操作特征的类。

    1、JUC包中的原子类是哪4类?

    基本类型:使用原子的方式更新基本类型

    AtomicInteger:整型原子类;

    AtomicLong:长整型原子类;

    AtomicBoolean:布尔类型原子类;

    数组类型:使用原子的方式更新数组里的某个元素

    AtomicIntegerArray:整型数组原子类;

    AtomicLongArray:长整型数组原子类;

    AtomicReferenceArray:引用类型数组原子类;

    引用类型:

    AtomicReference:引用类型原子类;

    AtomicStampedeReference:原子更新引用类型里的字段原子类;

    AtomicMarkableReference:原子更新带有标记位的引用类型;

    对象的熟悉修改类型:

    AtomicIntegerFieldUpdater:原子更新整型字段的更新器;

    AtomicLongFieldUpdater:原子更新长整型字段的更新器;

    AtomicStampedReference:原子更新具有版本号的引用类型,该类将整数值与引用关联起来,用于解决原子的更新数据和数据的版本号,可以解决使用CAS进行原子更新时可能出现的ABA问题。

    2、AtomicInteger类常用方法?

    public final int get() //获取当前的值
    public final int getAndSet(int newValue)//获取当前的值,并设置新的值
    public final int getAndIncrement()//获取当前的值,并自增
    public final int getAndDecrement() //获取当前的值,并自减
    public final int getAndAdd(int delta) //获取当前的值,并加上预期的值
    boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将该值设置为输入值(update)
    public final void lazySet(int newValue)//最终设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
    

     

    关于AQS:

     

    关于JDK提供的并发容器总结:

    ConcurrentHashMap:线程安全的HashMap;

    CopyOnWriteArrayList:线程安全的List,在读多写少的场合性能非常好,远远好于Vector;

    ConcurrentLinkedQueue:高效的并发队列,使用链表实现。可以看作一个线程安全的LinkedList,这是一个非阻塞队列;

    BlockingQueue:这是一个接口,JDK内部通过链表,数组等方式实现了这个接口。表示阻塞队列,非常适合用于作为数据共享的通道;

    ConcurrentSkipListMap:跳表的实现。这是一个Map,使用跳表的数据结构进行快速查找。

     

    ConcurrentHashMap:读和写都能保证很高性能的HashMap,在进行读操作时(几乎)不需要加锁,而在写操作时通过锁分段技术只对所操作的段加锁而不影响客户端对其他段的访问。

     

    CopyOnWriteArrayList:所有的可变操作(add、set等等)都是通过创建底层数组的新副本来实现的。即 当List需要被修改的时候,并不是修改原有的内容,而是将原数据复制,将修改的内容写入副本,写完之后将指向原来内存指针指向新的内存,原来的内存就可以被回收了。

     

    ConcurrentLinkedQueue:Java提供的线程安全的Queue可以分为阻塞队列和非阻塞队列,其中阻塞队列的典型例子就是BlockingQueue,非阻塞队列的典型例子就是ConcurrentLinkedQueue,在实际应用中药根据实际需要选用阻塞队列或者非阻塞队列。阻塞队列可以通过加锁来实现,非阻塞队列可以通过CAS操作实现。

    ConcurrentLinkedQueue这个队列使用链表作为其数据结构,应该算是在高并发环境中性能最好的队列了。它之所有有很好的性能,主要是因为其内部复杂的实现(CAS非阻塞算法来实现线程安全)。ConcurrentLinkedQueue适合对性能要求较高,即 如果对队列的加锁成本很高则适合使用无锁的ConcurrentLinkedQueue来替代。

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

  • 相关阅读:
    python连接数据库异步存储
    pythonscrapy之MySQL同步存储
    头有点大
    scrapy反爬虫
    《猫抓老鼠》
    Linux下系统监控工具nmon
    探索式测试学习资料
    开始探索式测试学习之前的思考
    Software Quality Characteristics 软件质量特性
    自动化测试整理 STAF/STAX & Robot Framework
  • 原文地址:https://www.cnblogs.com/Rain1203/p/10998031.html
Copyright © 2020-2023  润新知