java常用包:
java.lang–语言包:Java语言的基础类,包括Object类、Thread类、String、Math、System、Runtime、Class、Exception、Process等,是Java的核心类库
java.util–实用工具包:Scanner、Date、Calendar、LinkedList、AarryList、concurrent、Hashtable、Stack、TreeSet等;
java.NET–网络功能包:URL、Socket、ServerSocket等;
java.sql–数据库连接包:实现JDBC的类库;
java.io–输入输出包:提供与流相关的各种包;
Java 常用第三方jar包:
log4j:一个非常常用的log日志jar包。
JUnit:java单元测试;
maven:项目管理的;
gson:Google 的Json解析库;
jsoup:html解析;
apache commons:包含了大量组件,很多实用小工具。
Java常用接口:
Comparable ,Collection,Set, List, Map, Runnable Iterable Iterator 等等
标识符是什么?
标识符就是用于Java程序中变量,类,方法等命名的符号。
使用标识符时,需要遵守几条规则:
标识符可以由字母,数字,下划线(——),美元($)组成,但是不能包含@,%,空格等其他的特殊符号,不能以数字开头。例如 123name 就是不合法的
标识符不能是Java关键字和保留字(Java预留的关键字,或者以后升级版本中有可能作为关键字),但可以包含关键字和保留字~例如:不可以使用void 作为标识符,但是Myvoid 可以
标识符是严格却分大小写的,所以一定要分清alibaba和ALIbaba是两个不同的标识符哦
标识符的命名最好能反应出其作用,做到见名知意
封装、抽象、继承、多态
匿名对象使用方法
new Car().run()
new Car().run()
public static void show(Car c)
{
//......
}
show(new Car());
我们一般不会给匿名对象赋予属性值,因为永远无法获取到。
两个匿名对象永远都不可能是同一个对象。
Java之中只允许多层继承,不允许多重继承,Java存在单继承局限。
父类私有属性发现无法直接进行访问,但是却发现可以通过setter、getter方法间接的进行操作
现在默认调用的是无参构造,而如果这个时候父类没有无参构造,则子类必须通过super()调用指定参数的构造方法.
定义:抽象方法必须用abstract关键字进行修饰。如果一个类含有抽象方法,则称这个类为抽象类,抽象类必须在类前用abstract关键字修饰。因为抽象类中含有无具体实现的方法,所以不能用抽象类创建对象。
说明:包含抽象方法的类称为抽象类,但并不意味着抽象类中只能有抽象方法,它和普通类一样,同样可以拥有成员变量和普通的成员方法。注意,抽象类和普通类的主要有三点区别:
抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为public。
抽象类不能用来创建对象;
如果一个类继承于一个抽象类,则子类必须实现父类的抽象方法。如果子类没有实现父类的抽象方法,则必须将子类也定义为为abstract类。
接口
接口(Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合。接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。
如果一个类只由抽象方法和全局常量组成,那么这种情况下不会将其定义为一个抽象类,只会定义为一个接口。所以接口严格的来讲属于一个特殊的类,而这个类里面只有抽象方法和全局常量,就连构造方法也没有。
范例:定义一个接口
interface A{//定义一个接口
public static final String MSG = "hello";//全局常量
public abstract void print();//抽象方法
}
class B implements A{
public void print(){...};
}
public class TestDemo {
public static void main(String[] args){
A test = new B();
A.print();
}
}
1.由于接口里面存在抽象方法,所以接口对象不能直接使用关键字new进行实例化。接口的使用原则如下:
2.对于子类而言,除了实现接口外,还可以继承抽象类。若既要继承抽象类,同时还要实现接口的话,使用一下语法格式:
class 子类 [extends 父类] [implemetns 接口1,接口2,...] {}
class X extends C implements A,B {}
3.在Java中,一个抽象类只能继承一个抽象类,但一个接口却可以使用extends关键字同时继承多个接口(但接口不能继承抽象类)。
范例:
interface A{
public void funA();
}
interface B{
public void funB();
}
//C接口同时继承了A和B两个接口
interface C extends A,B{//使用的是extends
public void funC();
}
由此可见,从继承关系来说接口的限制比抽象类少:
(1)一个抽象类只能继承一个抽象父类,而接口可以继承多个接口;
(2)一个子类只能继承一个抽象类,却可以实现多个接口(在Java中,接口的主要功能是解决单继承局限问题)
设计层面上的区别:
(1)抽象类是对一种事物的抽象,即对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。
(2)设计层面不同,抽象类作为很多子类的父类,它是一种模板式设计。而接口是一种行为规范,它是一种辐射式设计。
在Java中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。广泛意义上的内部类一般来说包括这四种:成员内部类、局部内部类、匿名内部类和静态内部类。下面就先来了解一下这四种内部类的用法。
匿名内部类创建线程的两种方式:
public class ThreadDemo {
public static void main(String[] args) {
// 继承thread类实现多线程
new Thread() {
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(Thread.currentThread().getName() + "--"
+ x);
}
}
}.start();
;
// 实现runnable借口,创建多线程并启动
new Thread(new Runnable() {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(Thread.currentThread().getName() + "--"
+ x);
}
}
}) {
}.start();
}
}
当匿名内部类要使用外部定义的变量时,这个变量必须为final.
Static方法可以访问静态方法和静态变量,不可以访问非静态方法和变量;非Static方法可以访问静态方法和变量;
如果某个方法不能按照正常的途径完成任务,就可以通过另一种路径退出方法。在这种情况下会抛出一个封装了错误信息的对象。此时,这个方法会立刻退出同时不返回任何值。另外,调用这个方法的其他代码也无法继续执行,异常处理机制会将代码执行交给异常处理器。
在 Java 应用程序中,异常处理机制为:抛出异常,捕捉异常。throw/throws就是一个甩手掌柜,它只会把异常一层层地往上抛,直到有人去处理它。而try…catch就是那个劳苦工人,负责获取相应的异常并对它进行处理。
finally总会被执行;常见的问题return,在try和catch代码块中有return时,在执行return之前,会先执行finally代码块,然后在return;finally中改变变量的值,不会影响try和catch的值;
反射机制是 Java 语言提供的一种基础功能,赋予程序在运行时自省(introspect,官方用语)的能力。通过反射我们可以直接操作类或者对象,比如获取某个对象的类定义,获取类声明的属性和方法,调用方法或者构造对象,甚至可以运行时修改类定义。
JDK Proxy 是通过实现 InvocationHandler 接口来实现的,代码如下:
interface Animal {
void eat();
}
class Dog implements Animal {
@Override
public void eat() {
System.out.println("The dog is eating");
}
}
class Cat implements Animal {
@Override
public void eat() {
System.out.println("The cat is eating");
}
}
// JDK 代理类
class AnimalProxy implements InvocationHandler {
private Object target; // 代理对象
public Object getInstance(Object target) {
this.target = target;
// 取得代理对象
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("调用前");
Object result = method.invoke(target, args); // 方法调用
System.out.println("调用后");
return result;
}
}
public static void main(String[] args) {
// JDK 动态代理调用
AnimalProxy proxy = new AnimalProxy();
Animal dogProxy = (Animal) proxy.getInstance(new Dog());
dogProxy.eat();
}
定义:注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
把对象转换为字节序列的过程称为对象的序列化。把字节序列恢复为对象的过程称为对象的反序列化。
对象的序列化主要有两种用途:
List是一个有序的队列,每一个元素都有它的索引。第一个元素的索引值是0。List的实现类有LinkedList, ArrayList, Vector, Stack。
Set是一个不允许有重复元素的集合。Set的实现类有HastSet和TreeSet。HashSet依赖于HashMap,它实际上是通过HashMap实现的;TreeSet依赖于TreeMap,它实际上是通过TreeMap实现的。
Map是一个映射接口,即key-value键值对。Map中的每一个元素包含一个key和key对应的value,每一个key是唯一的。
接下来,再看Iterator。它是遍历集合的工具,即我们通常通过Iterator迭代器来遍历集合。我们说Collection依赖于Iterator,是因为Collection的实现类都要实现iterator()函数,返回一个Iterator对象。其中,ListIterator是专门为遍历List而存在的。
再看Enumeration,它是JDK 1.0引入的抽象类。作用和Iterator一样,也是遍历集合;但是Enumeration的功能要比Iterator少。在上面的框图中,Enumeration只能在Hashtable, Vector, Stack中使用。
最后,看Arrays和Collections。它们是操作数组、集合的两个工具类。
Java的List是非常常用的数据类型。List是有序的Collection。Java List一共三个实现类:分别是ArrayList、Vector和LinkedList。
ArrayList是最常用的List实现类,内部是通过数组实现的,它允许对元素进行快速随机访问。数组的缺点是每个元素之间不能有间隔,当数组大小不满足时需要增加存储能力,就要将已经有数组的数据复制到新的存储空间中。当从ArrayList的中间位置插入或者删除元素时,需要对数组进行复制、移动、代价比较高。因此,它适合随机查找和遍历,不适合插入和删除。
Arraylist是如何实现动态扩容的
Vector与ArrayList一样,也是通过数组实现的,不同的是它支持线程的同步,即某一时刻只有一个线程能够写Vector,避免多线程同时写而引起的不一致性,但实现同步需要很高的花费,因此,访问它比访问ArrayList慢。
LinkedList是用链表结构存储数据的,很适合数据的动态插入和删除,随机访问和遍历速度比较慢。另外,他还提供了List接口中没有定义的方法,专门用于操作表头和表尾元素,可以当作堆栈、队列和双向队列使用。
Set注重独一无二的性质,该体系集合用于存储无序(存入和取出的顺序不一定相同)元素,值不能重复。对象的相等性本质是对象hashCode值(java是依据对象的内存地址计算出的此序号)判断的,如果想要让两个不同的对象视为相等的,就必须覆盖Object的hashCode方法和equals方法。要计算set集合中的元素对象是否相等,就必须重写Object的hashcode方法和equals方法。插入元素对象时先比较hashcode方法,在比较equals方法.
说明:
1. equals()相等的两个对象他们的hashCode()肯定相等,也就是用equals()对比是绝对可靠的。
2. hashCode()相等的两个对象他们的equals()不一定相等,也就是hashCode()不是绝对可靠的。
3. 重写了equals方法,必须重写hashcode方法
4. set和map接口存储的的对象一般需要重写euqals方法和hashcode方法
哈希表边存放的是哈希值。HashSet存储元素的顺序并不是按照存入时的顺序(和List显然不同) 而是按照哈希值来存的所以取数据也是按照哈希值取得。元素的哈希值是通过元素的hashcode方法来获取的, HashSet首先判断两个元素的哈希值,如果哈希值一样,接着会比较equals方法 如果 equls结果为true ,HashSet就视为同一个元素。如果equals 为false就不是同一个元素。
哈希值相同equals为false的元素是怎么存储呢,就是在同样的哈希值下顺延(可以认为哈希值相同的元素放在一个哈希桶中)。也就是哈希一样的存一列。
对于LinkedHashSet而言,它继承与HashSet、又基于LinkedHashMap来实现的。LinkedHashSet底层使用LinkedHashMap来保存所有元素,它继承与HashSet,其所有的方法操作上又与HashSet相同,因此LinkedHashSet 的实现上非常简单,只提供了四个构造方法,并通过传递一个标识参数,调用父类的构造器,底层构造一个LinkedHashMap来实现,在相关操作上与父类HashSet的操作相同,直接调用父类HashSet的方法即可。
HashMap根据键的hashCode值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定的。 HashMap最多只允许一条记录的键为null,允许多条记录的值为null。HashMap非线程安全,即任一时刻可以有多个线程同时写HashMap,可能会导致数据的不一致。如果需要满足线程安全,可以用 Collections的synchronizedMap方法使HashMap具有线程安全的能力,或者使用ConcurrentHashMap。
Hashtable是遗留类,很多映射的常用功能与HashMap类似,不同的是它承自Dictionary类,并且是线程安全的,任一时间只有一个线程能写Hashtable,并发性不如ConcurrentHashMap,因为ConcurrentHashMap引入了分段锁。Hashtable不建议在新代码中使用,不需要线程安全的场合可以用HashMap替换,需要线程安全的场合可以用ConcurrentHashMap替换。
TreeMap实现SortedMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用Iterator遍历TreeMap时,得到的记录是排过序的。 如果使用排序的映射,建议使用TreeMap。 在使用TreeMap时,key必须实现Comparable接口或者在构造TreeMap传入自定义的Comparator,否则会在运行时抛出java.lang.ClassCastException类型的异常。
LinkedHashMap是HashMap的一个子类,保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的,也可以在构造时带参数,按照访问次序排序。
ConcurrentHashMap 和 HashMap 思路是差不多的,但是因为它支持并发操作,所以要复杂一些。整个 ConcurrentHashMap 由一个个 Segment 组成,Segment 代表”部分“或”一段“的意思,所以很多地方都会将其描述为分段锁。注意,行文中,我很多地方用了“槽”来代表一个 segment。简单理解就是,ConcurrentHashMap 是一个 Segment 数组,Segment 通过继承 ReentrantLock 来进行加锁,所以每次需要加锁的操作锁住的是一个 segment,这样只要保证每个 Segment 是线程安全的,也就实现了全局的线程安全。
遍历者自己在集合外部维护一个计数器,然后依次读取每一个位置的元素,当读取到最后一个元素后,停止。主要就是需要按元素的位置来读取元素。这也是最原始的集合遍历方法。写法为:
for (int i = 0; i < list.size(); i++) {
list.get(i);
}
Iterator本来是OO的一个设计模式,主要目的就是屏蔽不同数据集合的特点,统一遍历集合的接口。Java作为一个OO语言,自然也在Collections中支持了Iterator模式。写法为:
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
iterator.next();
}
屏蔽了显式声明的Iterator和计数器。优点:代码简洁,不易出错。缺点:只能做简单的遍历,不能在遍历过程中操作(删除、替换)数据集合。写法为:
for (ElementType element : list) {
}
遍历者自己在集合外部维护一个计数器,然后依次读取每一个位置的元素,当读取到最后一个元素后,停止。主要就是需要按元素的位置来读取元素。
每一个具体实现的数据集合,一般都需要提供相应的Iterator。相比于传统for循环,Iterator取缔了显式的遍历计数器。所以基于顺序存储集合的Iterator可以直接按位置访问数据。而基于链式存储集合的Iterator,正常的实现,都是需要保存当前遍历的位置。然后根据当前位置来向前或者向后移动指针。
根据反编译的字节码可以发现,foreach内部也是采用了Iterator的方式实现,只不过Java编译器帮我们生成了这些代码。
1、传统的for循环遍历,基于计数器的:
顺序存储:读取性能比较高。适用于遍历顺序存储集合。
链式存储:时间复杂度太大,不适用于遍历链式存储的集合。
2、迭代器遍历,Iterator:
顺序存储:如果不是太在意时间,推荐选择此方式,毕竟代码更加简洁,也防止了Off-By-One的问题。
链式存储:意义就重大了,平均时间复杂度降为O(n),还是挺诱人的,所以推荐此种遍历方式。
3、foreach循环遍历:
foreach只是让代码更加简洁了,但是他有一些缺点,就是遍历过程中不能操作数据集合(删除等),所以有些场合不使用。而且它本身就是基于Iterator实现的,但是由于类型转换的问题,所以会比直接使用Iterator慢一点,但是还好,时间复杂度都是一样的。所以怎么选择,参考上面两种方式,做一个折中的选择。
import java.lang.String;
import java.util.ArrayList;
import java.util.Iterator;
public class HelloWorld {
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add("Hello");
list.add("world");
Iterator it = list.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
import java.lang.String;
import java.util.Vector;
import java.util.Iterator;
import java.util.List;
public class HelloWorld {
public static void main(String[] args) {
List list = new Vector();
list.add("Hello");
list.add("world");
Iterator it = list.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
}
import java.lang.String;
import java.util.LinkedList;
import java.util.Iterator;
import java.util.List;
public class HelloWorld {
public static void main(String[] args) {
List list = new LinkedList();
list.add("Hello");
list.add("world");
Iterator it = list.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
}
import java.lang.String;
import java.util.Iterator;
import java.util.Set;
import java.util.HashSet;
public class HelloWorld {
public static void main(String[] args) {
Set st = new HashSet();
st.add("Hello");
st.add("world");
Iterator it = st.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
}
import java.lang.String;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;
public class HelloWorld {
public static void main(String[] args) {
Set st = new TreeSet();
st.add(100);
st.add(600);
st.add(400);
st.add(200);
st.add(300);
Iterator it = st.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
}
import java.lang.String;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
public class HelloWorld {
public static void main(String[] args) {
Set st = new LinkedHashSet();
st.add(100);
st.add(600);
st.add(400);
st.add(200);
st.add(300);
Iterator it = st.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
}
import java.lang.String;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class HelloWorld {
public static void main(String[] args) {
Map mmp = new HashMap();
mmp.put("b", "hello");
if(!mmp.containsKey("a")){
mmp.put("a", "world");
}
mmp.put("b", null); //会覆盖之前的值
Iterator it = mmp.keySet().iterator();
while(it.hasNext()){
String key = it.next();
System.out.println(mmp.get(key));
}
}
}
import java.lang.String;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
public class HelloWorld {
public static void main(String[] args) {
Map mmp = new Hashtable();
mmp.put("b", "hello");
mmp.put("a", "world");
Iterator> it = mmp.entrySet().iterator();
while(it.hasNext()){
Map.Entry entry = it.next();
System.out.println(entry.getValue());
}
}
}
import java.lang.String;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
public class HelloWorld {
public static void main(String[] args) {
Map mmp = new TreeMap();
mmp.put(new Integer(10), "hello");
mmp.put(new Integer(2), "world");
mmp.put(new Integer(5), "world");
mmp.put(new Integer(9), "world");
mmp.put(new Integer(3), "world");
for(Entry entry:mmp.entrySet()){
System.out.println(entry.getKey());
}
}
}
addThread类
public class addThread extends Thread{
private Map mmp;
addThread(Map mmp){
this.mmp = mmp;
}
public void run(){
while(true){
Random random = new Random();
mmp.put(random.nextInt(), "Hello world");
}
}
}
removeThread类
public class testThread extends Thread {
private Map mmp;
testThread(Map mmp){
this.mmp = mmp;
}
public void run(){
try {
sleep(1000);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
Iterator> it = mmp.entrySet().iterator();
while(it.hasNext()){
Map.Entry entry = it.next();
System.out.println("remove key:"+entry.getKey());
mmp.remove(entry.getKey());
try {
sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
Main方法
public class HelloWorld {
public static void main(String[] args) {
Map mmp = new ConcurrentHashMap();
Thread threadone = new addThread(mmp);
Thread threadtwo = new testThread(mmp);
threadone.start();
threadtwo.start();
}
}
public class HelloWorld {
public static void main(String[] args) throws InterruptedException, ExecutionException {
//thread类方式创建
new Thread(){
public void run(){
System.out.println("Thread方式创建");
}
}.start();
//Runnable接口
new Thread(new Runnable(){
public void run(){
System.out.println("runnable接口");
}
}).start();
//Callable接口和Future创建
FutureTask ft = new FutureTask(new Callable(){
public Integer call(){
System.out.println("callable接口");
return 1994;
}
});
new Thread(ft).start();
System.out.println("线程的返回值:"+ft.get());
}
}
Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。下面这张图完整描述了线程池的类体系结构。
public class HelloWorld {
public static void main(String[] args) throws InterruptedException,ExecutionException {
ExecutorService pool = Executors.newCachedThreadPool();//创建线程池
FutureTask ft = new FutureTask(new Callable(){
public Integer call(){
System.out.println("callable接口");
return 1994;
}
});
Thread thread = new Thread(ft);//执行线程
pool.execute(thread);
pool.execute(new Thread(){ //执行匿名内部类的线程
public void run(){
System.out.println("Thread方式创建");
}
});
pool.execute(new Thread(new Runnable(){//执行匿名内部类的线程
public void run(){
System.out.println("runnable接口");
}
}));
System.out.println("线程的返回值:"+ft.get());
}
}
多线程Socket通信
当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,它要经过新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)5种状态。尤其是当线程启动以后,它不可能一直"霸占"着CPU独自运行,所以CPU需要在多条线程之间切换,于是线程状态也会多次在运行、阻塞之间切换。
当程序使用new关键字创建了一个线程之后,该线程就处于新建状态,此时仅由JVM为其分配内存,并初始化其成员变量的值
当线程对象调用了start()方法之后,该线程处于就绪状态。Java虚拟机会为其创建方法调用栈和程序计数器,等待调度运行。
如果处于就绪状态的线程获得了CPU,开始执行run()方法的线程执行体,则该线程处于运行状态。
阻塞状态是指线程因为某种原因放弃了cpu 使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得cpu timeslice 转到运行(running)状态。阻塞的情况分三种:
- 等待阻塞(o.wait->等待对列): 运行(running)的线程执行o.wait()方法,JVM会把该线程放入等待队列(waitting queue)中。
- 同步阻塞(lock->锁池) 运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。
- 其他阻塞(sleep/join) 运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。
线程会以下面三种方式结束,结束后就是死亡状态。 正常结束
- run()或call()方法执行完成,线程正常结束。 异常结束
- 线程抛出一个未捕获的Exception或Error。 调用stop
- 直接调用该线程的stop()方法来结束该线程—该方法通常容易导致死锁,不推荐使用。
总结:对象锁和对象锁互斥,类锁和类锁互斥,类锁和对象锁互相独立。对象锁是指加了synchornized修饰的非静态方法和synchornized(this)的代码块;类锁是加了synchornized修饰的静态方法。没有加synchornized修饰的就是没有进行同步的代码块,任何线程可以访问。
public class testSynchornized implements Runnable {
private Lock lock = new ReentrantLock();
private int count = 10;
public void run(){
System.out.println("ThreadName=" + Thread.currentThread().getName());
try {
lock.lock();
for(int i =0;i<3;i++){
count--;
System.out.println(count);
Thread.sleep(500);
}
} catch (Exception e) {
// TODO: handle exception
}finally{
lock.unlock();
}
}
}
Semaphore是一种基于计数的信号量。它可以设定一个阈值,基于此,多个线程竞争获取许可信号,做完自己的申请后归还,超过阈值后,线程申请许可信号将会被阻塞。Semaphore可以用来构建一些对象池,资源池之类的,比如数据库连接池
实现互斥锁(计数器为1)
我们也可以创建计数为1的Semaphore,将其作为一种类似互斥锁的机制,这也叫二元信号量,表示两种互斥状态。
// 创建一个计数阈值为5的信号量对象
// 只能5个线程同时访问
Semaphore semp = new Semaphore(5);
try {
// 申请许可
semp.acquire();
try {
// 业务逻辑
} catch (Exception e) {
} finally {
// 释放许可
semp.release();
}
} catch (InterruptedException e){
}
首先说明,此处AtomicInteger,一个提供原子操作的Integer的类,常见的还有AtomicBoolean、AtomicInteger、AtomicLong、AtomicReference等,他们的实现原理相同,区别在与运算对象类型的不同。令人兴奋地,还可以通过AtomicReference将一个对象的所有操作转化成原子操作。
线程可以进入任何一个它已经拥有的锁所同步着的代码块。ReentrantLock和synchronized都是可重入锁
- 读锁 如果你的代码只读数据,可以很多人同时读,但不能同时写,那就上读锁
- 写锁 如果你的代码修改数据,只能有一个人在写,且不能同时读取,那就上写锁。总之,读的时候上读锁,写的时候上写锁!
- Java中读写锁有个接口java.util.concurrent.locks.ReadWriteLock,也有具体的实现ReentrantReadWriteLock。
锁的状态总共有四种:无锁状态、偏向锁、轻量级锁和重量级锁。
随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁(但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级)。
“轻量级”是相对于使用操作系统互斥量来实现的传统锁而言的。但是,首先需要强调一点的是,轻量级锁并不是用来代替重量级锁的,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用产生的性能消耗。在解释轻量级锁的执行过程之前,先明白一点,轻量级锁所适应的场景是线程交替执行同步块的情况,如果存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀为重量级锁。
Hotspot的作者经过以往的研究发现大多数情况下锁不仅不存在多线程竞争,而且总是由同一线程多次获得。偏向锁的目的是在某个线程获得锁之后,消除这个线程锁重入(CAS)的开销,看起来让这个线程得到了偏护。引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级锁的获取及释放依赖多次CAS原子指令,而偏向锁只需要在置换ThreadID的时候依赖一次CAS原子指令(由于一旦出现多线程竞争的情况就必须撤销偏向锁,所以偏向锁的撤销操作的性能损耗必须小于节省下来的CAS原子指令的性能消耗)。上面说过,轻量级锁是为了在线程交替执行同步块时提高性能,而偏向锁则是在只有一个线程执行同步块时进一步提高性能。
Synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的。但是监视器锁本质又是依赖于底层的操作系统的Mutex Lock来实现的。而操作系统实现线程之间的切换这就需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么Synchronized效率低的原因。因此,这种依赖于操作系统Mutex Lock所实现的锁我们称之为“重量级锁”。JDK中对Synchronized做的种种优化,其核心都是为了减少这种重量级锁的使用。JDK1.6以后,为了减少获得锁和释放锁所带来的性能消耗,提高性能,引入了“轻量级锁”和“偏向锁”。
ThreadLocal,很多地方叫做线程本地变量,也有些地方叫做线程本地存储,ThreadLocal的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。
CAS(Compare-and-Swap),即比较并替换,是一种实现并发算法时常用到的技术,Java并发包中的很多类都使用了CAS技术。但是,CAS存在ABA问题,解决方案是:可以对每个值添加一个版本号来判断,CAS只是一种思想
对于三者的总结:
HashMap是线程不安全的;HashTable是线程安全的concurrentHashMap是线程安全的
concurrentHashMap底层采用分段的数组+链表(红黑树)实现,线程安全;HashTable和HashMap底层采用数组+链表(红黑树)实现
HashMap可以接受为null的键值(key)和值(value),而Hashtable则不行
三者均可以实现线程安全的HashMap,实现方法不同
Vector\Stack\Hashtable\ConcurrentHashMap\enumeration
JVM内存结构::堆、虚拟机栈、方法区、程序计数器和本地方法栈
堆: 对于大多数应用来说,Java堆(Java Heap)是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。
虚拟机栈:Java虚拟机栈(Java Virtual Machine Stacks)是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。 局部变量表存放了编译期可知的各种基本数据类型对象(boolean、byte、char、short、int、float、long、double)、对象引用。
方法区:方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的应该是与Java堆区分开来。
程序计数器:此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
本地方法栈:本地方法栈则是为虚拟机使用到的Native方法服务
线程共享的内存区域:堆和方法区;
线程私有的内存区域:栈和程序计数器;
Java内存模型中规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存(可以与前面将的处理器的高速缓存类比),线程的工作内存中保存了该线程使用到的变量到主内存副本拷贝,线程对变量的所有操作(读取、赋值)都必须在工作内存中进行,而不能直接读写主内存中的变量。
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加
载器去完成,每一个层次的加载器都是如此,因此所有的类加载请求都会传给顶层的启动类加载器,只有当
父加载器反馈自己无法完成该加载请求(该加载器的搜索范围中没有找到对应的类)时,子加载器才会尝试
自己去加载。
- 强引用:在Java中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即使该对象以后永远都不会被用到JVM也不会回收。因此强引用是造成Java内存泄漏的主要原因之一。