java日常知识点积累

3,163 阅读35分钟

前言

  • 知识点整理来源于网络,个人只是单纯备份记录,如有侵权请联系本人处理(lvzhi1988@126.com),持续整理中。。。

1,java多线程与synchronized

  • java类型中的普通非static方法

示例代码:

package com.lvzhi;

/**
 * Created by lvzhi on 2017/9/3
 */
public class MyThread {
    private int num = 0;

    public synchronized void print(String args) throws InterruptedException {
        if (args.equals("a")) {
            System.out.println("I'am a ");
            num = 100;
            Thread.sleep(1000L);
        } else {
            System.out.println("I'am b ");
            num = 200;
            Thread.sleep(1000L);
        }
        System.out.println("Over");
    }

    public static void main(String[] args) {
        MyThread myThread1 = new MyThread();
        MyThread myThread2 = new MyThread();

        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    myThread1.print("a");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    myThread2.print("b");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        thread1.start();
        thread2.start();

    }
}

该段代码输出的结果是thread1和thread2的乱序结果,因为synchronized是在非static方法,那么对于线程来说就是myThread1和myThread2是相互独立的,类似于上边的代码跟没加synchronized效果是一样的。 结果如下: I'am a I'am b Over Over


*java类型中static方法

示例代码:

package com.lvzhi;

/**
 * Created by lvzhi on 2017/9/3
 */
public class MyThread {
    private static int num = 0;

    public static synchronized void print(String args) throws InterruptedException {
        if (args.equals("a")) {
            System.out.println("I'am a ");
            num = 100;
            Thread.sleep(1000L);
        } else {
            System.out.println("I'am b ");
            num = 200;
            Thread.sleep(1000L);
        }
        System.out.println("Over");
    }

    public static void main(String[] args) {
        MyThread myThread1 = new MyThread();
        MyThread myThread2 = new MyThread();

        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    myThread1.print("a");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    myThread2.print("b");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        thread1.start();
        thread2.start();

    }
}

该段代码输出的结果是thread1和thread2的有序结果,因为synchronized是在static方法上,static方法特性自己去百度,所以结果是有序的。 结果如下: I'am a Over I'am b Over

个人总结:synchronized加在static锁定的就是class类,类级别的锁。


2,java同一个类中不同方法的synchronized

  • 结论:同一个类中的所有synchronized方法,如果有一个synchronized方法已经被使用,那么该类中其他所有的synchronized方法都不能被使用,只能等第一个synchronized方法被释放,其他的synchronized方法才能使用;但是,其他的非synchronized方法可以被调用。结果就是synchronized锁定的class对象,第一个线程调用某个synchronized方法时,该class就被锁定了。 结合1,如果又new出来一个该类,那么是不冲突的。

3,脏读

  • 在oracle中,有一个概念叫“undo”,就是每次修改或者删除一条数据的时候,会把这个数据放置在undo中,如果需要回滚就去undo中找。 所以,如果在9点这一刻发起一个查询请求,检索一条数据,这个请求需要执行10分钟才能找到想要的数据,但是在9点5分的时候,有另个请求修改了想要查询的数据,那么在9点10分的时候,查看到的还是老数据,就是因为“undo”。 在mysql中应该也是一样的。查询发出的那一刻,数据是什么就是什么。

4, 类锁,对象锁,方法锁

  • 1 ,其实只有两种锁,方法锁和对象锁是同一个东西,所以只有对象锁和类锁。
  • 2,synchronized 加到 static 方法前面是给class 加锁,即类锁;而synchronized 加到非静态方法前面是给对象上锁。
  • 3, 如果多线程同时访问同一类的 类锁(synchronized 修饰的静态方法)以及对象锁(synchronized 修饰的非静态方法)这两个方法执行是异步的,原因:类锁和对象锁是2中不同的锁。
  • 4, 类锁对该类的所有对象都能起作用,而对象锁不能。

5,sleep & wait | notify | notifyAll

一、sleep & wait

  1. 两者来自不同的类(分别是Thread和Object) 2.sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或方法 3.wiat只能在同步控制方法或者同步控制块使用,而sleep可以在任何地方使用 4.sleep必须捕获异常,而wait不需要 二、wait和notify为什么会封装在Object类中,而不是像sleep方法在Thread中? wait和notify的本质是基于条件对象,而且只能由已经获得锁的线程调用。java的每个Object都有一个隐式锁,这个隐式锁关联一个Condition条件对象, 线程拿到这个隐式所(比如进入synchronized代码区域),就可以调用wait,语义是Condition条件对象上等待,其他的线程可以在这个Condition条件对象上等待 , 等满足条件之后,就可以调用notify或者notifyAll来唤醒所有在此条件对象上等待的线程。 三、死锁(产生死锁的必要条件) 互斥条件:一个资源每次只能被一个进程使用 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。 不剥夺条件:进程已获得的资源,在未使用完之前,不能强制剥夺 循环等待条件:若干进程之间形成一种头尾相接的循环资源关系。 这四个条件时死锁的必要条件,只有系统发生死锁,这些条件必然成立,只要上述条件之一不满足,就不发生死锁。 四、常识 1.在java中所有线程都是同时启动的,至于什么时候,哪个先执行,完全看谁先得到CPU的资源。 2.在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾回收线程。因为每当使用java命令执行一个类的时候,实际上启动了一个jvm, 每个jvm其实就是在操作系统中启动了一个线程 3.理解java编译器的线程处理和jvm。有助于编程高效、性能更好的java代码。 五、java中wait/notify机制 notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程, 而且不是按优先级。notify()方法和wait()方法的基本思想是给方法或代码块提供一种相互通信的方式,而这些方法或者代码块同步于某个特定对象。 代码块可以调用wait()方法来将自身的操作挂起,直到同一个对象上的其他同步方法或同步代码块以某种方式将其改变,并调用notfiy()方法来通知此代码块改变已经完成。 一个线程一般会因为它所同步的对象的某个属性没有设置,或者某个条件没有满足而调用wait()方法,这些由另一个线程的动作决定。 最简单的情况可能是资源因为正被另一个线程修改而繁忙,还有其他的可能情况。 通 常,多线程之间需要协调工作。例如,浏览器的一个显示图片的线程displayThread想要执行显示图片的任务,必须等待下载线 程 downloadThread将该图片下载完毕。如果图片还没有下载完,displayThread可以暂停,当downloadThread完成了 任务 后,再通知displayThread“图片准备完毕,可以显示了”,这时,displayThread继续执行。 以上逻辑简单的说就是:如果条件不满足,则等待。当条件满足时,等待该条件的线程将被唤醒。在Java中,这个机制的实现依赖于wait/notify。等待机制与锁机制是密切关联的。例如: synchronized(obj) {while(!condition) {obj.wait();}obj.doSomething();} 当线程A获得了obj锁后,发现条件condition不满足,无法继续下一处理,于是线程A就wait()。 在另一线程B中,如果B更改了某些条件,使得线程A的condition条件满足了,就可以唤醒线程A: synchronized(obj) {condition = true;obj.notify();} 需要注意的概念是: ◆调用obj的wait(), notify()方法前,必须获得obj锁,也就是必须写在synchronized(obj) {...} 代码段内。 ◆调用obj.wait()后,线程A就释放了obj的锁,否则线程B无法获得obj锁,也就无法在synchronized(obj) {...} 代码段内唤醒A。 ◆当obj.wait()方法返回后,线程A需要再次获得obj锁,才能继续执行。 ◆如果A1,A2,A3都在obj.wait(),则B调用obj.notify()只能唤醒A1,A2,A3中的一个(具体哪一个由JVM决定)。 ◆obj.notifyAll()则能全部唤醒A1,A2,A3,但是要继续执行obj.wait()的下一条语句,必须获得obj锁,因此,A1,A2,A3只有一个有机会获得锁继续执行,例如A1,其余的需要等待A1释放obj锁之后才能继续执行。 ◆当B调用obj.notify/notifyAll的时候,B正持有obj锁,因此,A1,A2,A3虽被唤醒,但是仍无法获得obj锁。直到B退出synchronized块,释放obj锁后,A1,A2,A3中的一个才有机会获得锁继续执行。 七、join join方法的功能就是使异步执行的线程变成同步执行。也就是说,当调用线程实例的start方法后,这个方法会立即返回,如果在调用start方法后后需要使用一个由这个线程计算得到的值,就必须使用join方法。如果不使用join方法,就不能保证当执行到start方法后面的某条语句时,这个线程一定会执行完。而使用join方法后,直到这个线程退出,程序才会往下执行。例如:你准备洗澡,需要准备的步骤,准备好衣服,沐浴的东西及烧水这些事情,由于烧水耗时太长,如果也放在主线程之中,就很浪费资源,所以如果我们另开线程去处 理,就会达到很好效果,于是乎在准备好衣服,沐浴的东西之前就去开子线程烧水,烧水的过程中主线程准备好衣服,沐浴的东西,此时就等待水烧好,然后方可痛 快的洗澡了!!

6,ConcurrentHashMap

有时间读读源码

7,Queue

  • 队列是一种数据结构.它有两个基本操作:在队列尾部加人一个元素,和从队列头部移除一个元素就是说,队列以一种先进先出的方式管理数据,如果你试图向一个 已经满了的阻塞队列中添加一个元素或者是从一个空的阻塞队列中移除一个元索,将导致线程阻塞.在多线程进行合作时,阻塞队列是很有用的工具。
  • add 增加一个元索 如果队列已满,则抛出一个IIIegaISlabEepeplian异常 remove 移除并返回队列头部的元素 如果队列为空,则抛出一个NoSuchElementException异常 element 返回队列头部的元素 如果队列为空,则抛出一个NoSuchElementException异常 offer 添加一个元素并返回true 如果队列已满,则返回false poll 移除并返问队列头部的元素 如果队列为空,则返回null peek 返回队列头部的元素 如果队列为空,则返回null put 添加一个元素 如果队列满,则阻塞 take 移除并返回队列头部的元素 如果队列为空,则阻塞

8,CopyOnWriteArrayList

  • 读的时候不需要加锁,如果读的时候有多个线程正在向ArrayList添加数据,读还是会读到旧的数据,因为写的时候不会锁住旧的ArrayList。
  • CopyOnWrite并发容器用于读多写少的并发场景。比如白名单,黑名单,商品类目的访问和更新场景
  • CopyOnWrite的缺点 CopyOnWrite容器有很多优点,但是同时也存在两个问题,即内存占用问题和数据一致性问题。所以在开发的时候需要注意一下。 内存占用问题: 因为CopyOnWrite的写时复制机制,所以在进行写操作的时候,内存里会同时驻扎两个对象的内存,旧的对象和新写入的对象(注意:在复制的时候只是复制容器里的引用,只是在写的时候会创建新对象添加到新容器里,而旧容器的对象还在使用,所以有两份对象内存)。如果这些对象占用的内存比较大,比如说200M左右,那么再写入100M数据进去,内存就会占用300M,那么这个时候很有可能造成频繁的Yong GC和Full GC。之前我们系统中使用了一个服务由于每晚使用CopyOnWrite机制更新大对象,造成了每晚15秒的Full GC,应用响应时间也随之变长。 针对内存占用问题,可以通过压缩容器中的元素的方法来减少大对象的内存消耗,比如,如果元素全是10进制的数字,可以考虑把它压缩成36进制或64进制。或者不使用CopyOnWrite容器,而使用其他的并发容器,如ConcurrentHashMap。 数据一致性问题: CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器。

9,阻塞和非阻塞队列

  • 阻塞队列与普通队列的区别在于,当队列是空的时,从队列中获取元素的操作将会被阻塞,或者当队列是满时,往队列里添加元素的操作会被阻塞。试图从空的阻塞队列中获取元素的线程将会被阻塞,直到其他的线程往空的队列插入新的元素。同样,试图往已满的阻塞队列中添加新元素的线程同样也会被阻塞,直到其他的线程使队列重新变得空闲起来,如从队列中移除一个或者多个元素,或者完全清空队列.
  • 1.ArrayDeque, (数组双端队列) 2.PriorityQueue, (优先级队列) 3.ConcurrentLinkedQueue, (基于链表的并发队列) 4.DelayQueue, (延期阻塞队列)(阻塞队列实现了BlockingQueue接口) 5.ArrayBlockingQueue, (基于数组的并发阻塞队列) 6.LinkedBlockingQueue, (基于链表的FIFO阻塞队列) 7.LinkedBlockingDeque, (基于链表的FIFO双端阻塞队列) 8.PriorityBlockingQueue, (带优先级的无界阻塞队列) 9.SynchronousQueue (并发同步阻塞队列)
  • ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。 LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列。 PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列。 DelayQueue:一个使用优先级队列实现的无界阻塞队列。 SynchronousQueue:一个不存储元素的阻塞队列。 LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。 LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
  • 方法\处理方式 抛出异常 返回特殊值 一直阻塞 超时退出 插入方法 add(e) offer(e) put(e) offer(e,time,unit) 移除方法 remove() poll() take() poll(time,unit) 检查方法 element() peek() 不可用 不可用
  • 抛出异常:是指当阻塞队列满时候,再往队列里插入元素,会抛出IllegalStateException(“Queue full”)异常。当队列为空时,从队列里获取元素时会抛出NoSuchElementException异常 。 返回特殊值:插入方法会返回是否成功,成功则返回true。移除方法,则是从队列里拿出一个元素,如果没有则返回null 一直阻塞:当阻塞队列满时,如果生产者线程往队列里put元素,队列会一直阻塞生产者线程,直到拿到数据,或者响应中断退出。当队列空时,消费者线程试图从队列里take元素,队列也会阻塞消费者线程,直到队列可用。 超时退出:当阻塞队列满时,队列会阻塞生产者线程一段时间,如果超过一定的时间,生产者线程就会退出。 BlockingQueue
  • 获取元素的时候等待队列里有元素,否则阻塞 保存元素的时候等待队列里有空间,否则阻塞 用来简化生产者消费者在多线程环境下的开发
  • ArrayBlockingQueue FIFO、数组实现 有界阻塞队列,一旦指定了队列的长度,则队列的大小不能被改变 在生产者消费者例子中,如果生产者生产实体放入队列超过了队列的长度,则在offer(或者put,add)的时候会被阻塞,直到队列的实体数量< 队列的初始size为止。不过可以设置超时时间,超时后队列还未空出位置,则offer失败。 如果消费者发现队列里没有可被消费的实体时也会被阻塞,直到有实体被生产出来放入队列位置,不过可以设置等待的超时时间,超过时间后会返回null
  • DelayQueue 有界阻塞延时队列,当队列里的元素延时期未到是,通过take方法不能获取,会被阻塞,直到有元素延时到期为止 如: 1.obj 5s 延时到期 2.obj 6s 延时到期 3.obj 9s 延时到期 那么在take的时候,需要等待5秒钟才能获取第一个obj,再过1s后可以获取第二个obj,再过3s后可以获得第三个obj 这个队列可以用来处理session过期失效的场景,比如session在创建的时候设置延时到期时间为30分钟,放入延时队列里,然后通过一个线程来获取这个队列元素,只要能被获取到的,表示已经是过期的session,被获取的session可以肯定超过30分钟了,这时对session进行失效。
  • LinkedBlockingQueue FIFO、Node链表结构 可以通过构造方法设置capacity来使得阻塞队列是有界的,也可以不设置,则为无界队列 其他功能类似ArrayBlockingQueue
  • PriorityBlockingQueue 无界限队列,相当于PriorityQueue + BlockingQueue 插入的对象必须是可比较的,或者通过构造方法实现插入对象的比较器Comparator<? super E> 队列里的元素按Comparator<? super E> comparator比较结果排序,PriorityBlockingQueue可以用来处理一些有优先级的事物。比如短信发送优先级队列,队列里已经有某企业的100000条短信,这时候又来了一个100条紧急短信,优先级别比较高,可以通过PriorityBlockingQueue来轻松实现这样的功能。这样这个100条可以被优先发送
  • SynchronousQueue 无内部容量的阻塞队列,put必须等待take,同样take必须等待put。比较适合两个线程间的数据传递。异步转同步的场景不太适用,因为对于异步线程来说在处理完事务后进行put,但是必须等待put的值被取走。

10,Future

这个配合callable使用,其实就是多线程有返回值,so easy !

11,master-worker

Master-Worker是常用的并行计算模式。它的核心思想是系统由两类进程协作工作:Master进程和Worker进程。Master负责接收和分配任务,Worker负责处理子任务。当各个Worker子进程处理完成后,会将结果返回给Master,由Master作归纳总结。其好处就是能将一个大任务分解成若干个小任务,并行执行,从而提高系统的吞吐量。处理过程如下图所示:

master-worker工作模式图
Master-Worker模式是一种将串行任务并行化的方案,被分解的子任务在系统中可以被并行处理,同时,如果有需要,Master进程不需要等待所有子任务都完成计算,就可以根据已有的部分结果集计算最终结果集。

12,Semaphore信号量

Semaphore当前在多线程环境下被扩放使用,操作系统的信号量是个很重要的概念,在进程控制方面都有应用。Java 并发库 的Semaphore 可以很轻松完成信号量控制,Semaphore可以控制某个资源可被同时访问的个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。比如在Windows下可以设置共享文件的最大客户端访问个数。 Semaphore实现的功能就类似厕所有5个坑,假如有10个人要上厕所,那么同时只能有多少个人去上厕所呢?同时只能有5个人能够占用,当5个人中 的任何一个人让开后,其中等待的另外5个人中又有一个人可以占用了。另外等待的5个人中可以是随机获得优先机会,也可以是按照先来后到的顺序获得机会,这取决于构造Semaphore对象时传入的参数选项。单个信号量的Semaphore对象可以实现互斥锁的功能,并且可以是由一个线程获得了“锁”,再由另一个线程释放“锁”,这可应用于死锁恢复的一些场合。 ps:有个重要问题,该段代码在main方法中运行,可以达到预期的效果,如果用junit的方法来测试,其预期的结果差很远。

public class SemaphoreTest {
    public static void main(String[] args) {
        // 线程池
        ExecutorService exec = Executors.newCachedThreadPool();
        // 只能5个线程同时访问
        final Semaphore semp = new Semaphore(5);
        // 模拟20个客户端访问
        for (int index = 0; index < 50; index++) {
            final int NO = index;
            Runnable run = new Runnable() {
                public void run() {
                    try {
                        // 获取许可
                        semp.acquire();
                        System.out.println("Accessing: " + NO);
                        Thread.sleep((long) (Math.random() * 10000));
                        // 访问完后,释放
                        semp.release();
                        //availablePermits()指的是当前信号灯库中有多少个可以被使用
                        System.out.println("-----------------" + semp.availablePermits());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            exec.execute(run);
        }
        // 退出线程池
        exec.shutdown();
    }

13,condition

  1. Condition可以替代传统的线程间通信,用await()替代wait,用signal替代notify(),用signalAll()替代notifyAll()。因为Object下面的wait/notify/notifyAll方法都是final的,所以名称上全都发生了改变。传统线程通信方式,condition都能实现。
  2. 注意:condition()是被绑定到Lock上面的,要创建一个Lock的conditon,需要用newCondition 。现在知道了,synchronized和notidy/wait/notifyAll结合使用, lock和condition的await/signal/signalAll结合使用。
  3. condition的强大之处,它可以为多个线程之间创建不同的condition。
public class ConditionTest {
    private static Lock lock = new ReentrantLock();
    private static final Condition firstCondition = lock.newCondition();
    private static final  Condition secondCondition = lock.newCondition();
    //以上的两个condition是不一样的,不能用secondCondition去唤醒firstCondition
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    lock.lock();
                    System.out.println("wait");
                    firstCondition.await();
                    System.out.println("over");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    lock.unlock();
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    lock.lock();
                    System.out.println("enter");
                    Thread.sleep(10000);
                    System.out.println("out");
                    firstCondition.signal();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    lock.unlock();
                }
            }
        }).start();
    }
}

14,ReentrantLock(重入锁)

公平锁:公平锁讲究先来先到,线程在获取锁时,如果这个锁的等待队列中已经有线程在等待,那么当前线程就会进入等待队列中; 非公平锁:不管是否有等待队列,如果可以获取锁,则立刻占有锁对象。 1、ReentrantLock 拥有Synchronized相同的并发性和内存语义,此外还多了 锁投票,定时锁等候和中断锁等候

 线程A和B都要获取对象O的锁定,假设A获取了对象O锁,B将等待A释放对O的锁定,

 如果使用 synchronized ,如果A不释放,B将一直等下去,不能被中断

 如果 使用ReentrantLock,如果A不释放,可以使B在等待了足够长的时间以后,中断等待,而干别的事情
  • ReentrantLock获取锁定与三种方式: a) lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁 b) tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false; c)tryLock(long timeout,TimeUnit unit), 如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false; d) lockInterruptibly:如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到或者锁定,或者当前线程被别的线程中断 2、synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中 3、在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态; 目前来说,就是lock和unlock,其它的也不知道多少,等到再找时间再具体研究。

15 ReentrantReadWriteLock(读写锁)

  • 读写锁的机制: "读-读"不互斥 "读-写"互斥 "写-写"互斥
  • 即在任何时候必须保证: 只有一个线程在写入; 线程正在读取的时候,写入操作等待; 线程正在写入的时候,其他线程的写入操作和读取操作都要等待;
  • java.util.concurrent.locks包定义了两个锁类,ReentrantLock和ReentrantReadWriteLock类。 当有很多线程都从某个数据结构中读取数据而很少有线程对其进行修改时,后者就很有用了。在这种情况下,允许读取器线程共享访问是合适的。当然,写入器线程依然必须是互斥访问的 下面是使用读/写锁的必要步骤: (1) 创建一个ReentrantReadWriteLock对象 [java] view plain copy private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    (2)抽取读锁和写锁: [java] view plain copy private Lock readLock = rwl.readLock();//得到一个可被多个读操作共用的读锁,但它会排斥所有写操作
    private Lock writeLock = rwl.writeLock();//得到一个写锁,它会排斥所有其他的读操作和写操作
    (3) 对所有访问者加读锁 [java] view plain copy public double getTotalBalance(){
    readLock.lock();
    try{...};
    finally{readLock.unlock();}
    } 对所有修改者加写锁 [java] view plain copy public void transfer(){
    writeLock.lock();
    try{...};
    finally{writeLock.unlock();}
    }

16, CountDownLatch、CyclicBarrier和Semaphore

其实,这三个都可以用于多线程中,很久以前就用过,只不过又忘记了。 其实就是,可以等待所有的线程都执行完,可以汇总一下。再此,再记录一下。

17,Disruptor

暂时不研究,目前所有的项目都没用过,用过的框架中不知道用没有用,所以,仅此先记录一下,如果以后工作中用到,再仔细研究。

18, 同步、异步、阻塞、非阻塞

  • 1,同步和异步是针对应用程序与内核的交互而言的。

  • 2,阻塞和非阻塞是针对于进程在访问数据的时候,根据IO操作的就绪状态来采取的不同方式,说白了是一种读取或者写入操作函数的实现方式,阻塞方式下读取或者写入函数将一直等待,而非阻塞方式下,读取或者写入函数会立即返回一个状态值。

  • 由上描述基本可以总结一句简短的话,同步和异步是目的,阻塞和非阻塞是实现方式。


  • 1,同步: 指的是用户进程触发IO操作并等待或者轮询的去查看IO操作是否就绪。
  • 2,异步: 异步是指用户进程触发IO操作以后便开始做自己的事情,而当IO操作已经完成的时候会得到IO完成的通知(异步的特点就是通知) 告诉朋友自己合适衣服的尺寸,大小,颜色,让朋友委托去卖,然后自己可以去干别的事。(使用异步IO时,Java将IO读写委托给OS处理,需要将数据缓冲区地址和大小传给OS)
  • 3,阻塞: 所谓阻塞方式的意思是指, 当试图对该文件描述符进行读写时, 如果当时没有东西可读,或者暂时不可写, 程序就进入等待 状态, 直到有东西可读或者可写为止 去公交站充值,发现这个时候,充值员不在(可能上厕所去了),然后我们就在这里等待,一直等到充值员回来为止。(当然现实社会,可不是这样,但是在计算机里确实如此。)
  • 4,非阻塞: 非阻塞状态下, 如果没有东西可读, 或者不可写, 读写函数马上返回,而不会等待, 银行里取款办业务时,领取一张小票,领取完后我们自己可以玩玩手机,或者与别人聊聊天,当轮我们时,银行的喇叭会通知,这时候我们就可以去了。

  • 同步阻塞IO(JAVA BIO): 同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。

  • 同步非阻塞IO(Java NIO) : 同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。用户进程也需要时不时的询问IO操作是否就绪,这就要求用户进程不停的去询问。

  • 异步阻塞IO(Java NIO):
    此种方式下是指应用发起一个IO操作以后,不等待内核IO操作的完成,等内核完成IO操作以后会通知应用程序,这其实就是同步和异步最关键的区别,同步必须等待或者主动的去询问IO是否完成,那么为什么说是阻塞的呢?因为此时是通过select系统调用来完成的,而select函数本身的实现方式是阻塞的,而采用select函数有个好处就是它可以同时监听多个文件句柄(如果从UNP的角度看,select属于同步操作。因为select之后,进程还需要读写数据),从而提高系统的并发性!

  • (Java AIO(NIO.2))异步非阻塞IO:
    在此种模式下,用户进程只需要发起一个IO操作然后立即返回,等IO操作真正的完成以后,应用程序会得到IO操作完成的通知,此时用户进程只需要对数据进行处理就好了,不需要进行实际的IO读写操作,因为真正的IO读取或者写入操作已经由内核完成了。

同步和异步是相对于应用和内核的交互方式而言的,同步 需要主动去询问,而异步的时候内核在IO事件发生的时候通知应用程序,而阻塞和非阻塞仅仅是系统在调用系统调用的时候函数的实现方式而已。

  • For Example: 如果你想吃一份宫保鸡丁盖饭: 同步阻塞:你到饭馆点餐,然后在那等着,还要一边喊:好了没啊! 同步非阻塞:在饭馆点完餐,就去遛狗了。不过溜一会儿,就回饭馆喊一声:好了没啊! 异步阻塞:遛狗的时候,接到饭馆电话,说饭做好了,让您亲自去拿。 异步非阻塞:饭馆打电话说,我们知道您的位置,一会给你送过来,安心遛狗就可以了。

19,AIO

目前理解起来有点没有完全搞明白,等以后再搞一次。

20,&、|、^、<<、>>>

A = 0011 1100 B = 0000 1101

操作符 描述 例子
如果相对应位都是1,则结果为1,否则为0 (A&B),得到12,即0000 1100
| 如果相对应位都是0,则结果为0,否则为1 (A | B)得到61,即 0011 1101
^ 如果相对应位值相同,则结果为0,否则为1 (A ^ B)得到49,即 0011 0001
按位补运算符翻转操作数的每一位,即0变成1,1变成0。 (〜A)得到-61,即1100 0011
<<  按位左移运算符。左操作数按位左移右操作数指定的位数。 A << 2得到240,即 1111 0000
>>  按位右移运算符。左操作数按位右移右操作数指定的位数。 A >> 2得到15即 1111
>>>  按位右移补零操作符。左操作数的值按右操作数指定的位数右移,移动得到的空位以零填充。 A>>>2得到15即0000 1111
> >* 负数的表示方法: 原码:一个整数,按照绝对值大小转换成的二进制数,称为原码。 反码:将二进制数按位取反,所得的新二进制数称为原二进制数的反码。 补码:反码加1称为补码。 ![此处输入图片的描述][2]

21,java中byte转换int时与0xff进行运算的原因

byte只有8位,如果byte中的数字为负数,那么在默认转化位int的时候会发生错误,0XFF默认是整形,运算后,就相当于显式的转换为int,不会出现二进制上的错误。

⑴一个数为正,则它的原码、反码、补码相同 ⑵一个数为负,刚符号位为1,其余各位是对原码取反,然后整个数加1

-1的原码为 10000001 -1的反码为 11111110

-1的补码为 11111111

0的原码为 00000000 0的反码为 11111111(正零和负零的反码相同)

0的补码为 100000000(舍掉打头的1,正零和负零的补码相同)

Integer.toHexString的参数是int,如果不进行&0xff,那么当一个byte会转换成int时,由于int是32位,而byte只有8位这时会进行补位, 例如补码11111111的十进制数为-1转换为int时变为11111111111111111111111111111111好多1啊,呵呵!即0xffffffff但是这个数是不对的,这种补位就会造成误差。 和0xff相与后,高24比特就会被清0了,结果就对了。 Java中的一个byte,其范围是-128~127的,而Integer.toHexString的参数本来是int,如果不进行&0xff,那么当一个byte会转换成int时,对于负数,会做位扩展,举例来说,一个byte的-1(即0xff),会被转换成int的-1(即0xffffffff),那么转化出的结果就不是我们想要的了。 而0xff默认是整形,所以,一个byte跟0xff相与会先将那个byte转化成整形运算,这样,结果中的高的24个比特就总会被清0,于是结果总是我们想要的

22,clone

  • shadow clone(浅克隆):通常只是对克隆的实例进行复制,但里面的其他子对象,都是共用的。【简单粗暴的理解:被克隆对象的基本数据类型属性都拷贝了,但是被克隆对象的对象属性,只是拷贝了个引用,该引用还是指向了堆中原来对象。一句话,如果克隆对象修改基本类型属性,那么被克隆对象不会被修改;如果克隆对象修改对象属性,那么被克隆对象就被修改了】
  • deep clone(深克隆): 克隆的时候会复制它的子对象的引用,里面所有的变量和子对象都是又额外拷贝了一份。【简单粗暴的理解:被克隆对象完全被复制了一份,克隆出来的对象无论修改什么属性,被克隆对象都不会变动】
  • 代码示例
package com.lvzhi.clone;

import java.io.Serializable;

/**
 * Created by lvzhi on 2018/1/14
 */
public class Wife implements Serializable {
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Wife{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

package com.lvzhi.clone;

import java.io.*;

/**
 * Created by lvzhi on 2018/1/14
 */
public class Husband implements Cloneable, Serializable {
    private String name;
    private int age;
    private Wife wife;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Wife getWife() {
        return wife;
    }

    public void setWife(Wife wife) {
        this.wife = wife;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Husband husband = null;
        try{
            husband = (Husband)super.clone();
        }catch(CloneNotSupportedException e){
            e.printStackTrace();
        }finally{
            return husband;
        }
    }

    /**
     * 利用串行化深克隆一个对象,把对象以及它的引用读到流里,在写入其他的对象
     * 深克隆在clone方法中也可实现,只要是属性是对象的,都要单独调用clone方法,
     * 如果对象属性很多,显得很麻烦,所以一般都是用序列化方式来实现(不理解的
     * 可以单独交流)
     * @return
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public Object deepClone() throws IOException,ClassNotFoundException {
        //将对象写到流里
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(this);
        //从流里读回来
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        return ois.readObject();
    }

    @Override
    public String toString() {
        return "Husband{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", wife=" + wife +
                '}';
    }
}

package com.lvzhi.clone;

/**
 * Created by lvzhi on 2018/1/14
 */
public class Test {

    //浅克隆
    @org.junit.Test
    public void testShadowClone() throws Exception {
        Wife wife = new Wife();
        wife.setName("韩梅梅");
        wife.setAge(19);
        Husband husband = new Husband();
        husband.setName("李雷");
        husband.setAge(20);
        husband.setWife(wife);

        //克隆一个对象
        Husband husband1 = (Husband) husband.clone();

        System.out.println("husband,husband1是否为同一个对象:" + (husband == husband1));

        husband1.setAge(21);
        System.out.println(husband);
        System.out.println(husband1);
        System.out.println("以上结果看出,原对象的基本属性值age,并没有改变。");

        husband1.getWife().setAge(21);
        System.out.println(husband);
        System.out.println(husband1);
        System.out.println("以上结果可以看出,原对象和被克隆对象的对象属性都改动了,wife的年龄都是21了。");
        System.out.println("不能做错误的测试,譬如:直接又new了一个wife对象,然后,husband1.setWife(newWife),这样" +
                "husband中对象wife是不会改变的,想不明白的,再问我吧,呵呵");

    }

    //深克隆
    @org.junit.Test
    public void testDeepClone() throws Exception {
        Wife wife = new Wife();
        wife.setName("韩梅梅");
        wife.setAge(19);
        Husband husband = new Husband();
        husband.setName("李雷");
        husband.setAge(20);
        husband.setWife(wife);

        //克隆一个对象
        Husband husband1 = (Husband) husband.deepClone();

        System.out.println("husband,husband1是否为同一个对象:" + (husband == husband1));

        husband1.setAge(21);
        System.out.println(husband);
        System.out.println(husband1);
        System.out.println("以上结果看出,原对象的基本属性值age,并没有改变。");

        husband1.getWife().setAge(21);
        System.out.println(husband);
        System.out.println(husband1);
        System.out.println("以上结果可以看出,原对象什么都没变动");

    }

}

23,jvm部分

parallel collector(throughput collector) 并行收集器:使用多线程的方式,利用多CUP来提高GC的效率,主要以到达一定的吞吐量为目标。 concurrent collector(concurrent low pause collector) 并发收集器:使用多线程的方式,利用多CUP来提高GC的效率,并发完成大部分工作,使得gc pause短。

以上两者主要区别:throughput collector只在young area使用使用多线程,而concurrent low pause collector则在tenured generation也使用多线程

24,java的重写(Override)与重载(Overload)

方法的重写规则:

  • 参数列表必须完全与被重写方法的相同;
  • 返回类型必须完全与被重写方法的返回类型相同;
  • 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为public,那么在子类中重写该方法就不能声明为protected。
  • 父类的成员方法只能被它的子类重写。
  • 声明为final的方法不能被重写。
  • 声明为static的方法不能被重写,但是能够被再次声明。
  • 子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为private和final的方法。
  • 子类和父类不在同一个包中,那么子类只能够重写父类的声明为public和protected的非final方法。
  • 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
  • 构造方法不能被重写。 如果不能继承一个方法,则不能重写这个方法。

方法重载规则:

  • 被重载的方法必须改变参数列表(参数个数或类型或顺序不一样)
  • 被重载的方法可以改变返回类型;
  • 被重载的方法可以改变访问修饰符;
  • 被重载的方法可以声明新的或更广的检查异常;
  • 方法能够在同一个类中或者在一个子类中被重载。
  • 无法以返回值类型作为重载函数的区分标准。 如果不能继承一个方法,则不能重写这个方法。