对象及变量的并发访问

synchronized同步方法

非线程安全的问题主要存在于实例变量中,如果是方法内部的私有变量,则不存在非线程安全问题。

1. 实例变量非线程安全

如果多个线程共同访问1个对象中的实例变量则有可能出现非线程安全的问题。
demo如下 :

public class HasSelfPrivateNum {	
    private int num = 0;	
    public void addI(String username) {
        try {
            if (username.equals("a")) {
                num = 100;
                System.out.println("a set over!");
                Thread.sleep(2000);
            } else {
                num = 200;
                System.out.println("b set over!");
            }
            System.out.println(username + " num=" + num);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }	
}

public class ThreadA extends Thread {
    private HasSelfPrivateNum numRef;
    public ThreadA(HasSelfPrivateNum numRef) {
        super();
        this.numRef = numRef;
    }
    @Override
    public void run() {
        super.run();
        numRef.addI("a");
    }
}

public class ThreadB extends Thread {
    private HasSelfPrivateNum numRef;
    public ThreadB(HasSelfPrivateNum numRef) {
        super();
        this.numRef = numRef;
    }
    @Override
    public void run() {
        super.run();
        numRef.addI("b");
    }
}

public class Run {
    public static void main(String[] args) {
        HasSelfPrivateNum numRef = new HasSelfPrivateNum();
        ThreadA athread = new ThreadA(numRef);
        athread.start();
        ThreadB bthread = new ThreadB(numRef);
        bthread.start();
    }
}

运行效果:
a set over!
b set over!
b num=200
a num=200

解决方法:只需要在addI() 方法前面加synchronized即可。通过用关键字synchronized声
明的方法一定是排队运行的,只有**共享资源的读写访问**才需要同步化。如果不是,
则根本没有同步的必要。改过之后的运行效果如下:
a set over!
a num=100
b set over!
b num=200

结论:当两个线程访问同一个对象中的同步方法时一定是线程安全的。

2. 多个对象多个锁

public class HasSelfPrivateNum {
    private int num = 0;
    synchronized public void addI(String username) {
        try {
            if (username.equals("a")) {
                num = 100;
                System.out.println("a set over!");
                Thread.sleep(2000);
            } else {
                num = 200;
                System.out.println("b set over!");
            }
            System.out.println(username + " num=" + num);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

public class ThreadA extends Thread {
    private HasSelfPrivateNum numRef;
    public ThreadA(HasSelfPrivateNum numRef) {
        super();
        this.numRef = numRef;
    }
    @Override
    public void run() {
        super.run();
        numRef.addI("a");
    }
}

public class ThreadB extends Thread {
    private HasSelfPrivateNum numRef;
    public ThreadB(HasSelfPrivateNum numRef) {
        super();
        this.numRef = numRef;
    }
    @Override
    public void run() {
        super.run();
        numRef.addI("b");
    }
}

public class Run {
    public static void main(String[] args) {
        HasSelfPrivateNum numRef1 = new HasSelfPrivateNum();
        HasSelfPrivateNum numRef2 = new HasSelfPrivateNum();
        ThreadA athread = new ThreadA(numRef1);
        athread.start();
        ThreadB bthread = new ThreadB(numRef2);
        bthread.start();
    }
}
运行效果:
a set over!
b set over!
b num=200
a num=100

结论:上述两个线程分别访问同一个类的两个不同实例的相同名称的同步方法,效果却是以异步的方式进行的。

关键字synchronized取得的锁都是对象锁,而不是把一段代码或方法当做锁,所以上面的示例中,哪个线程先执行带synchhronized关键字的方法,哪个线程就持有该方法所属对象的锁Lock,那么其他线程只能呈等待状态,前提是多个对象访问的是同一个对象。
如果多个线程访问多个对象,则会创建多个锁。

3. synchronized方法与锁对象

public class MyObject {
    synchronized public void methodA() {
        try {
            System.out.println("begin methodA threadName="
                    + Thread.currentThread().getName());
            Thread.sleep(5000);
            System.out.println("end endTime=" + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public void methodB() {
        try {
            System.out.println("begin methodB threadName="
                    + Thread.currentThread().getName() + " begin time="
                    + System.currentTimeMillis());
            Thread.sleep(5000);
            System.out.println("end");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class ThreadA extends Thread {
    private MyObject object;
    public ThreadA(MyObject object) {
        super();
        this.object = object;
    }
    @Override
    public void run() {
        super.run();
        object.methodA();
    }
}

public class ThreadB extends Thread {
    private MyObject object;
    public ThreadB(MyObject object) {
        super();
        this.object = object;
    }
    @Override
    public void run() {
        super.run();
        object.methodB();
    }
}

public class Run {
    public static void main(String[] args) {
        MyObject object = new MyObject();
        ThreadA a = new ThreadA(object);
        a.setName("A");
        ThreadB b = new ThreadB(object);
        b.setName("B");
        a.start();
        b.start();
    }
}

运行效果
begin methodA threadName=A
begin methodB threadName=B begin time=1521539509210
end endTime=1521539514210
end

给methodB()方法加上synchronized类型的方法。
运行效果如下:
begin methodA threadName=A
end endTime=1521539982244
begin methodB threadName=B begin time=1521539982244
end

结论:

  1. A线程先持有object对象 的Lock锁,B线程可以以异步 的方式调用 object对象 中的非synchronized类型的方法。
  2. A线程先持有object对象的Lock锁,B线程如果在这时调用object对象中的synchronized类型的方法则需等待,也就是同步。

4. 脏读

发生脏读的情况是在读取实例变量时,此值已经被其他线程更改过了。

public class PublicVar {
    public String username = "A";
    public String password = "AA";
    synchronized public void setValue(String username, String password) {
        try {
            this.username = username;
            Thread.sleep(5000);
            this.password = password;
            System.out.println("setValue method thread name="
                    + Thread.currentThread().getName() + " username="
                    + username + " password=" + password);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
     public void getValue() {
        System.out.println("getValue method thread name="
                + Thread.currentThread().getName() + " username=" + username
                + " password=" + password);
    }
}

public class ThreadA extends Thread {
    private PublicVar publicVar;
    public ThreadA(PublicVar publicVar) {
        super();
        this.publicVar = publicVar;
    }
    @Override
    public void run() {
        super.run();
        publicVar.setValue("B", "BB");
    }
}

public class Test {
    public static void main(String[] args) {
        try {
            PublicVar publicVarRef = new PublicVar();
            ThreadA thread = new ThreadA(publicVarRef);
            thread.start();
            Thread.sleep(200);// ��ӡ����ܴ�ֵ��СӰ��
            publicVarRef.getValue();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
运行结果:
getValue method thread name=main username=B password=AA
setValue method thread name=Thread-0 username=B password=BB

出现脏读的原因是getValue()方法并不是同步的。所以可以在任意时刻调用。
解决办法是加上在getValue()加上synchronized关键字。加上后的运行效果如下:

setValue method thread name=Thread-0 username=B password=BB
getValue method thread name=main username=B password=BB

脏读一定会出现在操作实例变量的情况下,这就是不同线程争抢实例变量的结果。

5. synchronized锁重入

在一个synchronized方法/块内部调用本部类的其他synchronized方法/块时,是永远可以得到锁的。

public class MyThread extends Thread {
    @Override
    public void run() {
        Service service = new Service();
        service.service1();
    }
}

public class Service {
    synchronized public void service1() {
        System.out.println("service1");
        service2();
    }
    synchronized public void service2() {
        System.out.println("service2");
        service3();
    }
    synchronized public void service3() {
        System.out.println("service3");
    }
}

public class Run {
    public static void main(String[] args) {
        MyThread t = new MyThread();
        t.start();
    }
}

运行效果:
service1
service2
service3

可重入锁的概念是:自己可以再次获取自己的内部锁,比如有一条线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话就会造成死锁 。

可重入锁也支持在父子类继承的环境中:

public class MyThread extends Thread {
    @Override
    public void run() {
        Sub sub = new Sub();
        sub.operateISubMethod();
    }
}

public class Main {
    public int i = 10;
    synchronized public void operateIMainMethod() {
        try {
            i--;
            System.out.println("main print i=" + i);
            Thread.sleep(100);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

public class Sub extends Main {
    synchronized public void operateISubMethod() {
        try {
            while (i > 0) {
                i--;
                System.out.println("sub print i=" + i);
                Thread.sleep(100);
                this.operateIMainMethod();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class Run {
    public static void main(String[] args) {
        MyThread t = new MyThread();
        t.start();
    }
}

运行效果如下:
sub print i=9
main print i=8
sub print i=7
main print i=6
sub print i=5
main print i=4
sub print i=3
main print i=2
sub print i=1
main print i=0

结论:当存在父子类继承关系时,子类是完全可以可重入锁调用父类的同步方法的。

6. 出现异常,锁自动释放

public class Service {
    synchronized public void testMethod() {
        if (Thread.currentThread().getName().equals("a")) {
            System.out.println("ThreadName=" + Thread.currentThread().getName()
                    + " run beginTime=" + System.currentTimeMillis());
            int i = 1;
            while (i == 1) {
                if (("" + Math.random()).substring(0, 8).equals("0.123456")) {
                    System.out.println("ThreadName="
                            + Thread.currentThread().getName()
                            + " run   exceptionTime="
                            + System.currentTimeMillis());
                    Integer.parseInt("a");
                }
            }
        } else {
            System.out.println("Thread B run Time="
                    + System.currentTimeMillis());
        }
    }
}

public class ThreadA extends Thread {
    private Service service;
    public ThreadA(Service service) {
        super();
        this.service = service;
    }
    @Override
    public void run() {
        service.testMethod();
    }
}

public class ThreadB extends Thread {
    private Service service;
    public ThreadB(Service service) {
        super();
        this.service = service;
    }
    @Override
    public void run() {
        service.testMethod();
    }
}

public class Test {
    public static void main(String[] args) {
        try {
            Service service = new Service();
            ThreadA a = new ThreadA(service);
            a.setName("a");
            a.start();
            Thread.sleep(500);
            ThreadB b = new ThreadB(service);
            b.setName("b");
            b.start();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行效果:
ThreadName=a run beginTime=1521613028384
ThreadName=a run   exceptionTime=1521613033721
Thread B run Time=1521613033722
Exception in thread "a" java.lang.NumberFormatException: For input string: "a"
    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
    at java.lang.Integer.parseInt(Integer.java:580)
    at java.lang.Integer.parseInt(Integer.java:615)
    at service.Service.testMethod(Service.java:15)
    at extthread.ThreadA.run(ThreadA.java:13)

线程a出现异常,线程b进入方法正常打印。结论:出现异步,锁自动释放。

7. 同步不具有继承性

public class Main {
    synchronized public void serviceMethod() {
        try {
            System.out.println("int main 下一步 sleep begin threadName="
                    + Thread.currentThread().getName() + " time="
                    + System.currentTimeMillis());
            Thread.sleep(5000);
            System.out.println("int main 下一步 sleep   end threadName="
                    + Thread.currentThread().getName() + " time="
                    + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class Sub extends Main {
    @Override
    public void serviceMethod() {
        try {
            System.out.println("int sub 下一步 sleep begin threadName="
                    + Thread.currentThread().getName() + " time="
                    + System.currentTimeMillis());
            Thread.sleep(5000);
            System.out.println("int sub 下一步 sleep   end threadName="
                    + Thread.currentThread().getName() + " time="
                    + System.currentTimeMillis());
            super.serviceMethod();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

public class MyThreadA extends Thread {
    private Sub sub;
    public MyThreadA(Sub sub) {
        super();
        this.sub = sub;
    }
    @Override
    public void run() {
        sub.serviceMethod();
    }
}
public class MyThreadB extends Thread {
    private Sub sub;
    public MyThreadB(Sub sub) {
        super();
        this.sub = sub;
    }
    @Override
    public void run() {
        sub.serviceMethod();
    }
}

public class Test {
    public static void main(String[] args) {
        Sub subRef = new Sub();
        MyThreadA a = new MyThreadA(subRef);
        a.setName("A");
        a.start();
        MyThreadB b = new MyThreadB(subRef);
        b.setName("B");
        b.start();
    }
}

运行效果:
int sub 下一步 sleep begin threadName=A time=1521614488673
int sub 下一步 sleep begin threadName=B time=1521614488684
int sub 下一步 sleep   end threadName=A time=1521614493673
int main 下一步 sleep begin threadName=A time=1521614493673
int sub 下一步 sleep   end threadName=B time=1521614493684
int main 下一步 sleep   end threadName=A time=1521614498674
int main 下一步 sleep begin threadName=B time=1521614498674
int main 下一步 sleep   end threadName=B time=1521614503674

在子类Sub serviceMethod方法加上synchronized后运行效果如下:
int sub 下一步 sleep begin threadName=A time=1521614592637
int sub 下一步 sleep   end threadName=A time=1521614597637
int main 下一步 sleep begin threadName=A time=1521614597637
int main 下一步 sleep   end threadName=A time=1521614602638
int sub 下一步 sleep begin threadName=B time=1521614602638
int sub 下一步 sleep   end threadName=B time=1521614607638
int main 下一步 sleep begin threadName=B time=1521614607638
int main 下一步 sleep   end threadName=B time=1521614612638

总结

本节知识点:

  1. 非线程安全的问题主要存在于实例变量中,如果是方法内部的私有变量,则不存在非线程安全问题
  2. 两个线程访问同一个对象中的同步方法时一定是线程安全的
  3. synchronized关键字是对象锁,多个线程,多个对象会创建多个锁。
  4. 对象内synchronized方法和非synchronized方法的调用情况: 当有对象锁的时候,同步调用synchronized方法,异步调用非synchronized方法
  5. 脏读出现的原因是方法不同步造成的。脏读一定会出现在操作实例变量的情况下,这就是不同线程争抢实例变量的结果。
  6. 可重入锁的两种情况
    1. 自己可以获取自己的内部锁
    2. 也支持出现在父子类继承的关系中,子类可以通过可重入锁调用父类的同步方法
  7. 当出现异常时,锁自动释放
  8. 同步不能继承:当父类在synchronized service()方法时,子类有覆盖service()方法时,子类方法并不能同步,必须要在子类加上synchronized方法才能同步。

计算机书籍

在高中时代凭着一腔热血,喜爱计算机,就学了计算机这一专业,到了之后发现并不是那么好,然尔事已至此,也不完全是坏,钻研技术,享受解决问题的快乐,剖析深处的秘密,还是挺好的。

Thinking in java

可以说是java方面的圣经书籍了。在学校学java的时候就买了这本书,当时是看不懂,翻了翻,至今也没看完,如今这本书早就丢了,也不想再买了。适合于中高级的java阅读,不适合初级,

看了30% 吸收10%

JavaScript权威指南

世人都说这本是javascript的经典之作,当时也在写js,所以就买了这本书看,自我感觉这本书一般,语言晦涩难懂,每一小节分的挺细致,理论知识也挺多的,但不符合我的风格,语言习惯

看了70% 吸收30%

JavaScript高级程序设计

js的书就买了这两本,

深入理解java虚拟机

Spring 实战

多线程编程

算法

一. Java内存区域与内存溢出异常

一,数据区域

java虚拟机在执行java程序的过程中会把它所管理 的内存分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在。有些区域则依赖用户线程的启动和结束而建立和销毁。

1. 程序计数器

一块较小的内存空间。可以理解为行号指示器,字节码解释器工作就是通过这个计数器的值来决定下一条执行的字节码指令,如分支,循环,跳转,异常处理,线程恢复等

由于java虚拟机的多线程是通过线程输液切换并分配处理器执行时间的方式来实现的,为了保证线程在切换的时候能恢复到正确的执行位置,每条线程都需要有一个独立 的程序计数器,各条线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存

2. java 虚拟机栈

java虚拟机栈也是线程私有的。生命周期和线程相同。虚拟机栈描述 的是java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表操作娄栈动态链接方法出口等。调用到执行的过程就是入栈到出栈的过程。

局部变量存放了各种数据类型(boolean,byte,char,short,int,float,long,double),对象引用(reference类型)

其中64位长度的long 和double类型会占用2个局部变量空间,其他的占一个。当进入一个方法时,在帧中分配的局部变量空间是完全确定的。在方法运行期间不会改变局部变量表的大小。

这个区域规定了两种情况:

  • 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常
  • 如果虚拟机栈可以动态扩展。如果扩展时无法申请到足够的内存将会抛出OutOfMemoryError异常

3. 本地方法栈

与虚拟机栈相似,区别是虚拟栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Naive方法服务。

与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常

4. java堆

是虚拟机所管理的内存中最大的一块内存。被所有线程共享,在虚拟机创建启动。目的是存放对象实例,几乎所有的对象实例都在这里分配内存。

java堆也是垃圾收集器管理的主要区域,也被称为 GC 堆,从内存回收的角度来看,由于现在收集器基本都采用分代收集算法,所以java堆还可细分为:新生代和老年代,再细致一点的有Eden空间,From Survivor空间,ToSurvivor空间等。从内存分配还可划分出多个线程私有的分配缓冲区

java堆可以处于物理上不连续的内存空间,只要逻辑上是连续的即可,

当前主流的虚拟机都是按照可扩展来实现的,如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常

5. 方法区

与java堆一样,线程共享的区域。用于存储已被虚拟机加载的类信息常量静态变量即时编译器编译后的代码等数据。

很多人习惯把方法区称为”永久代”,两者本质上并不等价。

当方法区无法满足内存分配的需求时,将抛出OutOfMemoryError异常

6. 运行时常量池

运行时常量池是方法区的一部分。Class文件中除了有类的版本,字段,方法接口等描述信息外,还有常量池,用于存放编译期生成的各种字面量和符号引用。java虚拟机对Class文件的每一部分都严格规定,但对于运行时常量池,没有做任何细节的要求。

运行时常量池是方法区的一部分,自然也受到方法区内存的限制,当常量池无法申请到内存时会抛出OutMemoryError异常

7. 直接内存

二. java集合源码解析

一、ArrayList数据结构

分析一个类的时候,数据结构往往是它的灵魂所在,理解底层的数据结构其实就理解了该类的实现思路,具体的实现细节再具体分析。

  ArrayList的数据结构如下:

 1 2 3 4 5 6 7 8 
| | | | | | | | |

说明:底层的数据结构就是数组,数组元素类型为Object类型,即可以存放所有类型数据。我们对ArrayList类的实例的所有的操作底层都是基于数组的。下面我们来分析通过数组是如何保证库函数的正确实现的。

二、ArrayList源码分析

2.1 类的继承关系

public class ArrayList<E> extends AbstractList<E> 
implements List<E>, RandomAccess, Cloneable, java.io.Serializable

说明:ArrayList继承AbstractList抽象父类,实现了List接口(规定了List的操作规范)、RandomAccess(可随机访问)、Cloneable(可拷贝)、Serializable(可序列化)。

2.2 类的属性

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    // 版本号
    private static final long serialVersionUID = 8683452581122892189L;
    // 缺省容量
    private static final int DEFAULT_CAPACITY = 10;
    // 空对象数组
    private static final Object[] EMPTY_ELEMENTDATA = {};
    // 缺省空对象数组
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    // 元素数组
    transient Object[] elementData;
    // 实际元素大小,默认为0
    private int size;
    // 最大数组容量
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
}

说明:类的属性中核心的属性为elementData,类型为Object[],用于存放实际元素,并且被标记为transient,也就意味着在序列化的时候,此字段是不会被序列化的。

2.3 类的构造函数

ArrayList(int)型构造函数

public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) { // 初始容量大于0
        this.elementData = new Object[initialCapacity]; // 初始化元素数组
    } else if (initialCapacity == 0) { // 初始容量为0
        this.elementData = EMPTY_ELEMENTDATA; // 为空对象数组
    } else { // 初始容量小于0,抛出异常
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}

说明:指定elementData数组的大小,不允许初始化大小小于0,否则抛出异常。

ArrayList()型构造函数

public ArrayList() { 
    // 无参构造函数,设置元素数组为空 
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

说明:当未指定初始化大小时,会给elementData赋值为空集合。

ArrayList(Collection<? extends E>)型构造函数

public ArrayList(Collection<? extends E> c) { // 集合参数构造函数
    elementData = c.toArray(); // 转化为数组
    if ((size = elementData.length) != 0) { // 参数为非空集合
        if (elementData.getClass() != Object[].class) // 是否成功转化为Object类型数组
            elementData = Arrays.copyOf(elementData, size, Object[].class); // 不为Object数组的话就进行复制
    } else { // 集合大小为空,则设置元素数组为空
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

说明:当传递的参数为集合类型时,会把集合类型转化为数组类型,并赋值给elementData。

2.4 核心函数分析

add函数

 public boolean add(E e) { // 添加元素
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

说明:在add函数我们发现还有其他的函数ensureCapacityInternal,此函数可以理解为确保elementData数组有合适的大小。ensureCapacityInternal的具体函数如下

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { // 判断元素数组是否为空数组
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); // 取较大值
    }
   
    ensureExplicitCapacity(minCapacity);
}

说明:在ensureCapacityInternal函数中我们又发现了ensureExplicitCapacity函数,这个函数也是为了确保elemenData数组有合适的大小。ensureExplicitCapacity的具体函数如下

private void ensureExplicitCapacity(int minCapacity) {
    // 结构性修改加1
    modCount++;
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

说明:在ensureExplicitCapacity函数我们又发现了grow函数,grow函数才会对数组进行扩容,ensureCapacityInternal、ensureExplicitCapacity都只是过程,最后完成实际扩容操作还是得看grow函数,grow函数的具体函数如下 

private void grow(int minCapacity) {
    int oldCapacity = elementData.length; // 旧容量
    int newCapacity = oldCapacity + (oldCapacity >> 1); // 新容量为旧容量的1.5倍
    if (newCapacity - minCapacity < 0) // 新容量小于参数指定容量,修改新容量
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0) // 新容量大于最大容量
        newCapacity = hugeCapacity(minCapacity); // 指定新容量
    // 拷贝扩容
    elementData = Arrays.copyOf(elementData, newCapacity);
}

说明:正常情况下会扩容1.5倍,特殊情况下(新扩展数组大小已经达到了最大值)则只取最大值。

一. java集合

https://www.cnblogs.com/leeplogs/p/5891861.html这个总结的还是挺好的,拿来用下下。

一. 集合与数组

  • 数组(可以存储基本数据类型)是用来存现对象的一种容器,但是数组的长度固定,不适合在对象数量未知的情况下使用。
  • 集合(只能存储对象,对象类型可以不一样)的长度可变,可在多数情况下使用。

二. 层次关系

如图所示: 图中,实线边框的是实现类,折线边框的是抽象类,而点线边框的是接口

Collection接口是集合类的根接口,Java中没有提供这个接口的直接的实现类。但是却让其被继承产生了两个接口,就是Set和List。Set中不能包含重复的元素List是一个有序的集合,可以包含重复的元素,提供了按索引访问的方式

Map是Java.util包中的另一个接口,它和Collection接口没有关系,是相互独立的,但是都属于集合类的一部分。Map包含了key-value对。Map不能包含重复的key,但是可以包含相同的value。

Iterator,所有的集合类,都实现了Iterator接口,这是一个用于遍历集合中元素的接口,主要包含以下三种方法:

  1. hasNext()是否还有下一个元素。
  2. next()返回下一个元素。
  3. remove()删除当前元素。

三. 几种重要的接口和类简介

1、List(有序、可重复)

List里存放的对象是有序的,同时也是可以重复的,List关注的是索引,拥有一系列和索引相关的方法,查询速度快。因为往list集合里插入或删除数据时,会伴随着后面数据的移动,所有插入删除数据速度慢。

2、Set(无序、不能重复)

Set里存放的对象是无序,不能重复的,集合中的对象不按特定的方式排序,只是简单地把对象加入集合中。TreeSet有序(二叉树排序)

3、Map(键值对、键唯一、值不唯一)

Map集合中存储的是键值对,键不能重复,值可以重复。根据键得到值,对map集合遍历时先得到键的set集合,对set集合进行遍历,得到相应的值。TreeMap有序(二叉排序树)

四. 遍历

在类集中提供了以下四种的常见输出方式:

1)Iterator:迭代输出,是使用最多的输出方式。

2)ListIterator:是Iterator的子接口,专门用于输出List中的内容。

3)foreach输出:JDK1.5之后提供的新功能,可以输出数组或集合。

4)for循环

代码示例如下:

for的形式:for(int i=0;i<arr.size();i++){…}

foreach的形式: for(int i:arr){…}

iterator的形式:
Iterator it = arr.iterator();
while(it.hasNext()){ object o =it.next(); …}

五. ArrayList和LinkedList

ArrayList和LinkedList在用法上没有区别,但是在功能上还是有区别的。LinkedList经常用在增删操作较多而查询操作很少的情况下,ArrayList则相反。

六. Map集合

实现类:HashMap、Hashtable、LinkedHashMap和TreeMap

HashMap

HashMap是最常用的Map,它根据键的HashCode值存储数据,根据键可以直接获取它的值,具有很快的访问速度,遍历时,取得数据的顺序是完全随机的。因为键对象不可以重复,所以HashMap最多只允许一条记录的键为Null,允许多条记录的值为Null,是非同步的

Hashtable

Hashtable与HashMap类似,是HashMap的线程安全版,它支持线程的同步,即任一时刻只有一个线程能写Hashtable,因此也导致了Hashtale在写入时会比较慢,它继承自Dictionary类,不同的是它不允许记录的键或者值为null,同时效率较低。

ConcurrentHashMap

线程安全,并且锁分离。ConcurrentHashMap内部使用段(Segment)来表示这些不同的部分,每个段其实就是一个小的hash table,它们有自己的锁。只要多个修改操作发生在不同的段上,它们就可以并发进行。

LinkedHashMap

LinkedHashMap保存了记录的插入顺序,在用Iteraor遍历LinkedHashMap时,先得到的记录肯定是先插入的,在遍历的时候会比HashMap慢,有HashMap的全部特性。

TreeMap

TreeMap实现SortMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序(自然顺序),也可以指定排序的比较器,当用Iterator遍历TreeMap时,得到的记录是排过序的。不允许key值为空,非同步的;

map的遍历

第一种:KeySet()
将Map中所有的键存入到set集合中。因为set具备迭代器。所有可以迭代方式取出所有的键,再根据get方法。获取每一个键对应的值。
keySet():迭代后只能通过get()取key 。取到的结果会乱序,是因为取得数据行主键的时候,使用了HashMap.keySet()方法,而这个方法返回的Set结果,里面的数据是乱序排放的。
典型用法如下:

Map map = new HashMap();
map.put("key1","lisi1");
map.put("key2","lisi2");
map.put("key3","lisi3");
map.put("key4","lisi4");  
//先获取map集合的所有键的set集合,keyset()
Iterator it = map.keySet().iterator();
 //获取迭代器
while(it.hasNext()){
Object key = it.next();
System.out.println(map.get(key));
}

第二种:entrySet()

Set<Map.Entry<K,V>> entrySet() //返回此映射中包含的映射关系的 Set 视图。(一个关系就是一个键-值对),就是把(key-value)作为一个整体一对一对地存放到Set集合当中的。Map.Entry表示映射关系。entrySet():迭代后可以e.getKey(),e.getValue()两种方法来取key和value。返回的是Entry接口。
典型用法如下:

Map map = new HashMap();
map.put("key1","lisi1");
map.put("key2","lisi2");
map.put("key3","lisi3");
map.put("key4","lisi4");
//将map集合中的映射关系取出,存入到set集合
Iterator it = map.entrySet().iterator();
while(it.hasNext()){
Entry e =(Entry) it.next();
System.out.println("键"+e.getKey () + "的值为" + e.getValue());
}

推荐使用第二种方式,即entrySet()方法,效率较高。
对于keySet其实是遍历了2次,一次是转为iterator,一次就是从HashMap中取出key所对于的value。而entryset只是遍历了第一次,它把key和value都放到了entry中,所以快了。两种遍历的遍历时间相差还是很明显的。

七. 主要实现类区别小结

Vector和ArrayList

  1. vector是线程同步的,所以它也是线程安全的,而arraylist是线程异步的,是不安全的。如果不考虑到线程的安全因素,一般用arraylist效率比较高。
  2. 如果集合中的元素的数目大于目前集合数组的长度时,vector增长率为目前数组长度的100%,而arraylist增长率为目前数组长度的50%。如果在集合中使用数据量比较大的数据,用vector有一定的优势。
  3. 如果查找一个指定位置的数据,vector和arraylist使用的时间是相同的,如果频繁的访问数据,这个时候使用vector和arraylist都可以。而如果移动一个指定位置会导致后面的元素都发生移动,这个时候就应该考虑到使用linklist,因为它移动一个指定位置的数据时其它元素不移动。
    ArrayList 和Vector是采用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,都允许直接序号索引元素,但是插入数据要涉及到数组元素移动等内存操作,所以索引数据快,插入数据慢,Vector由于使用了synchronized方法(线程安全)所以性能上比ArrayList要差,LinkedList使用双向链表实现存储,按序号索引数据需要进行向前或向后遍历,但是插入数据时只需要记录本项的前后项即可,所以插入数度较快。

arraylist和linkedlist

  1. ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
  2. 对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。
  3. 对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。 这一点要看实际情况的。若只对单条数据插入或删除,ArrayList的速度反而优于LinkedList。但若是批量随机的插入删除数据,LinkedList的速度大大优于ArrayList. 因为ArrayList每插入一条数据,要移动插入点及之后的所有数据。

HashMap与TreeMap

  1. HashMap通过hashcode对其内容进行快速查找,而TreeMap中所有的元素都保持着某种固定的顺序,如果你需要得到一个有序的结果你就应该使用TreeMap(HashMap中元素的排列顺序是不固定的)。
  2. 在Map 中插入、删除和定位元素,HashMap是最好的选择。但如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。使用HashMap要求添加的键类明确定义了hashCode()和 equals()的实现。
    两个map中的元素一样,但顺序不一样,导致hashCode()不一样。
    同样做测试:
    在HashMap中,同样的值的map,顺序不同,equals时,false;
    而在treeMap中,同样的值的map,顺序不同,equals时,true,说明,treeMap在equals()时是整理了顺序了的。

HashTable与HashMap

  1. 同步性:Hashtable是线程安全的,也就是说是同步的,而HashMap是线程序不安全的,不是同步的。
  2. HashMap允许存在一个为null的key,多个为null的value 。
  3. hashtable的key和value都不允许为null。

spring REST之 RestTemplate的使用

一. 基础知识

REST就是将资源的状态以最适合客户端或服务端的形式从服务器端转移到客户端(或者反过来)

在REST中是通过URL进行识别一定位。是通过HTTP方法来定义。也就是GET,POST,PUT,DELETE,PATCH等。这些HTTP方法通常会匹配如下的CRUD操作。

  • Create:POST
  • Read: GET
  • Update: PUT或PATCH
  • Delete: Delete

二. 基本使用

在RestTemplate提供了对应于每个的六个主要的HTTP方法

表1. RestTemplate方法的概述

RestTemplate定义了36个与REST资源交互的方法,其中的大多数都对应于HTTP的方法。
其实,这里面只有11个独立的方法,其中有十个有三种重载形式,而第十一个则重载了六次,这样一共形成了36个方法

  • delete() 在特定的URL上对资源执行HTTP DELETE操作
  • exchange() 在URL上执行特定的HTTP方法,返回包含对象的ResponseEntity,这个对象是从响应体中映射得到的
  • execute() 在URL上执行特定的HTTP方法,返回一个从响应体映射得到的对象
  • getForEntity() 发送一个HTTP GET请求,返回的ResponseEntity包含了响应体所映射成的对象
  • getForObject() 发送一个HTTP GET请求,返回的请求体将映射为一个对象
  • postForEntity() POST 数据到一个URL,返回包含一个对象的ResponseEntity,这个对象是从响应体中映射得到的
  • postForObject() POST 数据到一个URL,返回根据响应体匹配形成的对象
  • headForHeaders() 发送HTTP HEAD请求,返回包含特定资源URL的HTTP头
  • optionsForAllow() 发送HTTP OPTIONS请求,返回对特定URL的Allow头信息
  • postForLocation() POST 数据到一个URL,返回新创建资源的URL
  • put() PUT 资源到特定的URL

Rest服务,模拟提供Rest数据

@RestController
public class DataController {
    @RequestMapping(value = "getAll")
    public List<UserEntity> getUser() {
        List<UserEntity> list = new ArrayList<>();
        list.add(new UserEntity("zhangsan","123456","man"));
        list.add(new UserEntity("lisi","321654","woman"));
        list.add(new UserEntity("wangfer","741258","man"));
        return list;
    }

    @RequestMapping("get/{id}")
    public UserEntity getById(@PathVariable(name = "id") String id) {
        UserEntity userEntity = new UserEntity("zhangsan", "34524", "man");
        return userEntity;
    }

    @RequestMapping(value = "save")
    public String save(UserEntity userEntity) {
        return userEntity.toString()+"保存成功";
    }

    @RequestMapping(value = "saveByType/{type}")
    public String saveByType(UserEntity userEntity,@PathVariable("type")String type) {
        return "保存成功,type="+type;
    }
}

1. GET请求

(1)getForEntity使用方法

无参数的getForEntity

@RequestMapping("getForEntity")
public List<UserEntity> getAll2() {
    ResponseEntity<List> responseEntity = restTemplate.getForEntity("http://localhost:8080/getAll", List.class);
    HttpHeaders headers = responseEntity.getHeaders();
    HttpStatus statusCode = responseEntity.getStatusCode();
    int code = statusCode.value();
    List<UserEntity> list = responseEntity.getBody();
    System.out.println(list.toString());
    return list;

}

有参数的 getForEntity 请求,参数列表 如下才是参数的正确使用方式,曾在这踩了一个坑,浪费了好长时间,

@RequestMapping("getForEntity/{id}")
public UserEntity getById2(@PathVariable(name = "id") String id) {
    String url = "http://localhost:8080/get/{id}?username={username}&password={password}&sex={sex}";
    Map<String,String> map = new HashMap<>();
    map.put("id","11");
    map.put("username","lisi");
    map.put("password","12345");
    map.put("sex","man");
    ResponseEntity<UserEntity> responseEntity = restTemplate.getForEntity(url, UserEntity.class, map );
    UserEntity userEntity = responseEntity.getBody();
    return userEntity;
}

但是,通常情况下我们并不想要Http请求的全部信息,只需要相应体即可.对于这种情况,RestTemplate提供了 getForObject() 方法用来只获取 响应体信息.
getForObject 和 getForEntity 用法几乎相同,指示返回值返回的是 响应体,省去了我们 再去 getBody()

(2)getForObject使用方法

无参数的getForEntity

@RequestMapping("getAll2")
public List<UserEntity> getAll() {
    List<UserEntity> list = restTemplate.getForObject("http://localhost:8080/getAll", List.class);
    System.out.println(list.toString());
    return list;
}

有参数的 get 请求,使用map封装请求参数

@RequestMapping("get3/{id}")
public UserEntity getById3(@PathVariable(name = "id") String id) {
    HashMap<String, String> map = new HashMap<>();
    map.put("id",id);
    UserEntity userEntity = restTemplate.getForObject("http://localhost:8080/get/{id}", UserEntity.class, map);
    return userEntity;
}

2. Post请求

post 请求,提交 UserEntity 对像

@RequestMapping("saveUser")
public String save(UserEntity userEntity) {
    ResponseEntity<String> responseEntity = restTemplate.postForEntity("http://localhost:8080/save", userEntity, String.class);
    String body = responseEntity.getBody();
    return body;
}

有参数的 postForEntity 请求,使用map封装

@RequestMapping("saveUserByType2/{type}")
public String save3(UserEntity userEntity,@PathVariable("type")String type) {
    HashMap<String, String> map = new HashMap<>();
    map.put("type", type);
    ResponseEntity<String> responseEntity = restTemplate.postForEntity("http://localhost:8080/saveByType/{type}", userEntity, String.class,map);
    String body = responseEntity.getBody();
    return body;
}

以上get 和post 两个 请求基本上可以满足我们的大部分需求,如果不满足可以使用 **exchange()**或者 execute() 来实现,通过看源代码,可以发现,getForEntity() 和postForEntity()是对这两个方法的封装.

三. 手动指定转换器(HttpMessageConverter)

我们知道 ,reseful接口传递的数据内容和响应都是json格式的字符串.而postForObject方法请求和返回的参数都是java类,是RestTemplate通过HttpMessageConverter帮我们做了转换的操作.

通过查看restTemplate实例对象,

默认情况下RestTemplate自动帮我们注册了一组HttpMessageConverter用来处理一些不同的contextType的请求.如:

  • StringHttpMessageConverter来处理text/plain;
  • MappingJackson2HttpMessageConverter来处理application/json;
  • Jaxb2RootElementHttpMessageConverter来处理application/xml,text/xml。
  • AllEncompassingFormHttpMessageConverter来处理application/x-www-form-urlencoded。

这些可满足大部分的需求,如果这些都不能满足.你可以实现org.springframework.http.converter.HttpMessageConverter接口自己写一个。详情参考官方api。

HTTP解析

一直对请求响应不太明白,做下总结,分析。原理机制先不说了,先对用法,注意事项做下梳理

一. HTTP 无状态性

HTTP 协议是无状态的(stateless)。也就是说,同一个客户端第二次访问同一个服务器上的页面时,服务器无法知道这个客户端曾经访问过,服务器也无法分辨不同的客户端。HTTP 的无状态特性简化了服务器的设计,使服务器更容易支持大量并发的HTTP 请求。

二. HTTP 持久连接

HTTP1.0 使用的是非持久连接,主要缺点是客户端必须为每一个待请求的对象建立并维护一个新的连接,即每请求一个文档就要有两倍RTT 的开销。因为同一个页面可能存在多个对象,所以非持久连接可能使一个页面的下载变得十分缓慢,而且这种短连接增加了网络传输的负担。HTTP1.1 使用持久连接keepalive,所谓持久连接,就是服务器在发送响应后仍然在一段时间内保持这条连接,允许在同一个连接中存在多次数据请求和响应,即在持久连接情况下,服务器在发送完响应后并不关闭TCP 连接,而客户端可以通过这个连接继续请求其他对象。

1. HTTP/1.1 协议的持久连接有两种方式:

  1. 非流水线方式:客户在收到前一个响应后才能发出下一个请求;
  2. 流水线方式:客户在收到 HTTP 的响应报文之前就能接着发送新的请求报文;

三. 常见的HTTP请求头

  • Accept-Charset 用于指定客户端接受的字符集
  • Accept-Encoding 用于指定可接受的内容编码
  • Accept-Language 用于指定一种自然语言,
  • Host 用户指定被请求资源的Internet主机和端口号
  • User-Agent 客户端将它的操作系统,浏览器和其他属性告诉服务器
  • Connection 当前连接是否保持

四. 常见的HTTP响应头

  • Server 使用的服务器名称
  • Content-Type 用来指明发送给接收者的实体正文的媒体类型。如Content-Type:text/html:charset=GBK
  • Content-Encoding 与请求报头Accept-Encoding对应,告诉浏览器服务端采用的是什么压缩编码
  • Content-Language 描述了资源所有的自然语言,与Accent-Language 相对应
  • Content-Length 指明实体正文的长度,用以字节方式存储的十进制数字来表示
  • Keep-Alive 保持连接的时间

五. 常见的HTTP状态码

  • 200 请求已成功,请求所希望的响应头或数据体将随此响应返回。
  • 302 请求的资源现在临时从不同的 URI 响应请求。由于这样的重定向是临时的,客户端应当继续向原有地址发送以后的请求。
  • 400 1、语义有误,当前请求无法被服务器理解。除非进行修改,否则客户端不应该重复提交这个请求。   2、请求参数有误。
  • 403 服务器已经理解请求,但是拒绝执行它
  • 404 请求失败,请求所希望得到的资源未被在服务器上发现。
  • 500 服务器遇到了一个未曾预料的状况,导致了它无法完成对请求的处理。一般来说,这个问题都会在服务器的程序码出错时出现

六. HTTP请求信息由3部分组成

  1. 请求方法(GET/POST)、URI、协议/版本
  2. 请求头(Request Header)
  3. 请求正文

以上图做例进行分析:

POST http://xg.mediportal.com.cn/health/sms/verify/telephone HTTP/1.1

User-Agent: DGroupPatient/1.052701.230/Dalvik/2.1.0 (Linux; U; Android 5.1.1; KIW-AL10 Build/HONORKIW-AL10)
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Host: xg.mediportal.com.cn
Connection: Keep-Alive
Accept-Encoding: gzip
Content-Length: 33

username=zhangsan&password=admin

1.请求方法、URI、协议/版本

请求的第一行是“方法、URL、协议/版本”
根据HTTP标准,HTTP请求可以使用多种请求方法。
例如:HTTP1.1目前支持7种请求方法:GET、POST、HEAD、OPTIONS、PUT、DELETE和TARCE。

  • GET 请求获取由Request-URI所标识的资源
  • POST 在Request-URI所标识的资源后附加新的数据
  • HEAD 请求获取由Request-URI所标识的资源的响应消息报头
  • OPTIONS 请求查询服务器的性能,或查询与资源相关的选项和需求
  • PUT 请求服务器存储一个资源,并用Request-URI作为其标识
  • DELETE 请求服务器删除由Request-URI 所标识的资源
  • TRACE 请求服务器回送收到的请求信息,主要用语测试或诊断

2.请求头

  • Content-Type 内容类型,专业术语叫“媒体类型”,即MediaType,也叫MIME类型,用来指明报文主体部分内容属于何种类型。

但是content-type一般只存在于Post方法中,因为Get方法是不含“body”的,它的请求参数都会被编码到url后面,所以在Get方法中加Content-type是无用的。

常见的MIME类型如下:

  • text/html : HTML格式
  • text/plain :纯文本格式
  • text/xml : XML格式
  • image/gif :gif图片格式
  • image/jpeg :jpg图片格式
  • image/png:png图片格式

以application开头的媒体格式类型:

  • application/xhtml+xml :XHTML格式
  • application/xml : XML数据格式
  • application/atom+xml :Atom XML聚合格式
  • application/json : JSON数据格式
  • application/pdf :pdf格式
  • application/msword : Word文档格式
  • application/octet-stream : 二进制流数据(如常见的文件下载)
  • application/x-www-form-urlencoded :
    中默认的encType,form表单数据被编码为key/value格式发送到服务器(表单默认的提交数据的格式)

另外一种常见的媒体格式是上传文件之时使用的:

  • multipart/form-data : 需要在表单中进行文件上传时,就需要使用该格式

3. 请求正文

请求头和请求正文之间是一个空行,这个行非常重要,它表示请求头已经结束,接下来的是请求正文。请求正文中可以包含客户提交的查询字符串信息:

telephone=15527177736&userType=1&

七. HTTP响应格式

HTTP应答与HTTP请求相似,HTTP响应也由3个部分构成,分别是:

  1. 状态行

  2. 响应头(Response Header)

  3. 响应正文

    HTTP/1.1 200 OK //状态行
    Server: nginx
    Date: Tue, 31 May 2016 02:09:24 GMT
    Content-Type: application/json;charset=UTF-8
    Connection: keep-alive
    Vary: Accept-Encoding
    Access-Control-Allow-Origin: *
    Access-Control-Allow-Headers: X-Requested-With,access_token,access-token,content-type,multipart/form-data,application/x-www-form-urlencoded
    Access-Control-Allow-Methods: GET,POST,OPTIONS
    Content-Length: 49

    {“resultCode”:1,”resultMsg”:”手机号未注册”} //正文

1.状态行

协议版本、数字形式的状态代码、及相应的状态描述,各元素之间以空格分隔。

  • 状态描述: 状态描述给出了关于状态代码的简短的文字描述。
  • 状态代码: 状态代码由3位数字组成,表示请求是否被理解或被满足。

状态代码的第一个数字定义了响应的类别,后面两位没有具体的分类。
第一个数字有五种可能的取值:

  • 1xx: 指示信息—表示请求已接收,继续处理。
  • 2xx: 成功—表示请求已经被成功接收、理解、接受。
  • 3xx: 重定向—要完成请求必须进行更进一步的操作。
  • 4xx: 客户端错误—请求有语法错误或请求无法实现。
  • 5xx: 服务器端错误—服务器未能实现合法的请求。

状态代码 状态描述 说明

  • 200 OK 客户端请求成功
  • 400 Bad Request 由于客户端请求有语法错误,不能被服务器所理解。
  • 401 Unauthonzed 请求未经授权。这个状态代码必须和WWW-Authenticate报头域一起使用
  • 403 Forbidden 服务器收到请求,但是拒绝提供服务。服务器通常会在响应正文中给出不提供服务的原因
  • 404 Not Found 请求的资源不存在,例如,输入了错误的URL。
  • 500 Internal Server Error 服务器发生不可预期的错误,导致无法完成客户端的请求。
  • 503 Service Unavailable 服务器当前不能够处理客户端的请求,在一段时间之后,服务器可能会恢复正常

2.响应头

响应头可能包括:

  • Content-Type :指明发送给接收者的实体正文的媒体类型。如:Content-Type: text/html;charset=ISO-8859-1Content-Type: text/html;charset=GB2312
  • Content-Language:源所用的自然语言
  • Content-Length:指明正文的长度,

多线程编程技术

一,多线程基础及概念

1. 线程和进程

可以将运行在内存中的exe文件理解成进程,进程是受操作系统管理的基本运行单元。
线程可以理解成是在进程中独立运行的子任务。

单任务特点是排队执行,也就是同步。缺点是cpu利用率大幅降低。

一个进程在运行时至少会有一个线程在运行。

2. 线程实现的两种方式

一种是继承Thread类,另一种是实现Runnable接口。他们之间具有多态关系。使用继承Thread时,最大的局限是不支持多继承,为了支持多继承,可以实现Runnable接口。

2.1 继承Thread类

public class MyThread extends Thread{
    @override
    public void run(){
        super.run();
        System.out.println("myThread");
    }
}

运行代码如下:

public class Run{
    public static void main(String[] args){
        MyThread mythread = new MyThread();
        mythread.start();
        System.out.println("运行结束");
    }
}

代码执行顺序和运行顺序是无关的。

2.2 实现Runnable接口

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("运行中");
    }
}

运行代码:

public class Main {
    public static void main(String[] args) throws InterruptedException {
       Runnable runnable = new MyRunnable();
       Thread thread  = new Thread(runnable);
       thread.start();
        System.out.println("运行结束");
    }
}

3. 实例变量与线程安全

线程针对变量有共享和不共享之分,这在交互时是很重要的一个技术点。

3.1 不共享数据的情况

代码如下
public class MyThread extends Thread {
    private int count=5;
    public MyThread(String name){
        super();
        this.setName(name);
    }
    @Override
    public void run() {
        super.run();
        while (count>0){
            count--;
            System.out.println("由"+this.currentThread().getName()+" 计算,count="+count);
        }
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
       MyThread a = new MyThread("a");
       MyThread b = new MyThread("b");
       MyThread c = new MyThread("c");
       a.start();
       b.start();
       c.start();
    }
}
运行效果如下:
由c 计算,count=4
由c 计算,count=3
由c 计算,count=2
由c 计算,count=1
由c 计算,count=0
由b 计算,count=4
由b 计算,count=3
由b 计算,count=2
由b 计算,count=1
由b 计算,count=0
由a 计算,count=4
由a 计算,count=3
由a 计算,count=2
由a 计算,count=1
由a 计算,count=0

这种情况是变量不共享

3.2 共享数据的情况

多个线程可以访问同一个变量

代码如下
public class MyThread extends Thread {
    private int count = 5;
    @Override
    public void run() {
        super.run();
        count--;
        System.out.println("由" + this.currentThread().getName() + " 计算,count=" + count);
    }
}
public class Main {
    public static void main(String[] args) throws InterruptedException {
       MyThread myThread = new MyThread();
       Thread a = new Thread(myThread,"A");
       Thread b = new Thread(myThread,"B");
       Thread c = new Thread(myThread,"C");
       Thread d = new Thread(myThread,"D");
       Thread e = new Thread(myThread,"E");
       a.start();
       b.start();
       c.start();
       d.start();
       e.start();
    }
}
运行效果如下:
由E 计算,count=2
由A 计算,count=4
由C 计算,count=3
由D 计算,count=0
由B 计算,count=0

可以看出对A,B同时对count进行处理。产生了非线程安全问题。这不是我们想要的结果。

要解决些问题,只需要要在run方法前加入synchronized关键字,使多线程在执行run 方法时,以排队的方式进行处理。

public class MyThread extends Thread {
    private int count = 5;
    @Override
    synchronized public void run() {
        super.run();
        count--;
        System.out.println("由" + this.currentThread().getName() + " 计算,count=" + count);
    }
}

synchronized说明对此该当上了锁,上了锁,当有线程正在运行此方法时,必须等待其他线程对run线程调用结束后才可执行。

synchronized可以在任意对象及方法上加锁,而加锁这段代码称为 互斥区 或者 临界区

非线程安全: 主要是指多个线程对同一个对象中的同一个变量进行操作时会出现值被更改,值不同步的情况,进而影响程序的执行流程。

如下是一个非线程安全的Demo,觉得不错:

public class LoginServlet {
    private static String username;
    private static String password;
    public static void post(String username1, String password1) {
        try {
            username = username1;
            if (username.equals("a")) {
                Thread.sleep(2000);
            }
            password = password1;
            System.out.println("username = " + username + "  password = " + password);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class ALogin extends Thread{
    @Override
    public void run() {
        LoginServlet.post("a","aa");
    }
}

public class BLogin extends Thread {
    @Override
    public void run() {
        LoginServlet.post("b","bb");
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        ALogin aLogin = new ALogin();
        aLogin.start();
        BLogin bLogin = new BLogin();
        bLogin.start();
    }
}
运行结果:
username = b  password = bb
username = b  password = aa

解决这个非线程安全的问题也是使用synchronized

synchronized public class LoginServlet {
    private static String username;
    private static String password;
    public static void post(String username1, String password1) {
        try {
            username = username1;
            if (username.equals("a")) {
                Thread.sleep(2000);
            }
            password = password1;
            System.out.println("username = " + username + "  password = " + password);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

4 线程常用方法

currentThread()方法

方法返回代码段正常被哪个线程调用的信息。

demo如下

public class CountOperate extends Thread {
    public CountOperate(){
        System.out.println("begin");
        System.out.println(Thread.currentThread().getName());
        System.out.println(this.getName());
        System.out.println("end");
    }

    @Override
    public void run() {
        System.out.println("be");
        System.out.println(Thread.currentThread().getName());
        System.out.println(this.getName());
        System.out.println("en");
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        CountOperate countOperate = new CountOperate();
        Thread thread = new Thread(countOperate);
        thread.setName("A");
        thread.start();
    }
}
运行结果如下:
begin
main
Thread-0
end
be
A
Thread-0
en

结果说明:
构造方法CountOperate被main线程调用,
run方法是被名叫A的线程调用的

isAlive()方法

功能是判断当前的线程是否处于活跃状态。

活跃状态是指线程已经启动且尚未终止。线程处于正在运行或准备开始运行的状态,就认为线程是存活的。

sleep()方法

作用是指在指定的毫秒数内让当前 正在执行的线程 休眠(暂停执行)。
这个 正在执行的线程是指this.currentThread()返回的线程

getId()方法

作用是取得线程的唯一标识。

yield()方法

使用是放弃当前的cpu资源,

5 停止线程

java 有三种方法能停止线程

  1. 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
  2. 停止线程可以使用Thread.stop()方法,方法不安全,且已废弃
  3. 可用Thread.interrupt()方法,方法不会终止正在运行的线程,还需要加入一个判断才能完成线程的停止

5.1 判断线程是否停止状态

判断线程状态是不是停止的。有两种方法

  1. this.interrupted(): 测试当前线程是否已经中断,执行后具有将状态标志清除为false的功能

  2. this.isInterrupted(): 测试线程是否已经中断,但不清除状态标志

    public class Mythread extends Thread {
    private long i =0;
    @Override
    public void run() {
    for (int j = 0; j < 5000; j++) {
    System.out.println(“j = “+j);
    }
    }
    }

    public class Main {
    public static void main(String[] args) {
    try {
    Mythread mythread = new Mythread();
    mythread.start();
    Thread.sleep(1000);
    mythread.interrupt();
    System.out.println(“是否停止1 = “+mythread.interrupted());
    System.out.println(“是否停止2 = “+mythread.interrupted());
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    }

    运行结果
    j = 4998
    j = 4999
    是否停止1 = false
    是否停止2 = false

    打印两个false,这个当前线程是main,从未中断过,所以打印的结果是两个 false.

如何使main 线程产生中断效果呢

public class MainDemo {
    public static void main(String[] args) {
        Thread.currentThread().interrupt();
        System.out.println(Thread.interrupted());
        System.out.println(Thread.interrupted());
        System.out.println("end");
    }
}
运行结果:
true
false
end

5.2 能停止的线程-异常法

public class Mythread extends Thread {
    @Override
    public void run() {
        try {
            for (int j = 0; j < 500000; j++) {
                if(this.interrupted()){
                    System.out.println("已经是停止状态了,我要退出了");
                    throw new InterruptedException();
                }
                System.out.println("j = "+j);
            }
        } catch (InterruptedException e) {
            System.out.println("进入Mythered.java类run方法中的catch了");
            e.printStackTrace();
        }
    }
}

public class Main {
    public static void main(String[] args) {
        try {
            Mythread mythread = new Mythread();
            mythread.start();
            Thread.sleep(2000);
            mythread.interrupt();
        } catch (Exception e) {
            System.out.println("main catch");
            e.printStackTrace();
        }
        System.out.println("end");
    }
}

5.3 在沉睡中停止

5.4 stop()暴力停止

5.5 使用return停止线程

总结,还是建议使用 抛异常法来实现线程的停止.因为在catch块中还可以将异常向上抛.

6 暂停线程

暂停线程意味着还可以恢复运行,在java多线程中,可以 使用suspend()方法暂停线程,使用resume()方法恢复线程的执行。

6.1 suspend与resume 方法的缺点-独占

在使用suspend 与resume 方法时,如果使用不当,极易造成公共的同步对象的独占,使得其他线程无法访问公共同步对象。

6.2 suspend与resume方法的缺点-不同步

在使用suspend与resume方法时也容易出现因为线程的暂停而导致数据不同步的情况

7 线程的优先级

线程可以划分优先级,优先级高的线程得到的CPU资源多,

线程划分为1-10这10个等级,如果小于1或大于10,则JDK抛出异常 throw new IllegalArgumentException() ,jdk使用3 个常量来预置定义优先级。

public final static int MIN_PRIORITY = 1;

public final static int NORM_PRIORITY = 5;

public final static int MAX_PRIORITY = 10;

7.1 线程优先级具有继承特性

比如A线程启动线程B,则B线程的优先级和A是一样的。

代码如下:

public class Mythread1 extends Thread {
    @Override
    public void run() {
        System.out.println("Mythread1 run priority = "+this.getPriority());
        Mythread2 mythread2 = new Mythread2();
        mythread2.start();
    }
}

public class Mythread2 extends Thread {
    @Override
    public void run() {
        System.out.println("Mythread2 run priority="+this.getPriority());
    }
}

public class Main {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName()+" thread begin = "+Thread.currentThread().getPriority());
        Thread.currentThread().setPriority(7);
        System.out.println(Thread.currentThread().getName()+" thread end = "+Thread.currentThread().getPriority());
        Mythread1 mythread1 = new Mythread1();
        mythread1.start();
    }
}

运行结果:
main thread begin = 5
main thread end = 7
Mythread1 run priority = 7
Mythread2 run priority=7

7.2 优先级具有规则性

高优先级的线程就是大部分先执行完,但并不代表优先级高的线程全部先执行完。当线程优先级差别很大时,谁先报告完和代码的调用顺序无关。也就是说cpu尽量将执行资源让给优先级比较高的线程。

7.3 优先级具有随机性

线程的优先级高则优先执行完run()中的方法,但这个结果不能说的太肯定。因为线程优先级还具有随机性,也就是说优先级较高的线程不一定每次都先执行完。

不要把线程的优先级与运行结果的顺序作为衡量的标准。优先级高的线程并不一定每一次都先执行完run()中的方法,也就是说优先极与打印顺序无关。

8 守护线程

在java中有两种线程:

  • 用户线程
  • 守护线程 有陪伴的含义,当进程中不存在非守护线程了则守护线程自动销毁。比如垃圾守护线程。任何一个守护线程就是整个JVM中守护线程的保姆。只要非守护线程没有结束,守护线程就在工作。最典型的应用就是GC(垃圾回收器)

总结

本篇只是简单的介绍。后续陆续更新中……敬请期待

经典句子

宁惹白头翁,莫欺少年郎

灵魂的孤独比经济拮据更可怕

灵魂的贫穷是无法补救的,你该问自己真正想要的是什么 ,愿你不再错过,愿你不再将就

IBM rose工具的使用

第一次装上 IBM rose这个工具,出现了

解决无法启动此程序因为丢失suite objects.dll 这个错误,

通过配置环境变量即可解决这个问题,

把rose安装路径下的 E:\software\rose\Common 路径添加 到 path 环境变量 中即可