java多线程编程相关技术

首先要记住核心一点、多线程事异步的,也就是cpu利用率大幅提升。java

Stringbuffer 是线程安全的   stringbuilder是线程不安全的
HashTable是线程安全的       HashMap不是线程安全的    程序员

 

 

2.对象及变量的并发访问下的问题。

方法内的变量由于是方法的私有变量,全部不存在线程安全的问题。所以方法内的变量是线程安全的。安全

多个线程若是同时访问一个对象中的实例变量,则该实例变量不是线程安全的。多线程

 

synchronized并发

 

synchronized取得的锁都是对象锁,哪一个线程先执行带synchronized关键字的方法,哪一个线程就持有该方法所属对象的锁lock,那么其余线程只能呈等待状态,前提是多个线程访问的是同一个对象。异步

但若是多个线程访问多个对象,则jvm会建立多个锁。jvm

A线程先持有object对象的lock锁,B线程能够以异步的方式调用object对象中的非synchronized类型的方法。性能

A线程先持有object对象的lock锁,B线程若是这时调用object对象中的synchronized类型的方法则需等待,也就是同步。ui

 

synchronized 拥有锁重入的功能。this

锁重入:本身能够再次获取本身的内部锁,例如一条线程得到了某个对象的锁,此时这个对象锁尚未释放,当其再次想要获取这个对象的锁的时候仍是能够获取的,若是不可锁重入的话,就会形成死锁。

可重入锁也支持在父子类继承的环境中。当存在父子类存在继承关系时,子类是彻底可经过可重入锁调用父类的同步方法。

 

当一个线程执行的代码出现异常时,其所持有的锁会自动释放。

同步是不能够被继承的。

例如父类synchronized关键字修饰过的方法,子类下的该方法是不具有该关键字的。除非子类也本身修饰。

 

关键字synchronized修饰方法的弊端。

好比A线程调用同步方法执行一个长时间的任务,那么B线程则必须等待比较长时间。这种状况下可使用synchronized同步语句块来解决问题。

synchronized方法是对当前对象进行加锁,而synchronized代码块是对某一个对象进行加锁。

当一个线程访问object的一个synchronized 同步代码块时,另外一个线程仍然能够访问该object对象中的非synchronized(this)同步代码块。

当一个线程访问object的一个synchronized(this)同步代码块时,其余线程对同一个object中全部其余synchronized(this)同步代码块的访问即将被阻塞。这说明synchronized使用的对象监视器是一个。

 

若是一个类中有不少个synchronized方法,这时虽然能实现同步,但会收到阻塞,因此影响运行效率,但若是使用同步代码块锁非this对象,则synchronized(非this)代码块中的程序与同步方法是异步的,不与其余锁this同步方法争抢this锁,则可大大提升运行效率。

 

java还支持对“任意对象”做为“对象监视器”来实现同步的功能。这个“任意对象”大多数时实例变量及方法的参数,使用格式为synchronized(非this对象)

 synchronized(非this对象x)是将x对象自己做为“对象监视器”,所以:

1.当多个线程同时执行synchronized(x){}同步代码块时呈同步效果。

2.当其余线程执行x对象中synchronized同步方法时呈同步效果。

3.当其余线程执行x对象方法里面的synchronized(this)代码块时也呈现同步效果。

但须要注意:若是其余线程调用不加synchronized关键字的方法时,仍是异步调用。

 

静态同步synchronized方法与synchronized(class)代码块

关键字synchronized还恶意家用再static静态方法上,这是对当前*.java文件对应的class类进行加锁。

在持有不一样的锁的状况下,一个是对象锁,另一个是class锁,会致使异步运行。class锁会对该类的全部对象实例起做用。

同步synchronized(class)代码块的做用和synchronized static 方法的做用同样。

 

这里要注意一个数据类型String的常量池特性

将synchronized(string)同步块与String 联合使用时,要注意常量池带来的一些例外。

例子以下:

public class Main {
    public static void main(String[] args) {
        Service service =new Service();
        ThreadA a=new ThreadA(service);
        a.setName("A");
        a.start();
        ThreadB b=new ThreadB(service);
        b.setName("B");
        b.start();
    }
}
public class Service {
    public static void print(String param) {
        try {
        synchronized (param) {
            while(true) {
                System.out.println(Thread.currentThread().getName());
                
                    Thread.sleep(500);
                } 
            }
        }catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
public class ThreadA extends Thread{
    private Service service;
    public ThreadA(Service service) {
        super();
        this.service=service;
    }
    public void run() {
        service.print("AA");
    }
}
public class ThreadB extends Thread {
    private Service service;
    public ThreadB(Service service) {
        super();
        this.service=service;
    }
    public void run() {
        service.print("AA");
    }
}

运行结果就是无限打印A A A A A A

出现这样的状况就是由于String 的两个值都是AA,两个线程持有相同的锁,因此致使线程B并不能运行,这就是常量池带来的问题。

所以大多数状况,同步synchronized代码块都不使用String做为锁对象,而用其余,好比 new Object()实例化一个Object对象。

 

同步方法还有个弊端就是容易形成死循环。假如在某类中有多个synchronized修饰的方法,线程a和b分别访问不一样的synchronized修饰过的方法,当a线程访问的方法中出现了死循环,b线程则没法访问另一个方法,由于当前的对象锁并无释放。

 

死锁的问题:当设计程序时,双方互相持有了对方的锁,就会形成死锁。缘由就是线程互相等待对方释放锁。

 

以上的synchronized 方法和方法块一样适用于内部类和静态内部类。

 

要注意一种状况:就是在线程运行中锁对象发生了改变。

例如当前有个 String lock="123"的字符串,当synchronized(lock)的时候,若是在该同步代码块中,lock的值发生了改变,例如变成了456.那么同时访问改代码块的另一个线程便可当即访问,这就是锁对象发生了改变。

还有一种状况就是synchronized(user)注:user 是User的对象,那么在某线程执行该代码块的时候,改变了user中某一属性的值,例如user.setUsername("111"),则另外一访问该代码块的线程并不会当即得到该代码块的锁对象,所以原则就是只要对象不变,即便对象的某个属性发生变化,运行的结果仍是同步。

 

volatile 

 

volatile关键字的主要做用是使变量在多个线程间可见。

关键字volatile的做用是强制从公共堆栈中取得变量的值,而不是从线程私有数据栈中取得变量的值。

 

图2-75表明的是没有用volatile关键字的读取某变量的模式

 

经过使用volatile关键字,强制从公共内存中读区变量的值

volatile最致命的缺点是不支持原子性

volatile与synchronized的比较:

1.volatile是线程同步的轻量级实现,因此性能要比synchronized要好,而且volatile只能修饰变量,而synchronized能够修饰方法,以及代码块。

2.多线程访问volatile不会发生阻塞,而synchronized会出现阻塞

3.volatile能保证数据的可见性,但不能保证原子性。而synchronized能够保证原子性,也能够间接保证可见性,由于它会将私有内存和公公内存中的数据作同步。

4.最后终点重申,volatile解决的是变量在多个线程之间的可见性,而synchronized解决的是多个线程之间访问资源的同步性。

 

此处提一下线程安全:线程安全包含原子性和可见性,java的同步机制都是围绕这两个方面来确保线程安全的。

 

解释一下volatile非原子的特性:

若是修改实力变量中的数据,好比i++,这样一个操做并非原子操做。也就是非线程安全的。i++的操做步骤分解以下:

1)从内存中取出i的值

2)计算i的值

3)将i的值写到内存中。

假如第二部计算i的值的时候,另一个线程也修改i的值,此时就会出现脏数据。解决的办法其实就是使用synchronized关键字。

下图演示一下volatile时出现非线程安全的缘由。

use和assign时屡次出现,但这一操做并非原子性,也就是在read和load以后,若是主内存count变量发生修改以后,线程工做内存中的值因为已经加载,不会产生对应变化,也就是私有内存和公共内存中的变量不一样步,因此计算出来的结果和预期不同,就会出现非线程安全的问题。

综上所述,volatile关键字解决的是变量读时的可见性问题,但没法保证原子性,对于多个线程访问同一个实例变量仍是须要加锁同步。

 

synchronized代码块其实也有volatile同步的功能。

synchronized不只可使多个线程访问同一个资源具备同步性,并且它还有句将线程工做内存中的私有变量和与公共内存中的变量同步的功能。

关键字synchronized能够保证在同一时刻,只有一个线程能够执行某一个方法或代码块,它包含两个特征:互斥性和可见性。同步synchronized不只能够解决一个线程看到对象不一致的状态,(好比,修改了某个对象后,另一个线程便可得到修改后的对象的锁。)还能够保证进入同步方法或者同步代码块的每一个线程,都看到由同一个锁保护以前全部的修改效果。

 

3.线程间通讯的详解

使线程间进行通讯后,系统之间的交互性会更增强大,在大大提升CPU利用率的同时还会使程序员对个线程任务在处理的过程当中进行有效的把控与监督。

等待/通知机制咱们经过wait/notify方法来实现。

要注意的是:多个线程共同访问一个变量,也是一种通讯,但那种通讯机制不是“等待/通知”,两个线程彻底是主动式地读取一个共享变量,在花费读取时间的基础上,读到的值是否是想要的,并不能彻底肯定。

 

在调用wait()方法以前,线程必须得到该对象的对象级别锁,即只能在同步方法或者同步代码块中调用wait()方法。在执行wait方法后,当前线程释放锁。

方法notify()一样要在同步方法或同步块中调用,即在调用前,线程也必须得到该对象的对象级别锁。

要注意的是:在执行notify方法后,当前线程不会当即释放该对象锁,呈wait状态的线程也不能立刻获取该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出synchronized代码块后,当前线程才会释放锁,而呈wait状态所在的线程才能够获取该对象锁。当得到了该对象锁的线程运行完毕后,它会释放掉该对象锁,此时若是该对象没有再次使用notify语句,则即便该对象已经空闲,其余wait状态的线程因为没有获得该对象的通知,还会继续阻塞在wait状态,直到这个对象发出一个notify()或者notifyAll().

一句话总结wait和notify 就是wait使线程中止运行,notify使中止的线程继续运行。

notify方法能够随机唤醒等待队列中等待同一共享资源的“一个”线程,并使该线程退出等待队列,进入可运行状态。

notifyAll()方法使全部在等待队列中等待同一共享资源的“所有”线程从等待状态退出,进入可运行状态,此时优先级最高的那个线程最早执行,但也有多是随机执行,这要取决于JVM虚拟机的实现。

每一个锁对象都有两个队列,一个是就绪队列,一个是阻塞队列。就绪队列存储了将要得到锁的线程,阻塞队列存储了被阻塞的线程。一个线程被唤醒后,才会进入就绪队列,等待cpu的调度,反之,一个线程被wait后,就会进入阻塞队列,等待下一次被唤醒。

 

方法wait(long)带一个参数的方法功能室等待某一时间内是否有线程对锁紧型唤醒,若是超过这个时间则自动唤醒。

还要注意notify的时候,有个通知过早的问题,不要在另一个线程wait以前就notify 这样会致使wait的线程永远也得不到通知。

 

方法join的使用:

不少状况下,主线程建立并启动子线程,若是子线程中要进行大量的耗时运算,主线程每每将早于子线程结束以前结束。这是若是主线程响等待子线程执行完成以后再结束,好比子线程处理一个数据,主线程要取得这个数据中的值,就要用到join()方法了。

方法join的做用是等待线程对象销毁。

详细来讲,是使所属的线程对象x正常执行run()方法中的任务,而使当前线程z进行无限期的阻塞,等待线程x销毁后再继续执行线程z后面的代码。

join具备使线程排队运行的做用,有些相似同步的运行效果,join与synchronized的区别是:join在内部使用wait()方法进行等待,而synchronized关键字使用的是“对象监视器”原理做为同步。

join也有join(long)的方法,是设定等待的时间。join(long)内部是使用wait(long)的方法实现的,因此有释放锁的特色。

而Thread.sleep(long)方法却不释放锁。

 

类ThreadLocal的使用

类ThreadLocal主要解决的是每一个线程绑定本身的值。

类Threadlocal解决的是变量在不一样线程间的隔离性,也就是不一样线程拥有本身的值,不一样线程中的值是能够放入Threadlocal类中进行保存的。

在不给ThreadLocal类中的静态变量使用set方法以前,用get方法返回的都是null。

能够经过建立一个子类继承ThreadLocal类,里面有一个方法initialValue()来设定初始值。

public class ThreadLocalExt extends ThreadLocal {

  protected Object initialValue() {

    return "我是默认值,第一次get再也不为null";

  }

}

使用InheritableThreadLocal类可让子线程从父线程中取得值。

public class InheritableThreadLocalExt extends InheritableThreadLocal {

  protected Object initialValue() {

    return new Date().getTime();

  }

}

经过继承InheritableThreadLocal类,可使父子线程经过 public static InheritableThreadLocalExt tl=new InheritableThreadLocalExt();获得的tl的值是一致的。

若是想修改子线程的值,能够重写如下方法

protected Object childValue(Object parentValue) {

  return parentValue+"我在子线程加的";

}

使用该InheritableThreadLocal类须要注意的一点就是,若是子线程在取得值的同时,主线程将InheritableThreadLocal中的值进行更改,那么子线程取到的值仍是旧值。

 

Lock的使用

ReentranLock也能够实现等待/通知模式,利用Condition丢翔。Condition能够实现多路通知功能。也就是在一个Lock对象里面能够建立多个Condition实例,线程对象能够注册在指定的Condition中,从而能够有选择的进行线程通知,在调度线程上更加灵活。

在使用notify/notifyall方法进行通知时,被通知的线程倒是由JVM随机选择的。但使用ReentrantLock结合Condition类是能够实现前面介绍过的选择性通知,这个功能很重要。

Object类中的wait()方法至关于Condition类中的await()方法.

Object类中的wait(long)方法至关于Condition类中的await(long time, TimeUnit unit)方法。

Object类中的notify()方法至关于Condition类中的signal()方法。

Object类中的notifyAll()方法至关于Condition类中的signalAll()方法。

注意:在condition.await()调用以前 要先调用lock.lock() 得到监视器。

若是想单独唤醒部分线程就要使用多个Condition对象了,也就是condition对象能够唤醒部分指定线程。

 

锁Lock分为公平锁与非公平锁。

公平锁表示线程获取锁的顺序是按照线程加锁的顺序来分配的,既先来先得的FIFO先进先出顺序,而非公平锁就是一种获取锁的抢占机制,是随机得到锁的,和公平锁不同的就是先来的不必定先获得锁,这个方式可能形成某些线程一致拿不到锁,结果也就是不公平的了。

 

方法int getHoldCount()的做用是查询当前线程保持此锁定的个数,也就是调用lock()方法的次数。

方法 intgetQueueLength()的做用是反悔正等待此锁定的线程估计数。

相关文章
相关标签/搜索