If you are going to interview Android clients, you will have to ask the basics of Java and Kotlin, so I plan to spend 3 or 4 articles summarizing the basics of Java that will be asked in Android interviews.
1. The difference between object-oriented and procedural
Process-oriented: Process-oriented performance is higher than object-oriented performance. Because object invocation requires instantiation, it costs a lot and consumes resources, so when performance is the most important factor, such as SCM, embedded development, Linux/Unix, etc., process-oriented development is generally adopted. However, process orientation is not as easy to maintain, reuse, and extend as object orientation. Object oriented: Object oriented is easy to maintain, easy to reuse, easy to expand. Because object orientation has the characteristics of encapsulation, inheritance and polymorphism, it can design a low coupling system, which makes the system more flexible and easier to maintain.
So why is process oriented performance better than object oriented? Procedural orientation also requires allocating memory and calculating memory offsets. The main reason for Java’s poor performance is not because it is an object-oriented language, but because Java is a semi-compiled language, and the final execution code is not binary machine code that can be executed directly by the CPU. Most procedural languages are compiled directly into machine code for execution on the computer, and some other procedural scripting languages do not necessarily perform better than Java.
2. What are the characteristics of object orientation
- Encapsulation: Encapsulation is generally thought of as binding data to methods that manipulate it, and data can only be accessed through defined interfaces.
- Inheritance: Inheritance is the process of taking inherited information from an existing class and creating a new class. Classes that provide inheritance information are called superclasses (superclasses, base classes); The class that gets the inheritance information is called a subclass (derived class).
- Abstract: Abstract is the process of constructing a class by summarizing the common characteristics of a class of objects, including data abstraction and behavior abstraction. Abstractions focus only on what properties and behaviors an object has, not on the details of those behaviors.
- Polymorphism: Polymorphism is the practice of allowing objects of different subtypes to respond differently to the same message. That is, the same message can behave differently depending on which object it is sent to.
3. Explain the coexistence of Java compilation and interpretation
When.class bytecode files are converted through the JVM into binary machine code that can be executed by the machine, the JVM class loader first loads the bytecode files and then interprets them line by line through the interpreter, which is relatively slow to execute. Also, some methods and blocks of code are called repeatedly (so-called hot code), so a JIT compiler was introduced later, and JIT is runtime compilation. When the JIT compiler completes a compilation, it saves the machine code corresponding to the bytecode, which can be called directly the next time. This explains why we often talk about Java as a compilation and interpretation language.
4. A brief introduction to the MEMORY model of the JVM
The memory managed by Java virtual machines consists of program counters, Java virtual machine stack, local method stack, Java heap, and method area, as shown in the following figure.
4.1 Program counter
Because multithreading in the Java virtual machine is implemented by switching threads and allocating processor execution time, only one processor will execute instructions in one thread at any given time. In order to restore the thread to the correct execution position after switching, each thread needs to have an independent program counter. Counters between each thread do not affect each other and are stored independently. This kind of memory area is the memory of [thread private].
The program counter has the following characteristics:
- It’s a small memory space.
- Threads are private; each thread has its own program counter.
- On the lifecycle side, it is created as a thread is created and destroyed as a thread terminates.
- Is the only memory region where OutOfMemoryError does not occur.
4.2 Java Virtual Machine Stack
The Java virtual machine stack, which describes the thread-memory model of the execution of Java methods, is also thread-private and its lifecycle is synchronized with that of the thread. When each method is executed, the Java VIRTUAL machine synchronously creates a block of memory to store information during the execution of the method. Each method invocation corresponds to a stack frame in the VIRTUAL machine from the stack to the stack.
The Java virtual machine stack has the following characteristics:
- The memory space required for the local variable table is allocated at compile time, and when entering a method, the amount of local variable space that the method needs to allocate in the stack frame is fully determined and does not change the size of the local variable table during the method run.
- There are two types of exceptions to the Java virtual machine stack: StackOverflowError and OutOfMemoryError.
4.3 Local method stack
The role of the local method stack is similar to that of the virtual machine, except that the virtual machine stack serves the Java methods executed by the virtual machine, whereas the local method stack serves the local methods used by the virtual machine.
4.4 Java heap
The Java heap is the largest chunk of memory managed by the virtual machine. The Java heap is an area of memory shared by all threads and created when the virtual machine is started. The sole purpose of this memory area is to hold object instances, and “almost” all object instances in Java are allocated memory here. The use of “almost” is due to the development of the Java language, just-in-time compilation, the growing power of escape analysis, on-stack allocation, scalar substitution and other optimizations that make it less absolute that Java object instances are allocated on the heap. The Java heap is the primary area managed by the garbage collector and is often referred to as the “GC heap”. From the point of view of memory reclamation, since collectors now use generational collection algorithms (which have changed since G1, introducing regions, but still using generational ideas), the Java heap can also be subdivided into: New generation and old generation. More detailed are Eden space, From Survivor space, ToSurvivor space, etc. From the perspective of memory Allocation, it is possible to allocate Thread Local Allocation Buffers (TLabs) that are private to multiple threads in a Java heap shared by threads.
The Java heap size can be fixed or expanded, but the heap size of most VMS can be expanded. OutOfMemoryError is thrown if a thread is required to request memory allocation, but the heap is full and memory can no longer be expanded. Such as:
/ * * * Args VM: - Xms10m - Xmx10m - XX: + HeapDumpOnOutOfMemoryError * /
public class HeapOOMTest {
public static final int _1MB = 1024 * 1024;
public static void main(String[] args) {
List<Integer[]> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Integer[] ints = new Integer[2 * _1MB];
list.add(ints); }}}Copy the code
4.5 method area
The method area, like the Java heap, is an area of memory shared by threads that stores information about classes that have been loaded by the virtual machine, constants, static variables, and code caches compiled by the just-in-time compiler.
In the HotSpot JVM, the persistent generation (the persistent generation implementation Method area) is used to hold metadata for classes and methods, as well as constant pools such as Class and Method. Whenever a class is first loaded, its metadata is put into the persistent generation. The permanent generation is limited in size, so if too many classes are loaded, it is very possible to run out of memory for the permanent generation, so we have to tune the virtual machine.
Later HotSpot abandoned PermGen. In jdk1.7 HotSpot removed the string constant pool, static variables and so on from the PermGen. In jdk1.8 HotSpot completely abandoned PermGen and moved the method area to Metaspace. Such as class meta information, fields, static properties, methods, constants, and so on are all moved to the metasespace area. Similar in nature to permanent generations, meta-spaces are implementations of method areas in the JVM specification. However, the biggest difference between a meta-space and a permanent generation is that the meta-space is not in the virtual machine, but uses local memory. Therefore, by default, the size of the meta-space is limited only by local memory.
Common JVM tunings are as follows:
parameter | Function description |
---|---|
-XX:MetaspaceSize | The initial size assigned to Metaspace (in bytes). If not set, the default is 20.79 MB. This initial size is the threshold for triggering the first Metaspace Full GC, for example -xx :MetaspaceSize=256M |
-XX:MaxMetaspaceSize | The maximum value allocated to Metaspace, beyond which Full GC is triggered, is unlimited by default but should depend on the size of system memory. The JVM changes this value dynamically. However, you are advised to set this parameter for online environments, for example, -xx :MaxMetaspaceSize=256M |
-XX:MinMetaspaceFreeRatio | Minimum free ratio. When Metaspace GC occurs, the Metaspace free ratio is calculated. If the free ratio (free space/current Metaspace size) is smaller than this value, Metaspace expansion is triggered. The default value is 40, or 40%, for example -xx :MinMetaspaceFreeRatio=40 |
-XX:MaxMetaspaceFreeRatio | Maximum free ratio, when Metaspace GC occurs, the Metaspace free ratio is calculated. If the free ratio (free space/current Metaspace size) is greater than this value, the Metaspace free space is triggered. The default value is 70, or 70%, for example, -xx :MaxMetaspaceFreeRatio=70 |
Runtime constant pool runtime constant pool is part of the method, in addition to the Class file has a version of a Class, field, method, interface description information, such as there is a information is constant pool table, used to store generated during compilation of literal and symbolic references, this part will deposit after Class loading to the method of runtime constants in the pool. The method area holds: class information, constants, static variables, and just-in-time compiler compiled code. Constants are stored in the runtime constant pool. When a class is loaded by the Java Virtual machine, constants in the.class file are stored in the runtime constant pool of the methods area. And at run time, new constants can be added to the constant pool. The intern() method of the String class, for example, adds String constants to the constant pool at run time.
4.6 Direct Memory
Direct memory is not part of the virtual machine’s run-time data area, and a channel-based and buffered IO approach is introduced in NIO. It can directly allocate memory outside of the Java VIRTUAL machine by calling local methods, and then manipulate that memory directly through a DirectByteBuffer object stored in the heap, rather than copying data from external memory to the heap, thus improving the efficiency of data manipulation.
Since direct memory is not part of the Java Virtual machine, the size of direct memory is not controlled by the Java Virtual machine, but since it is memory, OutOfMemoryError is thrown if there is insufficient memory.
Here are some similarities and differences between direct memory and heap memory:
- Direct memory requisition space costs higher performance;
- Direct memory reads IO better than ordinary heap memory.
- Direct memory function chain: local IO -> Direct memory -> Local IO
- Heap memory chain: local IO -> Direct memory -> Indirect memory -> Direct memory -> local IO
When configuring VM parameters, the server administrator sets parameters such as -xmx based on the actual memory. However, the direct memory is often ignored. As a result, the sum of memory regions is greater than the physical memory limit, and an OutOfMemoryError occurs during dynamic expansion.
5. A brief introduction to Java class loaders
Java class loaders can be divided into BootstrapClassLoader, ExtClassLoader, and AppClassLoader. Their functions are as follows.
- BootstrapClassLoader: The Bootstrap class loader is responsible for loading JDK class files in rt.jar and is the parent loader of all class loaders. The Bootstrap class loader no parent class loader, if the call String. Class. GetClassLoader (), returns null, any code sell NUllPointerException based on this, Therefore, the Bootstrap loader is also called the initial class loader.
- ExtClassLoader: Extension will delegate the request to load the class to its parent loader, Bootstrap, and then load the class from the jre/lib/ext directory or the directory defined by the java.ext.dirs system property if the load fails. The Extension loader is implemented by sun.misc.Launcher$ExtClassLoader.
- AppClassLoader: The default Java loader is the System class loader, also known as the Application class loader. It is responsible for loading some application-specific classes from the classpath environment variable, which is usually defined by the -classpath or -cp command-line options, or the classpath property of the Manifest in the JAR, The Application class loader is a child of the Extension class loader.
Class loading involves several loading mechanisms.
- Delegation mechanism: The loading task is entrusted to the parent class loader. If it fails, the delegate task is passed down and loaded by its subclass loader, ensuring the security of the Java core library.
- Visibility mechanism: The subclass loader can see the classes loaded by the parent class loader, but not vice versa.
- The rule of uniformity: a class that has been loaded by the parent loader cannot be loaded twice by the quilt loader.
6. Talk about Java garbage collection and common garbage collection algorithms.
Java memory management mainly involves three parts: the heap (the Java heap reachable by Java code and the method area used by the JVM itself), the stack (the virtual machine stack that serves Java methods and the local method stack that serves Native methods), and the program counter that ensures the continuous execution of programs in a multithreaded environment. The Java heap is the main area for garbage collection, so it is also known as the GC heap; The method area mainly focuses on Cenozoic and Mesozoic. In general, heaps (including Java heaps and method areas) are the primary object of garbage collection, and Java heaps in particular.
6.1 Garbage collection Algorithm
6.1.1 Object survival Judgment
Reference counting
Each object has a reference count property. When a new reference is added, the count is increased by 1. When a reference is released, the count is decreased by 1. This approach, while simple, does not solve the problem of objects referring to each other circularly.
Accessibility analysis
A downward search from GC Roots takes a path called the reference chain. When an object is not connected to GC Roots by any reference chain, the object is deemed unavailable. In Java, GC Roots includes:
- Object referenced in the virtual machine stack.
- The object referenced by the class static attribute entity in the method area.
- The object referenced by the constant in the method area.
- Objects referenced by JNI in the local method stack.
6.2 Garbage collection algorithm
Mark clearance
As its name suggests, the algorithm is divided into two phases: “mark” and “clean” : first, mark all the objects that need to be recycled, and then recycle all the marked objects. The reason why it is the most basic collection algorithm is that subsequent collection algorithms are based on this idea and improve its shortcomings. The complex labeling algorithm has two main shortcomings: one is the efficiency problem, the efficiency of labeling and clearing process is not high; Another problem is the space problem. After the mark is cleared, a large number of discrete memory fragments will be generated. Too many space fragments may cause the program to be unable to find enough contiguous memory when it needs to allocate large objects in the future and have to trigger another garbage collection action in advance.
Replication algorithm
A replication collection algorithm that divides available memory by capacity into two equally sized pieces, one of which is used at a time. When this area of memory is used up, the surviving objects are copied to the other area, and the used memory space is cleaned up again. The advantage of it is that it only needs to reclaim one piece of memory at a time, and there is no need to consider the complicated situation of memory fragmentation when allocating memory, as long as the heap top pointer is moved and the memory is allocated in order, which is simple to implement and efficient to run. The disadvantages are obvious: memory shrinks to half of its original size, and continuous copying of long-lived objects reduces efficiency.
Label finishing
The copy-collection algorithm will perform more replication operations when the object survival rate is high, and the efficiency will be low. More importantly, if you do not want to waste 50% of the space, you need to have extra space for allocation guarantee, in the extreme case that all objects in the memory used are 100% alive, so in the old days, this algorithm generally cannot be used directly. According to the characteristics of the old s, someone put forward another “tag – finishing” (Mark – Compact) algorithm, the labeling still like “tag – clear” algorithm, but the subsequent steps are not directly to recyclable objects to clean up, but let all live objects move to the end, and then clear directly outside the boundary of the memory.
Generational collection algorithm The generational collection algorithm divides the Java heap into new generation and old generation, so that the most appropriate collection algorithm can be adopted according to the characteristics of each generation. In the new generation, a large number of objects are found dead and only a few survive in garbage collection, so the replication algorithm is selected, and only a small amount of the replication cost of the surviving objects can be collected. In the old days, because the object has a high survival rate and there is no extra space to allocate it, it has to use the “mark-clean” or “mark-tidy” algorithm for recycling.
7. The difference between member variables and local variables
- Syntactically, a member variable belongs to a class, while a local variable is a variable defined in a method or a method parameter. Member variables can be modified by public, private, static, etc., while local variables cannot be modified by these modifiers. But they can all be modified by final.
- If a variable is static, it belongs to a class. If it is not static, it belongs to an instance. Objects live in heap memory, and local variables live in stack memory (specifically, the Java virtual machine stack).
- In terms of the memory lifetime of variables: member variables are part of the object, it exists with the creation of the object, and local variables disappear automatically with the end of the method call.
- If a member variable is not initialized, it is automatically assigned to the default value of the type (exception: final modified member variables must be assigned at initialization). Local variables are not automatically assigned.
8. What it means to know about Overriding and Overload in Java
Method rewriting In Java programs, the class inheritance relationship can produce a subclass that inherits from the parent class. It has all the characteristics of the parent class and inherits all the methods and variables of the parent class. Subclasses can define new features. When a subclass needs to modify some methods of its parent class to extend and increase functionality, programmers often refer to such an operation as overwriting, also known as overwriting or overwriting.
Method rewriting has the following characteristics:
- The method name, argument list must be the same, and the return type can be the same or a subtype of the original type
- Overriding methods cannot be less accessible than the original method (that is, access permissions cannot be narrowed).
- Overriding methods cannot throw more exceptions than the original method.
- Overrides occur between subclasses and superclasses.
- Rewrite to implement runtime polymorphism.
Method overloading Method overloading is a way for classes to handle different types of data in a uniform way. When methods are called, they are determined by the different number and type of arguments passed to them. This is called polymorphism. Method overloading is when multiple methods in a class have the same method name but different argument lists. Different parameter lists refer to different parameter numbers, parameter types, or parameter sequences.
- The method name must be the same, and the parameter list must be different (different number, or type, parameter type order, etc.).
- Methods can have the same or different return types.
- Overloading occurs in the same class.
- Overloading implements compile-time polymorphism.
A brief introduction to passing and reference passing
Passing by value: Passing by value means that when a function is called, a copy of the actual parameter is passed to the function so that the actual parameter is not affected if it is changed in the function. In simple terms, it directly copies a copy of the data in the past, because it is directly copied, so the operation efficiency of this method will naturally become low if the data volume is very large. Therefore, the data transfer in Java is value transfer, such as the various basic types in Java: Int, float, double, Boolean, etc.
Passing by reference: Reference is actually made up for the inadequacy of the above said, if every time parameter is a copy, if this parameter is to take up too much memory space, the efficiency will be under, so the reference is a direct hand in the past, the memory address that is reference, operation are the source of the data, so change sometimes conflict, Remember to use logic to make up for it. There are more specific data types, such as Object, two-dimensional array, List, Map, etc. In addition to the basic types of parameters are passed by reference.
10. Why must hashCode be overridden when overriding equals
Here are the rules for using hashCode() and equals() :
- If two objects are equal (with equals returning true), the HashCode must also be the same;
- Two objects have the same hashcode value, and they are not necessarily equal (different objects may also produce the same Hashcode, probabilistic problems);
- If equals is overridden, hashCode must be overridden as well.
Why do I have to override the HashCode method? So if you overwrite equals and you don’t overwrite hashcode, It is possible to have two unrelated objects equal to equals (because equals is overridden based on the attributes of the objects), but HashCode is not the same.
11. What are the differences and similarities between interfaces and abstract classes
Similarities:
- Interfaces are absolutely abstract and cannot be instantiated, nor can abstract classes be instantiated.
- Classes may not implement all the methods declared by abstract classes and interfaces, in which case, of course, the class must also be declared abstract.
Similarities and differences:
- From the design level, abstraction is the abstraction of class, is a template design, interface is the abstraction of behavior, is a behavior specification.
- The key for defining an interface is interface, and the key for an abstract class is Abstract class
- All methods in an interface are implicitly abstract. Abstract classes, on the other hand, can contain both abstract and non-abstract methods.
- Classes can implement many interfaces, but can only inherit from one abstract class, and interfaces can inherit from multiple interfaces
- Variables declared in Java interfaces are public static final by default. Abstract classes can contain non-final variables.
- Before JDK1.8, you could not have static methods in an interface. You could have normal and static methods in an abstract class. After JDK1.8, interfaces can have default methods and static methods, and have method bodies.
- Abstract classes can have constructors, but cannot be instantiated directly by the new keyword.
- Before JDK1.8, the default access to abstract methods of abstract classes is protected, 1.8 default access is default, there are three modifiers default, protected, public, non-abstract methods can use four modifiers. Before JDK1.8, the default interface method was public. In 1.8, the default interface method was public, and public and default could be used. In 1.9, interface methods also supported private.
12. Briefly describe HashMap
At the bottom of HashMap, the data structure of array + linked list is adopted. Array is the main body of HashMap, while linked list is mainly used to solve hash conflicts.
If the location of the array does not contain a list, the search, add, and other operations are quick, requiring only one address; If the array to be located contains a linked list, the time complexity of the add operation is O(n), and the linked list is first traversed. If it exists, it will be overwritten; otherwise, it will be added. For lookups, you still need to traverse the linked list and then compare lookups one by one through the Equals method of the key object. Therefore, for performance purposes, the fewer linked lists in a HashMap, the better the performance.
HashMap has four constructors. Other constructors use default values initialCapacity (16) and loadFactory (0.75) if the user does not pass in the initialCapacity and loadFactor parameters.
public HashMap(int initialCapacity, float loadFactor) {// Check the initial capacity passed in here. The maximum value cannot be greater than MAXIMUM_CAPACITY = 1<<30(230)
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor; threshold = initialCapacity;init(a);The init method has no actual implementation in HashMap, but it does in its subclasses such as linkedHashMap
}
Copy the code
The reason for the loading factor, again, is to mitigate hash collisions, so if you start with a bucket of 16 and wait until it has 16 elements to expand, some buckets may have more than one element. So the default load factor is 0.75, which means that a HashMap of size 16 will grow to 32 by element 13.
Put the process
- Determines whether the current array is to be initialized.
- If key is null, put a null value into it.
- Calculate the Hashcode based on the key.
- Locate the position in the bucket according to hSAHcode.
- If the bucket is a linked list, you need to iterate over the HashCode, and if the key is equal to the original key, it overwrites and returns the original value.
- If the bucket is empty, no data is stored in the current location. An Entry object is added to write the current location. When calling addEntry to write Entry, you need to determine whether to expand the capacity. Double the size if necessary, and rehash the current key and position it. CreateEntry passes the bucket with the current location to the new bucket, and if the bucket has a value, it forms a linked list of locations.
The Get process
- Calculate the HashCode based on the key and locate the location within the bucket.
- If the key is equal to the hashcode of the key, return the value. If the key is not equal to the hashcode of the key, return the value.
- Returns null if nothing is retrieved.
The underlying HashMap of JDK 1.8 adopts linked list + red-black tree. A threshold is added to determine whether to convert the linked list to red-black tree and change HashEntry to Node. The purpose is to solve the problem that the linked list becomes longer and the query is slow caused by hash conflicts.
The Get process
- To check whether the bucket is empty, initialize it.
- According to the hashcode, locate the specific bucket and determine whether the current bucket is empty. If it is empty, create a new bucket if there is no HSAH conflict.
- If there is a hash conflict, the key in the bucket is compared, and the hashcode of the key is equal to the written key. If the key is equal, the value is assigned to e. At step 8, the assignment and return are performed uniformly.
- If the current position is a red-black tree, the data is written as a red-black tree.
- If the current location is a linked list, encapsulate the key and value into a new node and add it to the end of the bucket (tail insertion method) to form a linked list.
- Then judge whether the size of the current list is greater than the preset threshold, if so, it will be converted to a red-black tree;
- If the same key is found during traversal, exit traversal directly;
- If e! = null is equivalent to the same key, which needs to be overwritten;
- Finally, determine whether expansion is needed;
The Get process
- The key is first hash and the bucket is retrieved.
- If the bucket is empty, null is returned.
- Otherwise, it checks whether the key in the first position of the bucket (such as a linked list or red-black tree) is the query key and returns value directly.
- If the first one does not match, it is determined whether the next one is a red-black tree or a linked list.
- The red-black tree returns the value as the tree looks up.
- Otherwise, the match returns are iterated through in a linked list.
13, CurrentHashMap
In JDK8, ConcurrentHashMap refers to the implementation of JDK8 HashMap, using array + linked list + red-black tree implementation to design, a large number of internal CAS operation, that is what is CAS.
CAS is the abbreviation of compare and swap, which is called “comparison exchange” in Chinese. CAS is a lock-based operation, and it is optimistic locking. In Java, there are optimistic locks and pessimistic locks. Pessimistic locking means that a resource is locked until the next thread can access it after the previous thread has released the lock. Optimistic locks, on the other hand, take a broad approach, processing resources in some way without locking, and have much better performance than pessimistic locks.
The CAS operation contains three operands — the memory location (V), the expected old value (A), and the new value (B). If the value in the memory address is the same as the value of A, then the value in memory is updated to B. CAS fetches data through an infinite loop. If thread A gets the address in the first loop and the value in the address is changed by thread B, thread A needs to spin until the next loop can execute.
14. What are optimistic locks and pessimistic locks
Java is divided into optimistic lock and pessimistic lock according to the implementation of lock, optimistic lock and pessimistic lock is not a real lock, but a design idea.
Pessimistic locking Pessimistic lock is a kind of negative thinking, it always think the worst may appear, it was assumed that the data could be modified by others, so pessimistic locks in holding data always lock the resources, or data, so that other threads want to request this resource will be blocked, until when pessimistic locks release resources. Traditional relational database inside used a lot of this locking mechanism, such as row lock, table lock, read lock, write lock, etc., are in the operation before the first lock. The realization of pessimistic locking often depends on the locking function of database itself.
Exclusive locks such as Synchronized and ReentrantLock in Java are also implementations of the pessimistic locking concept because Synchronzied and ReetrantLock attempt to lock regardless of whether they hold resources. Afraid of their beloved treasure is taken away by others.
Optimistic locking Optimistic locking is the opposite of pessimistic locking. It always believes that resources and data will not be modified by others, so it will not be locked, but optimistic locking will determine whether the current data has been modified during the write operation (more on how to determine this later). Generally speaking, there are two implementation schemes of optimistic locking: version number mechanism and CAS implementation. Optimistic locking is suitable for multiple application types to improve throughput.
In Java. Java util. Concurrent. Atomic package this atomic variable classes is to use the optimistic locking a way of implementation of CAS.
15. Talk about understanding Java threads
Threads are the smallest unit that can be independently executed in a process and the basic unit for allocating CPU resources (time slices). Threads in the same process can share resources in the process, such as memory space and file handles. Threads have some basic attributes, such as ID, name, and Priority. Id: The thread ID is used to identify different threads. The id can be used by subsequent threads. The id is read-only and cannot be changed. Name: the name of the Thread. The default value is Thread-(id) daemon: The Thread is either a daemon or a user Thread. SetDaemon (true) is used to set the Thread to a daemon. Daemon threads are usually used to perform unimportant tasks, such as monitoring the health of other threads, and the GC thread is a daemon thread. SetDaemon () should be set before the thread starts, otherwise the JVM will throw an illegal thread state exception that can be inherited. Priority: This value is used by the thread scheduler to determine which thread runs first (not guaranteed). The value ranges from 1 to 10. The default value is 5, which can be inherited. Thread defines the following three priority constants:
- Lowest priority: MIN_PRIORITY = 1
- Default priority: NORM_PRIORITY = 5
- Maximum priority: MAX_PRIORITY = 10
After a thread is created, it goes through the state from creation to death.The following table shows the lifetime state of a thread:
state | instructions |
---|---|
New | A new thread object is created, but the start() method has not yet been called. |
Runnable | After the Ready thread object is created, other threads (such as the main thread) call its start() method. Threads in this state are in the runnable thread pool, waiting to be selected by thread scheduler for CPU usage. A thread in the Running state becomes in the Running state after obtaining a CPU time slice. |
Blocked | The thread has relinquished CPU usage for some reason (waiting for a lock) and has temporarily stopped running. |
Waiting | The thread enters the wait state because of Object#wait(), Thread#join(), LockSupport#park() |
Terminated | The thread has finished executing. |
16. Synchronized, volatile, and Lock concurrency
Thread synchronization and concurrency often ask about the role of Synchronized, volatile, and Lock. Lock is a class, and the other two are Java keywords.
Synchronized
Synchronized is a Key word of Java and a built-in feature of Java. At the JVM level, Synchronized mutually exclusive access to critical resources is realized by operating on the header file of the object, so as to achieve the purpose of locking and releasing the lock. Code or methods that use Synchronized modifications usually have the following properties:
- Synchronized automatically releases the lock occupied by the thread when an exception occurs, so it does not cause deadlocks.
- Unable to respond to interrupts.
- Only one thread can read or write a shared resource at a time, and other threads can only wait, resulting in low performance.
Because of the above, the disadvantage of Synchronized is obvious: if a block of code is modified by Synchronized, when one thread acquires the lock and executes the block, the other threads have to wait, which is inefficient.
Volatile ensures the visibility of different threads operating on the variable. When one thread changes the value of a variable, the new value is immediately visible to other threads. And volatile disallows instruction reordering.
Instruction reordering refers to that the processor may optimize the input code to improve the efficiency of the program. It does not ensure that the execution sequence of each statement in the program is the same as that in the code, but it ensures that the final execution result of the program is the same as that in the code.
In order to be atomic, volatile must have the following conditions:
- Writes to variables do not depend on the current value
- This variable is not contained in an invariant with other variables
17, lock
Java locks can be classified as follows:
Pessimistic lock, optimistic lock
Pessimistic locks assume that data must be modified by other threads when they are using data. Therefore, they lock data before acquiring data to ensure that data cannot be modified by other threads. In Java, the implementation classes for the synchronized keyword and Lock are pessimistic locks. Pessimistic locking applies to scenarios where many write operations are performed. The pessimistic locking ensures correct data during write operations.
Optimistic locks, on the other hand, assume that no other thread will modify the data when they use it, so they will not add the lock, but just check whether the data has been updated before by another thread. If the data is not updated, the current thread writes its modified data successfully. If the data has already been updated by another thread, different operations are performed (such as reporting an error or automatic retry) depending on the implementation. Optimistic locking is implemented in Java by using lock-free programming, most commonly CAS algorithm, where incremental operations in Java atomic classes are implemented by CAS spin. Optimistic lock is suitable for scenarios where many read operations are performed. The no-lock feature greatly improves the read operation performance.
So the CAS algorithm, what is the CAS algorithm?
CAS algorithm
If one thread fails or suspends without causing other threads to fail or suspend, the algorithm is called a non-blocking algorithm. CAS is a non-blocking algorithm implementation, is also a kind of optimistic lock technology, it can achieve multi-thread security without the use of lock, so it is a lock-free algorithm.
Definition of CAS algorithm: THE main function of CAS is to achieve thread safety without locking. CAS algorithm, also known as comparison switching algorithm, is a technology often used to implement concurrent algorithms. Many classes in Java and packet sending use CAS technology. CAS contains three parameters: the current memory value V, the old expected value A, and the value B to be updated. If and only if the expected value A and the memory value V are the same, change the memory value to B and return true. Otherwise, do nothing and return false.
The basic operations for atomic updating include:
- AtomicBoolean: Atom updates Boolean variables;
- AtomicInteger: Atom update integer variable;
- AtomicLong: Atom updates long integer variables;
Taking AtomicInteger as an example, the code is as follows:
public class AtomicInteger extends Number implements java.io.Serializable {
Return the current value
public final int get(a) {
return value;
}
// The atom updates to the new value and returns the old value
public final int getAndSet(int newValue) {
return unsafe.getAndSetInt(this, valueOffset, newValue);
}
// Will eventually be set to the new value
public final void lazySet(int newValue) {
unsafe.putOrderedInt(this, valueOffset, newValue);
}
// If the input value is equal to the expected value, it is updated atomically to the new value
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
// Atomic increment
public final int getAndIncrement(a) {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
Atomic adds the current value to the input value and returns the result
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta); }}Copy the code
As an example, here is the code for incrementing an int using multiple threads, as shown below.
public class AtomicIntegerDemo {
private static AtomicInteger atomicInteger = new AtomicInteger(0);
public static void main(String[] args){
for (int i = 0; i < 5; i++){
new Thread(new Runnable() {
public void run() {
// Calling getAndIncement of AtomicInteger returns the incremented value
System.out.println(atomicInteger.getAndIncrement()); }}).start(a); } System.out.println(atomicInteger.get()); }}Copy the code
Spin lock, adaptive spin lock
Blocking or waking up a Java thread requires the operating system to switch CPU state, which takes processor time. In many scenarios, synchronization resources are locked for a short period of time, and the cost of thread suspension and site recovery may not be worth the cost to the system to switch threads for this short period of time. If the physical machine has multiple processors that allow two or more threads to execute in parallel at the same time, we can let the later thread that requests the lock not give up CPU execution time to see if the thread that holds the lock will release the lock soon.
In order for the current thread to spin, we need to spin the current thread. If the thread that held the synchronized resource before spinning has released the lock, the current thread can acquire the synchronized resource without blocking, thus avoiding the overhead of switching threads. This is called a spin lock.
A deadlock
The current thread has resources needed by other threads, and the current thread waits for resources already owned by other threads without giving up its own resources.
18. Talk about your understanding of Java reflection
Reflection refers to the ability to obtain all the properties and methods of any class in the running state. For any object, can call any of its methods and attributes, and this dynamic information and dynamic call object method function is called the Reflection mechanism of Java language.
Using bytecodes that need to be obtained in advance before reflection, there are three ways to obtain bytecodes in Java:
- Class.forName(className)
- The name of the class. The class
- this.getClass()
19, annotations
Classes, methods, variables, parameters, packages, and so on in the Java language can be annotated. Unlike Javadoc, Java annotations can be retrieved through reflection. Java annotations can be divided into three types, depending on their timing:
- SOURCE: Annotations are discarded by the compiler (annotations of this type are discarded only in the SOURCE code after the SOURCE code is compiled, not in the compiled class file), such as @override.
- CLASS: Annotations are available in the CLASS file, but are discarded by the VM (annotation information of this type is retained in the source code and in the CLASS file, and is not loaded into the VM at execution time). Note that the default value is CLASS when annotations do not define Retention.
- RUNTIME: Annotation information will be retained at RUNTIME (JVM), so annotation information can be read by reflection (source code, class file, and execution time annotation information), such as @deprecated.
20, singleton
To ensure that only one object exists, you can use the singleton pattern, and there are seven ways to write the singleton pattern on the Internet. Let’s introduce some common ones:
Lazy lazy uses the static keyword and is therefore thread-unsafe.
public class Singleton {
private static Singleton instance;
private Singleton (a){}
public static Singleton getInstance(a) {
if (instance == null) {
instance = new Singleton(a); }returninstance; }}Copy the code
To be thread-safe, use the synchronized keyword.
public class Singleton {
private static Singleton instance;
private Singleton (a){}
public static synchronized Singleton getInstance(a) {
if (instance == null) {
instance = new Singleton(a); }returninstance; }}Copy the code
However, after using synchronized lock, the operation efficiency is significantly reduced.
Static inner classes Static inner classes make use of the classLoder mechanism to ensure that instance is initialized with only one thread.
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton(a); }private Singleton (a){}
public static final Singleton getInstance(a) {
returnSingletonHolder.INSTANCE; }}Copy the code
Double check lock
public class Singleton {
private volatile static Singleton singleton;
private Singleton (a){}
public static Singleton getSingleton(a) {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton(a); }}}returnsingleton; }}Copy the code
Double-checked locking is an upgrade to synchronized, so the use of volatile is intended to prohibit reordering on initialization instances. As we know, there are four steps to initializing an instance in Java bytecode:
- Applying for Memory Space
- Initialize defaults (as opposed to constructor method initializations)
- Execute the constructor method
- Connect references and instances
The last two steps are likely to be reordered, and using volatile disables instruction reordering.