精华内容
下载资源
问答
  • Offer——知识点储备-Java基础

    万次阅读 多人点赞 2016-10-21 23:54:52
    Offer——知识点储备-Java基础网址来源: http://www.nowcoder.com/discuss/5949?type=0&order=0&pos=4&page=2 参考资料:(java方面的一些面试答案) ...

    #剑指Offer——知识点储备-Java基础
    网址来源:
    http://www.nowcoder.com/discuss/5949?type=0&order=0&pos=4&page=2
    参考资料:(java方面的一些面试答案)
    http://www.nowcoder.com/discuss/6890?type=0&order=0&pos=29&page=1
    http://www.nowcoder.com/discuss/7342

    ##一:java基础
    ###1、java 集合类问题
    ###2、 hashMap相关问题
    ####2.1 HashMap的实现原理?
      回答主要是三个方面:(1)hashmap的基本原理 (2)hashmap的put存源码解读 (3)hashmap的get取源码解读
     hashmap是基于hash算法的key-value键值对,通过key可以快速的找到value值,解决了数组的增加和删除以及链表的查询效率低的问题。

    public V put(K key, V value) {
            if (key == null)//如果key为空,调用putForNullKey()处理
                return putForNullKey(value);
            int hash = hash(key);//通过key值获得hash码(看hash函数,是通过右移位,这种方式使数据散列均匀)
        //通过indexFor()获得对应table中的索引
            int i = indexFor(hash, table.length);//源码采用&的方式
        //取出table表中的元素,并循环单链表,判断key是否存在
            for (Entry<K,V> e = table[i]; e != null; e = e.next) {
                Object k;
              //hash码相同,且对象相同key值相同
              if (e.hash == hash && ((k = e.key) == key || key.equals(k)))          
           {        //新值替换旧值,并返回旧值
                    V oldValue = e.value;
                    e.value = value;
                    e.recordAccess(this);
                    return oldValue;
                }
            }
           //key不存在,加入新的元素
            modCount++;
            addEntry(hash, key, value, i);//hash码每个key都对应唯一的hash码,i是table的索引(通过hash函数算出来的)
            return null;
    }
    

      Hashmap通过调用put函数,实现键值对的存储。

    • (1)为了保证key的唯一性,一般选用final修饰的类型,比如基本类型的引用类型、String。

      问:如果不用这些类型,别人把key都改了,取出来的值肯定就是错的啦

    • (2)获得hash值int hash =
      hash(key);先得到key的hashcode值(因为每一个key的hashcode值都是唯一的),然后通过hash算法(底层是通过移位实现的),hash算法的目的就是让hashcode值能均匀的填充table表,而不是造成大量的集中冲突。

    • (3)将hash值与table数组的长度进行与运算,获得table数组下标int i = indexFor(hash,
      table.length);

      问题:传统的方法是通过hashcode与table数组长度相除取余,这个使得table表中的数据分布不均匀,产生大量集中冲突。

    • (4)通过下标获得元素存储的位置,先判断该位置上有没有元素(hashmap定义的一个类entity,基本结构包含三个元素key、value和指向下一个entity的next),若不同的hash值算在同一个table下标下,这就产生了冲突。常用的冲突解决算法是:拉链法(采用头插法)、线性探测法。
    • (5)若不等,调用addEntry()将新创建一个元素添加到table中。创建元素entity时,要判断table的填充容量是否大于负载因子0.75,若大于就要扩容,容量扩充到两倍。
    • (6)扩容的时候,是在内存中开辟新的内存空间,然后把原来的对象放到新的table数组中,这个过程叫做重新散列(rehashing)。但是在多线程的时候,会出现条件竞争。比如两个线程发现hashmap需要重新调整大小,它们会同时试着调整大小。在调整大小的过程中,存储在链表中的元素的次序会反过来,因为移动到新的bucket位置的时候,HashMap并不会将元素放在链表的尾部,而是放在头部,这是为了避免尾部遍历(tail
      traversing)。如果条件竞争发生了,那么就死循环了。
      可以参考这篇文章:http://coolshell.cn/articles/9606.html
    • (7)为了解决这个问题,采用了线程安全的CocurrentHashMap,它是行级锁,而hashtable是整体加锁。

    ####2.2常用的hash算法有哪些?
      链接:
    http://www.360doc.com/content/13/0409/14/10384031_277138819.shtml
    #####1、构造哈希函数的方法有哪些?

    • (1)直接定址法(或直接寻址法)
    • (2)数字分析法
    • (3)平方取中法(平方散列法)

      求index是一个非常频繁的操作,而乘法运算比除法来的省事(对CPU而言),所以把除法换成乘法和一个移位操作,公式: Index=(value*value)>>28(右移,是除以2^28. 记住:左移变大,是乘。右移变小,是除)

    • (4)折叠法
    • (5)除留余数法(这是最简单也是最常用的构造hash函数的方法)即对关键字取模Index=value%16(取模)
    • (6)随机数法

    #####2、解决hash冲突的方法有哪些?

    • (1)开放定址法(线性探测再散列、二次探测再散列)(线性探测法)
    • (2)再哈希法(双散列函数法)

      在发生冲突的时候,再次使用另一个散列函数,计算哈希函数地址,直到冲突不再发生。

    • (3)链地址法(拉链法)(常用重点)

       就是一个数组+链表(链表采用头插法)

    • (4)建立一个公共溢出区。

    #####3、常见的hash算法

    • (1)MD4
    • (2)MD5
    • (3)SHA-1算法

    ####2.3 HashMap为什么要扩容?
      Hashmap初始容量是16,若实际填充的容量是初始容量*负载因子,若全部填满查询的开销太大。因此hashmap的容量达到75%,就会扩容。扩容的大小是原来的一倍。

    • 注意jdk1.8中若链表的结点数大于8,则会转化成红黑树(目的提高查询效率)
      链接:http://www.cnblogs.com/leesf456/p/5242233.html

    #####2.3.1load factor的作用
      负载因子load factor=hashmap的数据量(entry的数量)/初始容量(table数组长度),负载因子越高,表示初始容量中容纳的数据会越多,虽然在空间中减少了开销,但是大量数据聚集,造成查询上的大量开销。负载因子越低,表示初始容量中容纳的数据会越少,造成大量的内存空间浪费,但是查询的效率比较高。这是一个矛盾体,为了寻求平衡点,负载因子0.75效果是最好的。
    ####2.4 ConcurrentHashMap的实现原理?
      链接:
    http://www.ibm.com/developerworks/cn/java/java-lo-concurrenthashmap/index.html
      要知道ConcurrentHashMap的结构、put和get方法

    • 1、ConcurrentHashMap类中包含两个静态的内部类HashEntry
      和Segment.HashEntry用来封装映射表的键值对;Segment 用来充当锁的角色,每个 Segment
      对象守护整个散列映射表的若干个桶。每个桶由若干个HashEntry 对象链接起来的链表。一个ConcurrentHashMap
      实例中包含由若干个 Segment 对象组成的数组。
    • 2、用分离锁实现多个线程间的并发写操作(put方法实现的过程)

    (1)key通过hash函数得到散列码
    (2)散列码通过segmentFor找到对应的Segment(不允许value为空)
    1)将散列值右移segmentShift 个位,并在高位填充 0
    2) 然后把得到的值与 segmentMask 相“与”
    3)从而得到 hash 值对应的 segments 数组的下标值
    4)根据下标值返回Segment对象
    (3)在Segment中执行具体的put操作
    1)加锁(锁定某个segment,而非整个ConcurrentHashMap)
    2)判断HashEntry 是否超过阀值(负载因子*数组长度),若超过要进行散列
    3)没超过,判断键值对是否存在,采用头插法加入链表中‘;
    4)然后解锁
    2.5 HashMap与ConcurrentHashMap的关联与区别?
    2.6 HashTable的实现原理?与ConcurrentHashMap的区别
    2.7 concurrent包的并发容器有哪些?
    3、java多线程问题
    3.1 java实现多线程的方式及三种方式的区别
    ####实现多线程的方式

    • (1)继承Thread类,重写run函数
    • (2)实现Runnable接口(最常用)
    • (3)实现Callable接口

      三种方式的区别

    • (1)实现Runnable接口可以避免java单继承特性带来的局限,增强程序健壮性,代码能够被多个线程共享。

    • (2)Thread和Runnable启动线程都是使用start方法,然后JVM将此线程放到就绪队列中,如果有处理机可用,则执行run方法。

    • (3)实现Callable接口要实现call方法,并且线程执行完毕后会有返回值,其他的两种方法都是重写run方法,没有返回值。Callable接口提供了一个call()方法可以作为线程执行体,但call()方法比run()方法功能更为强大:call()方法可以有返回值;call()方法可以声明抛出异常。

    ###3.2 线程安全
    定义:
    (1)某个类的行为与其规范一致;
    (2)不管多个线程是怎样的执行顺序和优先级,或是wait,sleep,join等控制方式。 如果一个类在多个线程访问下运转一切正常,并且访问类不需要进行额外的同步处理或协调,那么我们认为它是线程安全的。
    如何保证线程安全?
    (1)对变量使用volatile
    (2)对程序段进行加锁(synchronized,lock)
    注意:
    非线程安全的集合在多线程环境下可以使用,但并不能作为多个线程共享的属性,可以作为某个线程独立的属性。例如:Vector是线程安全的,ArrayList不是线程安全的,如果每一个线程new一个ArrayList,而这个ArrayList在这个线程中使用肯定没有问题。(若再new一个ArrayList,就会线程不安全)
    ###3.3 多线程如何进行信息交互
    1、采用object方法:wait(), notify(), notifyAll();
    2、在java 1.5中采用condition,比传统的wait,notify()更加安全高效
    3、采用生产者----消费者模式(采用队列的形式)
    生产者发布消息在队列中,消费者从队列中取任务去执行。
    ###3.4多线程共用一个数据变量需要注意什么?
    出现的问题:当我们在线程对象(Runnable)中定义了全局变量,run方法修改该变量时,如果有多个线程同时使用该线程对象,那么就会造成全局变量的值被同时修改,造成错误。
    解决方法1:ThreadLocal是JDK引入的一种机制,它用于解决线程共享变量,使用ThreadLocal声明的变量,会在每个线程内产生一个变量副本,从而解决了线程不安全。
    解决方法2:volatile变量每次被线程访问时,都强迫线程从主内存中重新取该变量的最新值到工作内存中,而当该变量发生修改变化时,也会强迫线程将最新的值刷新会到主内存中。这样一来,不同的线程都能及时的看到该变量的最新值。
    ###3.5什么是线程池?如果让你设计一个动态大小的线程池,如何设计,应该有哪些方法?
    什么是线程池?
    线程池顾名思义就是事先创建若干个可执行的线程放入一个池(容器)中,需要的时候从池中获取线程不用自行创建(类似于工厂设计模式),使用完毕不需要销毁线程而是返回池中,从而减少创建和销毁线程对象的开销。
    设计一个动态大小的线程池,如何设计,应该有哪些方法??
    一个线程池包括以下四个基本单位:
    (1)线程管理器(ThreadPool):用于创建并管理线程池,包括创建线程,销毁线程池,添加新任务;
    (2)工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;
    (3)任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定任务的入口,任务执行完成后的收尾工作,任务的执行状态等。
    (4)任务队列(TaskQueue):用于存放没有处理的任务,提供一种缓冲机制。
    所包含的方法:
    (1)private ThreadPool()创建线程池;
    (2)Public static ThreadPool getThreadPool()获得一个默认线程个数的线程池;
    (3)Public void execute(Runnable task)执行任务,其实只是把任务加入任务队列,什么时候执行由线程池管理器确定;
    (4)Public void execute(Runnable[] task) 批量执行任务,其实只是把任务加入任务队列,什么时候执行由线程管理器确定。
    (5)Public void destroy() 销毁线程池,该方法保证所有任务都完成的情况下才销毁所有线程,否则等待任务完成销毁。
    (6)Public int getWorkThreadNumber() 返回工作线程的个数。
    (7)Public int getFinishedTasknumber()返回已完成任务的个数,这里的已完成是指出了任务队列的任务个数,可能该任务并没有实际执行完成。
    (8)Public void addTread() 在保证线程池中所有线程正在执行,并且要执行线程的个数大于某一值时(就是核心池大小),增加线程池中线程的个数(最大是线程池大小)。
    (9)Public void reduceThread() 在保证线程池中有很大一部分线程处于空闲状态,并且空闲状态的线程在小于某一值时(就是核心池大小),减少线程池中线程的个数。
    ###3.6 volatile与synchronized区别
    Volatile 关键字的作用
    (1)保证内存可见性
    (2)防止指令重排序
    注意:volatile并不保证原子性。
    内存的可见性:
    Volatile保证可见性的原理是在每次访问变量时都会进行刷新,因此每次访问都是在主内存中得到最新的版本。所以volatile关键字的作用之一就是保证变量修改的实时可见性。
    当且仅当满足以下所有条件时,才应该使用volatile变量
    (1)在两个或更多的线程需要访问的成员变量上使用volatile.当需要访问的变量已在synchronized代码块中,或者为常量,没必要使用volatitle;
    (2)由于使用volatile屏蔽了JVM中必要的代码优化,所以在效率上比较低,因此一定在必要时才使用此关键字。
    Volatile和synchronized区别
    (1)volatile不会进行加锁操作
    Volatile变量是一种稍弱的同步机制,在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比synchronized关键字更轻量级的同步机制。(其实volatitle在读写时,就相当于加锁)
    (2)Volatile变量作用类似于同步变量读写操作
    从内存可见性的角度看,写入volatile变量相当于退出同步代码块,而读取volatile变量相当于进入同步代码块。
    (3)volatile不如synchronized安全
    在代码中如果过度依赖volatile变量来控制状态的可见性,通常会比加锁的代码更加脆弱,也更加难以理解。仅当volatile变量能简化代码的实现以及对同步策略的验证时,才应该使用它。一般来说,用同步机制更安全些。
    (4)volatile无法同时保证内存可见性和原子性
    加锁机制(即同步机制)既可以确保可见性又可以确保原子性,而volatile变量只能确保可见性,原因是声明为volatile的简单变量如果当前值与该变量以前的值相关,那么volatile关键字不起作用,也就是说以下的表达式都不是原子操作:“count++”“count=count+1”。
    ###3.7 sleep和wait分别是哪个类的方法,有什么区别?
    Sleep和wait
    Sleep是Thread类的方法。Wait是object类方法(与notify(), notifyAll();连用,即等待唤醒)
    两者有什么区别
    Sleep()方法(休眠)是线程类(Thread)的静态方法,调用此方法会让当前线程暂停执行指定的时间,将执行机会(cpu)让给其他线程,但是对象的锁依然保持,因此休眠时间结束后会自动回复(线程回到就绪状态)。
    Wait()是Object类的方法,调用对象的wait方法导致当前线程放弃对象的锁(线程暂停执行),进入对象的等待池(wait poo),只有调用对象的notify方法或notifyAll时才能唤醒等待池中的线程进入等锁池(lock pool),如果线程重新获得对象的锁就可以进入就绪状态。
    ###3.8 synchronized与lock的区别,使用场景,看过synchronized的源码没?
    Synchronized与lock的区别
    (1)(用法)synchronized(隐式锁):在需要同步的对象中加入此控制,synchronized可以加在方法上,也可以加在特定代码块中。
    (2)(用法)Lock(显示锁):需要显示指定起始位置和终止位置,一般使用ReentrantLock类做为锁,多个线程中必须要使用一个 ReentrantLock类做为对象才能保证锁的生效。且在加锁和解锁处需要通过lock()和unlock()显示指出。所以一般在finally块中写unlock以防死锁。
    (3)(性能)synchronized是托管给JVM执行的,而lock是java写的控制锁代码。在java1.5中,synchronized是性能低效的。因为这是一个重量级操作,需要调用操作接口,导致有可能加锁消耗的系统时间比加锁以外的操作还多。相比之下使用java提供的lock对象,性能更高一些。但是到了java1.6,发生了变化。Synchronized在语义上很清晰,可以进行很多优化,有适应自旋,锁消除,锁粗化,轻量级锁,偏向锁等等。导致在java1.8上synchronized的性能并不比lock差。
    (4)(机制)synchronized原始采用的是cpu悲观锁机制,即线程获得是独占锁。独占锁意味着其他线程只能依靠阻塞来等待线程释放锁。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止,乐观锁实现的机制就是CAS操作(Compare and Swap)。
    ###3.9 synchronized底层如何实现的?用在代码块和方法上有什么区别?
    Synchronized底层如何实现的?(看源码)用在代码块和方法上有什么区别?
    (1)synchronized用在代码块锁的是调用该方法的对象(this),也可以选择锁住任何一个对象。
    (2)Synchronized用在方法上锁的是调用该方法的对象。
    (3)Synchronized用在代码块可以减少锁的粒度,从而提高并发性能。
    (4)无论用在代码块上还是用在方法上,都是获取对象的锁。每一个对象只有一个锁与之相关联;实现同步需要很大的系统开销为代价,甚至可能造成死锁,所以尽量避免无谓的同步控制。
    Synchronized与static Synchronized的区别
    (1)synchronized是对类的当前实例进行加锁,防止其他线程同时访问该类的该实例的所有synchronized块,同一个类的两个不同实例就没有这种约束(这个对象压根就是两个不相关的东西)。
    (2)那么static synchronized恰好就是要控制类的所有实例的访问,static synchronized是限制线程同时访问jvm中该类的所有实例同时访问对应的代码块。
    ###3.10 java中的NIO、BIO、AIO分别是什么
    BIO:
    (1)同步并阻塞(互斥同步),服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个链接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善;
    (2)BIO方式适用于链接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,jdk1.4以前的唯一选择,但程序直观简单易理解。
    NIO:
    (1)同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的链接请求都会注册到多路复用器上,多路复用器轮询到链接有I/O请求时才启动一个线程进行处理。
    (2)NIO方式适用于链接数目多且链接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,jdk1.4开始支持。
    AIO:
    (1)异步非阻塞,服务器实现模式为一个有效一个线程,客户端的I/O请求都是由OS(操作系统)先完成了在通知服务器应用去启动线程进行处理;
    (2)AIO方式使用于链接数目多且链接比较长(重操作)的架构,比如相册服务器,充分调用os参与并发操作,编程比较复杂,jdk7开始支持。
    ###3.11什么是java内存模型(java memory model jmm)
    描述了java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取出变量这样的低层细节。
    (1)所有的变量都存储在主内存中。
    (2)每个线程都有自己独立的工作内存,里面保存该线程使用到的变量的副本(主内存中该变量的一份拷贝)。
    这里写图片描述
    Java内存模型的两条规定
    (1)线程对共享变量的所有操作都必须在自己的工作内存中进行,不能直接从主内存中读写;
    (2)不同线程无法直接访问其他线程工作内存中的变量,线程间变量值的访问都需要通过主内存来完成。
    ###3.12 JVM线程死锁,你该如何判断是什么原因?如果用VisualVM,dump打印线程信息出来,会有哪些信息?
    常常需要在隔两分钟后再收集一次thread dump,如果得到的输出相同,仍然是大量thread都在等待给同一个地址上的锁,那么肯定是死锁了。
    3.1.2 各种锁
    1、悲观锁
    无论共享数据是否产生争用、是否由于争用产生冲突,都会加锁。
    2、乐观锁
    假设没有共享数据争用,就执行成功。若监测出有共享数据争用产生冲突,就进行补救措施(如:重试)。
    3、可重入锁
    一个线程加锁了,该线程中调用其他的方法,也同样加锁了,如递归;
    4、读写锁
    对一个资源的访问,可以分成读锁和写锁;
    5、可中断锁
    一个线程等待的时间太长,可以中断等待的过程,去执行其他的事情;
    6、公平锁
    尽量以锁的顺序来获取锁;
    7、优化方面的锁
    1)自旋锁(自适应锁)
    共享数据锁定状态比较短,对于阻塞的线程不要立马挂起,而是自旋一下就可得到,避免线程切换的开销。
    2)锁消除
    有些数据是不会被其他线程访问到的,这时候就不需要加同步措施,进行锁消除。
    3)锁粗化
    同步块的作用域一般越小越好,但是对一个对象的连续操作,不停的加锁解锁,这样会出现很大的性能问题。
    4)轻量级锁
    为了减少获得锁和释放锁所带来的性能消耗。Java 1.6有无锁状态,偏向锁状态,轻量级锁状态和重量级锁状态四个状态。随着竞争,不断升级,不能降级。
    5)偏向锁
    目的是消除数据在无竞争情况下的同步原语。进一步提升程序的运行性能。 偏向锁就是偏心的锁,意思是这个锁会偏向第一个获得他的线程。
    ###4、异常问题
    ###4.1 常见异常分为两种(Exception,Error)
    Throwable是java语言中所有错误和异常的基类,它由两个子类:Error, Exception.
    异常种类
    Error: Error为错误,是程序无法处理的,如OutOfMemoryError,ThreadDeath等,出现这种情况你唯一能做的就是听之任之,交由JVM来处理,不过大多数情况下会选择终止线程。
    Exception: Exception是程序可以处理的异常,它又分为两种CheckedException(受检异常),一种是unCheckedException(不受检异常)
    CheckedException发生在编译阶段,必须要使用try……catch(或则throws)否则编译不通过
    unCheckedException发生在运行期,具有不确定性,主要是由于程序的逻辑问题所引起的,难以排查,我们一般都需要纵观全局才能够发现这类的异常错误。
    常见异常的基类:
    IOException
    RuntimeException
    这里写图片描述
    5、 加载问题
    ##6、 关键字的比较
    ###6.1 static和final的区别和用途
    Static:
    1)修饰变量:静态变量随着类加载时被完全初始化,内存中只有一个,且JVM也只会为它分配一次内存,所有类共享静态变量。
    2)修饰方法:在类加载的时候就在,不依赖任何实例;static方法必须实现,不能用abstract修饰。
    3)修饰代码块:在类加载完成后就会执行代码块中的内容
    4)执行的顺序:父类静态代码块—>子类静态代码块---->父类非静态代码块---->父类构造方法----->子类非静态代码块----->子类构造方法
    Final:
    1)修饰变量:编译期常量(类加载的过程完成初始化,编译后带入到任何计算式中,只能是基本类型) 运行时常量(基本数据类型或引用数据类型,引用不可变,但引用的对象内容可变)
    2)修饰方法:不能被继承,不能被子类修改
    3)修饰类:不能被继承
    修饰形参:final形参不可变
    ##7、字符串
    ###7.1 String、StringBuffer、StringBuilder以及对String不变性的理解
    1)都是final类,都不允许被继承;
    2)String长度是不可变动的,StringBuffer、StringBuilder长度是可变的;
    3)StringBuffer是线程安全的(在StringBuilder方法上添加关键字synchronized),StringBuilder线程不安全
    4)StringBuilder比StringBuffer拥有更好的性能;
    5)如果一个String类型的字符串,在编译时就可以确定是一个字符串常量,则编译完成之后,字符串会自动拼接成一个常量,此时String的速度比StringBuilder和StringBuffer的性能好的多。
    String不变性的理解
    1)String类是被final进行修饰的,不能被继承;
    2)在用+号连接字符串的时候会创建新的字符串;
    3)String s=new String(“helle world”);可能创建两个对象也可能创建一个对象。如果静态区中有“hello world”字符串常量对象的话,则仅仅在堆中创建一个对象。如果静态区没有“hello world”对象,则在堆和静态区中都创建对象。
    4)在java中,通过使用“+”符合来链接字符串的时候,实际底层会转成通过StringBuilder实例的append()方法实现。
    ###7.2 String有重写Object的hashcode和toString吗?如果重写equals不重写hashcode会出现什么问题?
    有重写这些方法。当equals方法被重写,通常有必要重写hashCode方法,才能保证相等。
    如:object1.equal(object2)为true, object1.hashCodeobject2.hashCode()为true
    两个对象内容相等,那么hashCode指向的是同一个内容,返回的哈希值是相同的;
    object1.hashCodeobject2.hashCode()为false时,object1.equal(object2)为false
    两个hashCode不等,那么两个对象的内容必然不同(每个对象的哈希值是唯一的);
    object1.hashCode==object2.hashCode()为true时,object1.equal(object2)不一定为true;
    比如hashmap,hashCode是数组的下标,但是会产生hash冲突,比如一个数组下标后连接一个链表;
    重写equals不重写hashcode会出现什么问题??
    在存储散列集合时(如set类),如果原对象.equals(新对象),但没有对hashcode重写,即两个对象拥有不同的hashcode,则在集合中会存储两个值相同的对象,从而导致混淆,因此在重写equals方法时,必须重写hashcode方法。
    ###7.3 如果你定义一个类,包括学号,姓名,分数,如何把这个对象作为key?要重写equals和hashcode吗?
    需要重写equals方法和hashcode,必须保证对象的属性改变时,其hashcode不能改变。

    ##8、java协议
    ###8.1 java序列化、如何实现序列化和反序列化,常见的序列化协议有哪些
    Java序列化定义
    将哪些实现Serializable接口的对象转换成一个字节序列,并能够在以后将这个字节序列完全恢复为原来的对象,序列化可以弥补不同操作系统之间的差异。
    Java序列化的作用
    (1)Java远程方法调用(RMI)
    (2)对javaBeans进行序列化
    如何实现系列化和反序列化
    实现序列化
    (1)实现Serializable接口
    1)该接口只是一个可序列化的标志,并没有包含实际的属性和方法;
    2)如果不在该方法中添加readObject()和writeObject()方法,则采取默认的序列化机制,如果添加了这两个方法之后还想利用java默认的序列化机制,则在这两个方法中分别调用defaultReadObject()和defaultWriteObject()两个方法;
    3)为了保证安全性,可以使用transient关键字进行修饰不必序列化的属性。因为在反序列化时,private修饰的属性也能查看到。
    (2)实现ExternalSerializable方法
    自己对要序列化的内容进行控制,控制哪些属性被序列化,哪些不能被序列化;
    实现反序列化
    (1)实现Serializable接口的对象在反序列化时不需要调用对象所在类的构造方法,完全基于字节;
    (2)实现ExternalSerializable接口的方法在反序列化时会调用构造方法;
    注意事项
    (1)被static修饰的属性不能被序列化;
    (2)对象的类名、属性都会被序列化,方法不会被序列化;
    (3)要保证序列化对象所在类的属性也是可以序列化的;
    (4)当通过网络、文件进行序列化时,必须按照写入的顺序读取对象;
    (5)反序列化时有序列化对象的class文件;
    (6)最好显式的声明serializableID,因为在不同的JVM之间,默认生成serializableID;可能不同,会造成反序列化失败。
    常见的序列化协议有哪些?
    (1)COM
    (2)CORBA
    (3)XML&SOAP
    (4)JSON
    (5)Thrift
    ……
    9.其他
    ##9.1 java的四个基础特性(抽象、封装、继承、多态),对多态的理解(多态的实现方式,以及在项目中用到的多态)
    1)java的四个基本特性
    抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面,抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。
    继承:继承是从已有类得到继承信息创建新类的过程,提供继承信息的类称为父类(超类、基类);得到继承信息的类被称为子类(派生类),继承让变化中的软件系统有了一定的延续性,同时继承也是封装程序中可变因素的重要手段。
    封装:通常认为封装是把数据和操作方法绑定起来,对数据的访问只能通过已定义的接口,面向对象的本质就是将现实世界描绘成一系列完全自治、封闭的对象,我们在类中编写的方法就是对实现细节的一种封装;我们编写一个类就是对数据和数据操作的封装。可以说,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口。
    多态:是指允许不同子类型的对象对同一消息做出不同的响应。
    多态的理解(多态的实现方式)
    1)方法重载(overload)实现编译时的多态性(也称为前绑定)(1、类型不同 2、参数个数不同 3、与返回值无关)。
    2)方法重写(override)实现运行时的多态(也称为后绑定)。(核心精髓)
    3)实现多态的两件事:
    a:方法重写(子类继承父类并重写父类中已有的或抽象的方法)
    B:对象造型(用父类型引用子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为)
    项目中多态的应用:
    1)单继承
    2)接口实现
    例如:在接口中写一个爬虫的方法
    在不同的网站,爬虫实现的方法都不同,如网易采用jsoup进行解析、百度采用RSS,今日头条采用ajax异步传输,实现就要采用获取json进行解析。

    ![这里写图片描述](https://img-blog.csdnimg.cn/img_convert/e415b30dbbbc5ab4a2c1175b0e899fa7.png) ![这里写图片描述](https://img-blog.csdnimg.cn/img_convert/5ea7f92a4b50d8465587c45e4b34108a.png) ![这里写图片描述](https://img-blog.csdnimg.cn/img_convert/f26b6c802951d54cd92c22204011ed16.png)
    展开全文
  • 如果连成五子就赢了,如果没有就换对手下棋,每一步过程组合在一起就组成了棋盘,电脑有超强计算和存储能力,人脑也有,但电脑没有人脑灵活,人工智能尚在起步,未来可; 下完棋后想看下过程于是就有了复盘会看...

    目录

     

    前言

    体验效果

    关键技术简介

    从无到有思路

    代码实现过程

    1.棋盘实现

    2.计算棋子是否获胜

    3.网络聊天室实现

    4.查看复盘实现

    遇到问题

    源码下载地址及注意事项

    总结


    前言


    小时候很喜欢玩电视上自带的积木游戏,那时候电子产品也不少,小学就认识了low和high两个单词,因此攒了零花钱搞到了高级版的游戏--小霸王学习机,说错了,是“游戏机”,特别是一放暑假,插个小霸王游戏机卡就能开始撸几把魂斗罗,坦克大战,比夏天吃根冰棍可香多了,那时候没有空调,不惧严寒酷暑的我们在这些小游戏的陪伴下玩的不亦乐乎,陪我们度过一个又一个快乐冬夏,下图为证,有没有很熟悉的赶脚!

    image.png

    看着这些画面,带着学习的心情的出发了,开始我们的五指棋大战;砍柴磨刀互相不耽误,先看看实现的网络版本效果吧,当然为了你看着舒服,体验的开心,博主花了些功夫大致在客户端(手机)做了适配,不至于你屏幕溢出,你爽我爽大家爽;简单说明下本人主攻后端Java,前端不深入,页面效果将就看看哈!

    体验效果


    看看你想下盘棋,你需要做什么,首先你需要抢个位子啊【我媳妇说这个举手的小人太丑了,能换个动漫吗,我说我对漫画没啥审美,但是对另一半要求很高,所以我们在一起了,嘻,这个求生欲也特强了吧!】,然后你就到棋盘页面了,点击举手坐下,你就开始等,有人来了也举手了你们就可以开始了,就像你去棋牌室打牌一模一样,如果硬说有啥区别,咱们这是学习,他们那是赌博,好了,好了,扯远了,简单介绍下,五指棋的规则大家都懂,你五个棋子连成一条线就胜利了,一攻一防,胜率就看你的手段了,不知道你有没有发现,博主为了不让你有更多的束缚,连账户密码都默认为你输出了,同时为了让程序不是那么单调,下棋还能发声,还实现了查看复盘,和在线聊天,聊天IP内容已经存档,自动过滤色情信息;博主为你们担心的事情都精心考虑设计实现,是不是暖男你们说了算!下面两个动画分别是“准备过程”和“下棋过程”;

    体验地址:五指棋体验地址,有些功能可能有不完善的地方,功能也不特别太全,但基本功能应该是满足了,如果没有人陪你玩,你可以开两个客户端体验或call me,一毛钱一把,欢迎来战如果有强迫症的或是有good idea,想要加入我们的修改的童鞋,欢迎留言加入我们,让我们共同成长。

    准备过程.gif

    游戏准备过程                                                               

    下棋过程.gif

    下棋过程

     

    关键技术简介


    其实在开发完这个小游戏后,回顾总了下,没有啥高精尖的技术,前端页面完全是html,css(样式),js+jquery(脚本语言),像“举手准备”,“画棋盘”等都是用到的这些前端技术,大家看到的淘宝页面基本都是基于这些技术来完成的;后端用到的技术是websocket,很多面试官会问到websocket和http的区别,博主简单介绍下,它是一个双向通信的协议,这个不难理解,如果你打我了,我也可以还手,不像http,它是单向的,比如你打我了,我就只能默默忍着,顶多就能回骂一句,绝不能还手,相信在这个人人平等的社会,你肯定喜欢websocket,他帮助我们实现了在线聊天,咿!我怎么突然想到了QQ,感觉我像腾讯的高级攻城狮一样牛掰;当然了,作为一个Java攻城狮,必须要理解的就是线程啦,我们里面的“查看复盘”的功能就用到了Java读写文件和多线程技术;是不是小小的程序还有大大的奥妙;好啦,就到这里吧,明不明白都不要紧,因为我们后面详细一点你就能明白了!

    总结下:前端css+js实现画页面和交互,后台Java+websocket实现存储和通信相信有一点基础的你,肯定也能实现这个程序;在实际开发中,前后端是两个不同的岗位,前后端各司其职,当然有些小公司要求程序员都要会,所以有些时候在挑选公司时擦眼睛不是眼里进了沙子,是为了看的更清楚!

     

    从无到有思路


    这个程序博主一个人断断续续差不多花了两个礼拜完成的,当然你肯定怀疑我是否有一气呵成的能力,可以肯定的回答你,你的怀疑是对的,因为我也是人,我不能忘了吃饭和睡觉;最原始最原始的要追忆到两周前,下图是最初的版本,这是在idea控制台输出的,是的,你没有看错,五指棋程序实现了,“五指棋连线成功,恭喜你赢了”,说实话,打出这句话,连我这么没有追求的人都觉得他太枯燥了,生活它需要仪式感,你上面看到的页面都是我在仪式感基础上添加的;新事物,全新的页面,至少我感受到了程序至少还有那么一丝丝的美,是的,至少此刻我沦陷了,学程序我们要有意识的去追求一下她的美,不然他会教你从入门到放弃;

    image.png

    慢慢的,“仪式感”让我走向一条不归路,首先需要解决的问题,就是棋盘的问题,现实中有木头刻制的棋盘,网络上可以用代码生成的棋盘,现实的中棋盘有她独特的美感,网络中这些棋盘他就更灵活了,每个方格,棋盘大小都是可调整的,成本微乎其微,有了棋盘那就需要把棋子落在棋盘上,这些可以用css+js画出来的,还不用浪费木材,落子了每次都需要判断这次走的这步棋是否连成了一个五子,如果连成五子就赢了,如果没有就换对手下棋,每一步过程组合在一起就组成了棋盘,电脑有超强计算和存储能力,人脑也有,但电脑没有人脑灵活,人工智能尚在起步,未来可期;

    下完棋后想看下过程于是就有了复盘会看功能;下棋要沟通就有了网络聊天室;棋牌室永远不止一个座位,于是就需要支持多个座位,多人同时娱乐;当然你也可以说自己水平不够,需要人机对战,提升经验,或者有人说游戏赢了或输了要有对应的奖惩机制,没错,你能想到的,有兴趣的事情你都可以动手实现,兴趣或者学习目的都好,总比无聊消磨时光来的更有意义;这就是我从无到有一步步构建出来的程序。欢迎和我一起讨论学习!【公众号:叫练

    代码实现过程


    1.棋盘实现

    棋盘是用table表格画的,像你看到博主实现的这个表格,横轴是26,纵轴是23,每个格子的大小都是40像素,不信你话可以量量哈,前端循环放置在div中就可以了,格子像素是固定的,因为我们需要通过像素计算最终得到坐标,下棋过程就是通过点击格子,把白或黑的棋子(图片)放置上去,那怎么计算坐标呢?你需要计算相对于棋盘点击的坐标,也就是下图我们的红圈,我们知道棋盘表格(table)是整个页面(body)中的一部分,js中click事件可以获取到全局事件event变量,所以能够获取到相对于body的event.left,event.top,再减去table的top,left就可以得到表格具体的像素,table的top left可以通过getBoundingClientRect方法获取;table的像素再除40px就是具体坐标了;大家在实现的时候还需要注意一个有效点击,只有在这个范围内才算有效范围,像我们这个程序,是以坐标点15px都算有效点击;那么无效点击范围在16px-25px,也就是点击格子中间是无效的。举个例子吧,比如我们现在算出表格具体坐标是top:130px,left:70px,先计算这个坐标是否有效,130%40=10, 70%40=30,都不在无效范围内,说明是有效坐标,我们这里横纵坐标分别记为xy,那么具体坐标就y=130/40 =3,x=70/40=1,此时x坐标需要加上1,因为x坐标大于25,靠右,需要再除的基础上加上1,所以最终横纵坐标为(2,3),这个过程涉及到一点小算法,你对着下图再思考下这个计算过程看看是否有不明白的,万事开头难!下面的图方便大家理解!

    image.png

    2.计算棋子是否获胜

    在写这个之前先给大家爆料下,没思考听到这个问题前呼吸都是混乱的,说实话真的好难啊,什么?还要实现,我TM不想学习了!嘿,兄嘚,别怕,有我在呢!怕解决不了问题,我牵着你,我们在一起吧!

    image.png

    兵马未动,图表先行,给大家先上个图吧,其实就四条线;一个"米"子,会写米字,问题就解决了,也就是说每次落子后需要逐一判断这4条线上是否有五个相同颜色的棋子,其实对应Java程序程序来说,他是用一个二维数组形式来存储的棋盘数据的,我们以红色为中心点,其实这个过程就是通过方式1(纵向):先向上找,找红色的棋子,找到一个计数器就+1,不是红色的棋子直接break退出,同理向下找,找红色的棋子,找到一个计数器就+1,不是红色的棋子直接break退出,最终如果满足5个相同颜色的棋就算成功,也不用再通过方式2,3,4找了,如果方式1不满足条件,方式1就结束了,接下来就按方式2(左斜)查找,也就是同样的套路,一直到方式4为止,哈哈,是不是有点感觉了!先以方式1查找举例子吧,对二维数组来说,以先上找后下找,上找:横坐标不变,纵坐标递减;下找:横坐标不变,纵坐标递增。再按方式三举例,先右上后左下查找,右上:横坐标+1,纵坐标-1,左下:横坐标-1,纵坐标+1;你看看原理还是很简单吧!isSuccess方法是按方式1查找的。

    1599037247(1).jpg

    public boolean isSuccess(int x, int y,int color,int[][] oriData)  {
            boolean flag = false;
            int count = 0;
            //(2)上-下,左-右,左上-右下,右上-左下; 4种方式
            // 方式1 :上-下 x相同,y不同
            //上 纵坐标递减;
            for (int i = y-1; i>-1; i--) {
                //判断同一颜色的子;
                if (oriData[x][i] != color) break;
                count++;
            }
            //下 纵坐标递增;
            for (int i = y+1; i<GameManager.Y; i++) {
                //判断同一颜色的子;
                if (oriData[x][i] != color) break;
                count++;
            }
            //重置
            if(count >=4) return true;
            else count = 0;
            // 方式2
            // 方式3
            // 方式4
            return falg
        }

    3.网络聊天室实现

    不知道你有没有发现,你有时候浏览网页会弹出一些广告页,上面时长会自动弹出人工客服窗口,不需要登陆什么的,你们能在线对话,其实这个功能可能就是websocket实现的,我们说过,websocket是双向的,在实现上和http一样,都是基于TCP实现的应用层协议,除了http是单向,ws是双向的。在用法上http是以http://头请求的短连接,是一次请求,ws是以ws://请求头的长连接,我们在“五指棋游戏大厅”点击位置进入棋盘页面其实就是一个连接过程,前端通过ws://ip:port/xx形式连接后台暴露的地址,会触发后台onOpen函数,每个连接在后台都是以不同Session对象存在,通过Session可以获取每个每个网页的session的id,就可以调用sendText方法,通知对应的浏览器,这样一个过程就实现了浏览器和后台交互;比如我们的对话,每个桌子是保存两个用户(Session),当其中某一个用户发送了某段话,会通知对应的浏览器,另一个用户也会把这句话发给对应的浏览器,所以你看到的两个页面都出现了一样的文字,其实原理还是比较简单的。

    我们画个图来理解下这个过程。

    image.png

    websocket通信过程

     

    下面这两个段代码是websocket客户端和服务器交互的代码,只贴了部分源码,限于篇幅有限,如果需要完整代码,下方【环境部署部分】给了源码下载压缩包

    webSocket = new WebSocket("ws://"+serverIp+"/game/game/"+table+"/" + nickname);
    /**
     * websocket服务连接入口
     * update by jiaolian 2020 8 12
     * 公众号:叫练
     */
    @ServerEndpoint("/game/{table}/{nickname}")
    public class WebserviceSession {
        @OnOpen
        public void onOpen(@PathParam("table") String tableName,@PathParam("nickname") String nickname, Session session) throws IOException {}
        @OnMessage
        public void onMessage(@PathParam("table") String tableName,String message, @PathParam("nickname") String nickname) {}
        @OnClose
        public void onClose(Session session,@PathParam("nickname") String nickname,@PathParam("table") String roomName) {}
    }

    4.查看复盘实现

    这个功能是线程+文件实现的,既然需要查看复盘,那说明我们需要先写复盘,复盘其实就是写一个文件,查看复盘就是读文件,在一局棋中,每次落子就会写一行数据,每行数据会记录棋子的x,y坐标和棋子的颜色,按空格隔开,我们代码中Table【棋盘】对象保存color约定1为黑子,2为白子,当一局游戏结束后,查看复盘会生成对应一个线程,以每秒一行的速度读取保存在服务器的文件,文件名以“桌号+时间戳.wzq”命名,如:1_2020_09_01_15_45_15.wzq,每行数据读取出来后,会单独发给我们的User【Session】,浏览器收到通知后渲染页面就完事了;贴一段简单的代码;其中Table类是用来保存棋盘的;User类是用来保存用户信息的;finishReplayer()方法是读取文件,sendTextSingle()方法负责给浏览器发送信息;可以参考上面websocket通信过程;

    /**
     * @author :jiaolian
     * @date :Created in 2020-08-14 13:30
     * @description:桌号
     * @modified By:
     * 公众号:叫练
     */
    public class Table {
    
        //保存用户信息
        @JsonIgnore
        private List<User> userList = new CopyOnWriteArrayList<>();
        //桌号名
        private String name;
        //默认棋盘
        private int[][] oriData = new int[GameManager.X][GameManager.Y];
        //约定:1 黑子 2 白子  -1 无子(页面选棋子) 此时应该走动棋子颜色;
        //棋子颜色<默认黑子先走>
        private int color = CONST.CHEER_COLOR.BLACK;
        //对应的文件名<复盘> 最近一次
        private String fileName;
        //桌子状态; 已开始/未开始
        private int tableStatus;
        private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss");
        private List<User> tempList;
    }
    /**
     * @author :jiaolian
     * @date :Created in 2020-08-14 13:56
     * @description:在线用户
     * @modified By:
     * 公众号:叫练
     */
    public class User {
    
        //websocket sessoin;
        private Session session;
        private String nickname;
        //棋手是否已准备;
        private boolean isPrepared;
    
        //用户的复盘线程;key规则:桌号_用户名,为什么这么设计? 因为一个用户可能在多个桌子上同时游戏;
        //val值设计成Boolean,主要是查询对应桌数用户线程是否执行完毕; 如果有疑问,请咨询叫练;
        //设计这个目的主要实现:不能同时多次点击复盘;
        private ConcurrentHashMap<String,Boolean> reviewThreadMap = new ConcurrentHashMap();
    }

     

    public void finishReplayer(Table table,User user) {
            inputStream = new FileInputStream(table.getFileName());
            inputStreamReader = new InputStreamReader(inputStream);
            bufferedReader = new BufferedReader(inputStreamReader);
            while ((res = bufferedReader.readLine()) != null) {
                System.out.println(res);
                //谁点谁看
                GameManager.sendTextSingle(table,res,user);
                try {
                    //休眠一秒
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    
        }

    遇到问题


    • springMVC jackson返回实体对象通过不能修改后不能正常转化为json传递到前端?

    在实现上述功能过程中,说没遇到问题是不可能的,很多都是业务的bug,就不详细介绍了 ,该五指棋程序没有写数据库,都是在内存处理的,其中渲染首页桌子页面的数据都是从后台一个大的List<User>获取的,如果List数据在修改后,再刷新首页,会出现栈溢出,不能写json的情况,目前处理的方式是用了@JsonIgnore忽略对象返回到前端,把这个List的User对象重新复制了一份到一个新的List<User>,查看了源码,暂时还没找到原因!找到原因的童鞋欢迎下方留言,一起探讨下!感谢了

    Could not write JSON: Infinite recursion (StackOverflowError); nested exception is com.fasterxml.jackson.databind

    源码下载地址及注意事项

    • JDK1.8及以上;有lamda表达式需要支持
    • maven;需要下载websocket包
    • 源码📎fivegame.rar

    总结


    整个程序实现下来还是有种莫名的成就感,博主希望把这个过程分享给你,如果你能有所收获,对我来说就是最大的宽慰,写作的目的也在于此,文章创作过程中涉及的内容和知识点可能不太全,后续如果有需要会适当补充和删改,这篇文章是博主的技术处女作,如果有疑问请下方留言哦!我们可以一起讨论交流学习心得;我是【公众号:叫练,边叫边练;喜欢的可以加关注哦!再次感谢大家的观看支持。

    以上的文字,厚起脸皮问你要一颗小红心,你会给吗?体验地址:五指棋体验,再发一遍。

    微信公众号.jpg

     

    展开全文
  • 生存,是程序中的简单变量和类的对象,它们同自然界中的万物一样。也有自己的产生和消亡的生存,变量和对象的生存包含静态生存和动态生存。具有静态生存的变量和对象,它们的生存和程序运行相同;...

    作用域是表示某个标识符在什么范围内有效;
    可见性是指标识符是否可见,可引用;
    生存期,是指程序中的简单变量和类的对象,它们同自然界中的万物一样。也有自己的产生和消亡的生存期,变量和对象的生存期包含静态生存期和动态生存期。具有静态生存期的变量和对象,它们的生存期和程序运行相同;除了具有静态生存期的变量和对象,其余的变量和对象则具有动态生存期。具有动态生存期的变量和对象,它们处于被声明处产生,于自身的作用域结束处释放。
    全局变量有文件作用域,也就是说,在整个文件作用域中,除了定义了同名局部变量块以外的其他地方(可见性问题),都可以直接引用全局变量;局部变量就是 具有块作用域的对象。
    局部变量也可以有静态生存期,静态局部数据的生存期并不与局部作用域一致,它的生存期一直延续到程序运行结束。它产生于所在的函数第一次被调用、该局部静态变量被创建时。函数返回时,静态局部变量处于不可访问的休眠状态。当再次调用该函数时,静态局部变量又苏醒过来,处于可访问状态,与动态局部变量的区别在于,静态局部变量苏醒时,并不需要初始化,而是保持上次休眠状态开始之前的值。
    可见性与作用域的区别:几乎是一样的,没有太大的区别,变量的作用域影响着变量的可见性。例如,内外层同时定义同名变量,则外层变量对于内层来说就是不可见的。

    展开全文
  • 休眠让当前线程暂停执行,从运行状态进入阻塞状态,将 CPU 资源让给其他线程的一种调度方式。 sleep() - 线程合并 合并是将指定的某个线程合并到当前线程中,将原本两个交替执行的线程改为顺序执行,...

     

    ### 线程调度
    
    - 线程休眠
    
    休眠是指让当前线程暂停执行,从运行状态进入阻塞状态,将 CPU 资源让给其他线程的一种调度方式。
    
    sleep()
    
    - 线程合并
    
    合并是指将指定的某个线程合并到当前线程中,将原本两个交替执行的线程改为顺序执行,即一个线程执行完毕之后再来执行第二个线程,通过调用线程的 join 方法来实现合并。
    
    假设有两个线程:线程A和线程B,线程A在执行到某个时间点时,调用线程B的join方法,则表示从当前的时间节点开始,CPU资源被线程B独占,线程A进入阻塞状态,等待获取CPU资源进入运行状态。
    
    - 线程礼让
    
    在某一个时间节点,线程暂停争夺CPU资源,进入阻塞状态,但只是暂时的一瞬间,过后线程对象再次进入就绪状态来争夺CPU资源,yield 实现线程礼让。
    
    - 线程中断
    
    1、线程执行完毕之后自动停止。
    
    2、线程在执行过程中出现错误停止。
    
    3、线程在执行过程中手动停止。
    
     - public void stop()
     - public void interrupt()
     - public boolean isInterrupt()
    
    ### 线程同步
    
    可以通过 synchronized 修饰方法来实现线程同步,每个 Java 对象都有一个内置锁,内置锁会保护使用 synchronized 关键字修饰的方法,要调用该方法就必须先获得内置锁,否则就处于阻塞状态。
    
    ### 线程安全的单例模式
    
    单例是一种常见的软件设计模式,其核心思想是一个类只能有一个实例对象,由多个线程来共享该实例对象的资源。
    
    ```java
    public class SingletonDemo {
        private static SingletonDemo instance;
        private SingletonDemo() {
            System.out.println("创建单例对象");
        }
        public static SingletonDemo getInstance() {
            synchronized (SingletonDemo.class) {
                if(instance == null) {
                    instance = new SingletonDemo();
                }
            }
            return instance;
        }
    }
    ```

    Account.java

    public class Account implements Runnable {
        private static int num;
        @Override
        public synchronized void run() {
            // TODO Auto-generated method stub
            try {
                Thread.currentThread().sleep(1);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            num++;
            System.out.println(Thread.currentThread().getName()+"是当前第"+num+"位访客");
        }
        
    }

     

    JoinRunnable.java

    public class JoinRunnable implements Runnable {
    
        @Override
        public void run() {
            // TODO Auto-generated method stub
            for(int i = 0; i < 200;i++) {
                System.out.println(i+"---------JoinRunnable");
            }
        }
    
    }

     

    MyRunnable.java

    public class MyRunnable implements Runnable {
    
        @Override
        public void run() {
            // TODO Auto-generated method stub
            for(int i = 0; i < 100; i++) {
                try {
                    Thread.currentThread().sleep(3000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println("------MyRunnable");
            }
        }
    
    }

     

    MyThread.java

    public class MyThread extends Thread {
        @Override
        public void run() {
            // TODO Auto-generated method stub
            for(int i = 0; i < 100;i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println("------Thread");
            }
        }
    }

     

    SingletonDemo.java

    public class SingletonDemo {
        private static SingletonDemo instance;
        private SingletonDemo() {
            System.out.println("创建单例对象");
        }
        public static SingletonDemo getInstance() {
            synchronized (SingletonDemo.class) {
                if(instance == null) {
                    instance = new SingletonDemo();
                }
            }
            return instance;
        }
    }

     

    SynchronizedTest.java

    public class SynchronizedTest {
        public static void main(String[] args) {
            for(int i = 0;i<5;i++) {
                Thread thread = new Thread(new Runnable() {
                    
                    @Override
                    public void run() {
                        // TODO Auto-generated method stub
                        SynchronizedTest synchronizedTest = new SynchronizedTest();
                        synchronizedTest.test();
                    }
                });
                thread.start();
            }
        }
        
        public void test() {
            synchronized (SynchronizedTest.class) {
                System.out.println("start...");
                try {
                    Thread.currentThread().sleep(1000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println("end...");
            }
        }
    }

     

     Test.java

    public class Test {
        public static void main(String[] args) {
    //        MyRunnable myRunnable = new MyRunnable();
    //        Thread thread = new Thread(myRunnable);
    //        thread.start();
            MyThread myThread = new MyThread();
            myThread.start();
        }
    }

     

    Test2.java

    public class Test2 {
        public static void main(String[] args) {
            JoinRunnable joinRunnable = new JoinRunnable();
            Thread thread = new Thread(joinRunnable);
            thread.start();
            for(int i = 0;i<500;i++) {
                if(i == 10) {
                    try {
                        thread.join(500);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
                System.out.println(i+"+++++++++main");
            }
        }
    }

     

    Test3.java

    public class Test3 {
        public static void main(String[] args) {
            YieldThread1 thread1 = new YieldThread1();
            thread1.setName("Thread1");
            YieldThread2 thread2 = new YieldThread2();
            thread2.setName("Thread2");
            thread1.start();
            thread2.start();
        }
    }

     

    Test4.java

    public class Test4 {
        public static void main(String[] args) {
            Thread thread = new Thread(new Runnable() {
                
                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    for(int i = 0; i < 10;i++) {
                        if(i == 5) {
                            Thread.currentThread().interrupt();
                        }
                        System.out.println("------Test");
                    }
                }
            });
            thread.start();
            System.out.println(thread.getState());
            System.out.println(thread.isInterrupted());
            System.out.println(thread.getState());
        }
    }

     

    Test5.java

    public class Test5 {
        public static void main(String[] args) {
            Account account = new Account();
            Thread t1 = new Thread(account,"线程A");
            Thread t2 = new Thread(account,"线程B");
            t1.start();
            t2.start();
            
        }
    }

     

    Test6.java

    public class Test6 {
        public static void main(String[] args) {
            Thread thread = new Thread(new Runnable() {
                
                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    SingletonDemo singletonDemo = SingletonDemo.getInstance();
                }
            });
            thread.start();
            Thread thread2 = new Thread(new Runnable() {
                
                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    SingletonDemo singletonDemo = SingletonDemo.getInstance();
                }
            });
            thread2.start();
        }
    }

     

     YieldThread1.java

    public class YieldThread1 extends Thread {
        @Override
        public void run() {
            // TODO Auto-generated method stub
            for(int i = 0; i < 10; i++) {
                if(i == 5) {
                    yield();
                }
                System.out.println(getName()+"------"+i);
            }
        }
    }

     

    YieldThread2.java

     

    public class YieldThread2 extends Thread {
        @Override
        public void run() {
            // TODO Auto-generated method stub
            for(int i = 0; i < 10;i++) {
                System.out.println(getName()+"------"+i);
            }
        }
    }

     

    转载于:https://www.cnblogs.com/HiJackykun/p/11172817.html

    展开全文
  • 休眠与关机的不同是,休眠将当前工作环境转换为休眠文件,保存在系统盘的根目录下,下次开机后会自动恢复现场。休眠文件保存之后,机器会彻底关闭。 休眠 = 关机前 + 将当下内存中的数据保持到硬盘的某个文件中。 ...
  • Java 中的一条语句还真不一定是一条 “操作”,这里说的 “操作” 是对 CPU 而言的,的是一条指令。 而我们 Java 中的一条语句可能由一条指令组成,也可能由多条指令组成,操作系统只能保证一条指令的原子性,也...
  • 几个礼拜没管博客,评论也没有邮箱提醒,因此上去LeanCloud上看是不是评论系统出问题了,结果一看日志,果然,评论系统的邮箱提醒实例一直都是处于休眠状态 报错内容如下: CloudQueue 运行失败 50dfc756-16b4-4de4...
  • 不(da)定(si)(bu)更新,注明本帖链接即可转载。我可懒得写太详细,所以直接引了贴吧/论坛链接,这里衷心感谢原作。  首发贴吧,结果没几个人回复加上某度抽风难止就转移阵地到博客园了,自家的博客就详细...
  • 千次阅读 2016-11-22 15:18:10
    在执行设备操作时若不能获得资源则挂起进程,直到满足可操作的条件后再进行操作。 被挂起的进程进入休眠状态,被从调度器的运行队列移走,直到等待的条件被满足。 非阻塞操作 进程在不能进行设备...
  • 小学实训之day2

    2016-07-19 21:53:40
     (3)使用sleep(int millsecond)来使自己放弃cpu资源,休眠一段时间。  (4)使用synchronized关键字来修饰将要被多个线程使用的方法,进而达到线程的同步。 二、反射。 JAVA反射机制是在运行状态中,...
  • 一 C语言中的变量存储类别: 1 auto(自动) 2 register(寄存器) ...不同的分配方式下,变量的生存,作用域和可见域各不相同。 二 作用域区分: 变量可分为局部变量和全局变量。 局部变量: 在函数内部定义...
  • 所谓核苷酸,是一个核苷加上一个或多个磷酸基团,核苷则是一个碱基加上一个糖类分子。 DNA携带有合成RNA和蛋白质所必需的遗传信息,是生物体发育和正常运作必不可少的生物大分子。 DNA由脱氧核苷酸组成的大分子...
  • 深入理解Java并发之synchronized实现原理

    万次阅读 多人点赞 2017-06-04 17:44:44
    【版权申明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权) ... 出自【zejian的博客】 关联文章: 深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) ...深入理解Java注解类型(@...
  •  回到这个话题本质,在eDRX(DRX)的休眠(程序员吹牛)期间,平台(不运营商平台)发送的消息(BUG报错)是否能通知到终端(程序员)?答案是: 可以的 。  在终端休眠期间,如果云端有数据回传下发,此时...
  • 生存,是程序中的简单变量和类的对象,它们同自然界中的万物一样,也有自己的产生和消亡的生存,变量和对象的生存包括静态生存和动态生存。具有静态生存的变量和对象,它们的生存与程序的运行相同...
  • MySql优化之前探索

    2020-02-17 08:11:41
    Command:显示当前连接执行的是什么命令,一般有休眠(Sleep),查询(Query),连接(Connection)等 Time:显示这个状态持续的时间,单位是秒 State:显示当前sql语句执行状态,比如查询语句可能经过如下状态:...
  • 基于S3C2440A+SDRAM(K4M51163)

    千次阅读 2010-10-13 13:21:00
    同步是其时钟频率(CLK)与CPU的AHB总线时钟频率(HCLK)相同,并且内部的命令的发送于数据的传输都以它为基准;动态是存储阵列需要不断的刷新来保证数据不丢失;随机是数据不是线性依次存储,而是自由指定地址进
  • 对于缓存,大家肯定都不陌生,不管是前端还是...而缓存穿透是缓存和数据库中都没有的数据,这样每次请求都会去查库,不会查缓存,如果同一时间有大量请求进来的话,就会给数据库造成巨大的查询压力,甚至击垮db系统。
  • 关于用户生命周期分析的总结

    千次阅读 2020-04-21 11:36:02
    通常情况下,我们将用户的生命周期划分为以下五个阶段:引入期、成长期、成熟期、休眠期、流失期。我们在进行生命周期的划分时候要根据自己所在的行业情况来进行划分,依据不同的行业情况来划分相应的阶段。 2.在用户...
  • Java并发编程与技术内幕:线程池深入理解

    万次阅读 多人点赞 2016-06-08 08:38:57
    林炳文Evankaka原创作品。...  摘要: 本文主要讲了Java当中的线程池的使用方法、注意事项及其实现源码实现原理,并辅以实例加以说明,对加深...照笔者的简单理解,其实就是一组线程实时处理休眠状态,等待唤醒执行。...
  • 多线程之基础篇

    千次阅读 多人点赞 2015-06-14 23:10:14
    进程是一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。 一个进程是一个独立的运行环境,它可以被看作一个程序或者一个应用。而线程是在进程中执行的一个任务。Java...
  • 并发(Concurrency) 的是多个执行单元同时、 并行被执行, 而并发的执行单元对共享资源(硬件资源和软件上的全局变量、 静态变量等) 的访问则很容易导致竞态(Race Conditions)   只要并发的多个执行单元存在...
  • blocking commit 主要用于显示的休眠唤醒操作,而 non-blocking commit 则用于显示的内容更新,以便能达到最高刷新率。 但是这里面有个细节,就是用于实现 non-blocking commit 的 workqueue 是 system_unbound_wq,...
  • 用户生命周期可分为:引入期、成长期、成熟期、休眠期、流失期。对应的是用户对产品不同的参与程度。 用户生命周期有什么用? 按照用户生命周期来划分用户,能够帮助我们了解处于不同生命周期用户的需求,开展...
  • 自从今年六一儿童节开缸以来,已经过去5个月了,也在张大妈分别发表了四篇“菜鸟的草缸”...知道张大妈里卧虎藏龙,不乏有众多高手和高高手,如果文章有哪些地方说错的,请大家出来,谢谢!我们开始草缸之旅吧。...
  • 水曰润下,火曰炎上,木曰曲直(弯曲,舒张),金曰从革(成分致密,善分割),土爰稼穑(意播种收获)。润下作咸,炎上作苦,曲直作酸,从革作辛,稼穑作甘。” 《孔子家语·五帝》:“天有五行,水、火、金、...
  • Java Thread 总结

    2014-02-20 17:04:03
    Ø线程的概述(Introduction) 线程是一个程序的多个执行路径,执行调度的单位,依托于进程存在。 线程不仅可以共享进程的内存,而且还拥有一个属于自己的...抢占机制的是有多个线程处于可运行状态,但是只允许
  • 第二-Linux内核发展史(1)

    千次阅读 2020-04-03 15:41:08
    对探测模块的性能支持和可访问列表本地和全局变量、使用LZO压缩的图像休眠、IPv4支持的PPP、一些网络微优化和许多其他小的更改、改进和新的驱动程序。 2.6.38 2011.3.14 此版本增加了对自动进程分组(在新闻中称为...
  • 架构真经 | 缓存为王

    千次阅读 多人点赞 2017-04-18 23:48:59
    作者介绍:陈斌,易宝支付 CTO,《架构即未来》、《架构真经》等书译者,1024 学院互联网 CTO 班幽谷派掌门。 正文在商业世界中,人们常说“现金为王”。然而,在技术世界里,我们...静态内容那些不经常改变的文本和

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 5,469
精华内容 2,187
关键字:

休眠期是指