为了更好地对线程执行结果有正确的预期。

一、synchronized同步方法。

1> 实例变量非线程安全:
-- 公共对象的(即使private)成员变量,在异步方式调用的方法中被访问时,会产生数据不一致(两个线程访问一个公共对象)。


2> 锁个数==实例对象个数;
-- 线程获取的锁是对象锁:当线程A调用Object对象中加入synchronized关键字的方法F()时,线程A就获得了该方法F()所在对象的锁--即Object对象的锁。


3> 共享对象的类Object中有:

public class Object{  
    public void methodA(){}
    public void methodB(){}
}

--1. methodA为syn,methodB为普通:线程A先持有object对象的锁(调用了methodA),B线程可以以异步方式调用object中的非synchronized方法methodB;
--2. methodA和methodB都为syn:线程A先持有object对象的锁(调用了methodA),B线程调用对象中另外的syn方法,也需要等待。


4> 脏读。(读与写不同步)
-- 线程A对公共实例变量进行写时(未完成),线程B对齐进行读,取到的是错误的数据。


5> synchronized锁重入
-- 线程获得实例对象的锁后,再次请求该锁时是可以获得的。对照<3>中,若methodA()和methodB()都是syn的,那么在methodA中调用methodB是可以的(否则的话也不合理)。
--* 如果不锁重入的话,会造成死锁。
--* 锁重入存在于继承中 [ 子类对于syn的A方法和syn的B(继承来的)方法,可锁重入 ]。


6> 同步不具有继承性质
-- 子类S继承父类M中的syn方法,子类必须显式对其添加synchronized关键字,才可以实现同步。


 

二、synchronized(xx)同步代码块。

1. synchronized方法对于非临界资源的耗时操作很不友好,增加了时耗(体现:时间花在等待上而不是在操作上)。可以用 [ synchronized(this) ] 将代码块区分开,一半同步一半异步。对临界资源的操作,放在synchronized中;隔离其它操作。


2. 当一个线程访问object的一个synchronized同步代码块时,其它线程对同一个object中所有其它synchronized同步代码块的访问将被阻塞(this对象监视器是同一个)。


3*. syn同步方法和syn同步代码块,访问前者时,后者也被阻塞;反之亦然。


4. synchronized(任意对象)同步代码块。

-- 使用synchronnized(非this对象x)同步代码块时,对象监视器必须是同一个(实例对象),否则结果就是异步调用。

-- 特别注意对可能出现脏读的数据处理,对多线程的共享变量的操作时,对其上锁。


5. synchronized public static void method(){}和synchronized(Object.class). 其实一样,对该类的Class文件持有锁,对所有该类实例生效。在线程A持有该Class的锁后,其他线程访问该类中其他static方法时会阻塞。

synchronized public static void methodA(){}
synchronized public static void methodB(){}
Object o1 = new Object();
Object o2 = new Object();
MyThread t1 = new MyThread(o1);
MyThread t2 = new MyThread(o2);

-- 在t1和t2当中对methodA或B方法的调用,都是同步的,无论传进的对象不是同一个。


6. 使用synchronized(String)时注意常量池的特性,若传入的是一个String常量,则持有的锁是同一个,无法将代码块同步;如果传入的是一个[new String()],则持有的锁不是同一个,可以将其同步化。


7. 对于两个方法methodA()和methodB(),若要他们各自对自身同步(不同线程执行自己这个方法的时候是同步的),则可以利用:

Object lock1 = new Object();
public void methodA()
{
    synchronized(lock1)
    {...}
}
Object lock2= new Object();
{
    synchronized(lock2)
    {...}
}

 

8. 死锁。双方互相持有对方的锁,各自都得不到释放。
避免:
-- 加锁顺序(线程按照一定的顺序加锁)
-- 加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)
-- 死锁检测

 


 三、volatile关键字

1. volatile保证了操作的可见性
假如线程1先执行,线程2后执行:
//线程1

boolean stop = false;
    while(!stop){
    doSomething();
}

//线程2

stop = true;

--* 在大多数时候,这个代码能够把线程中断,但是也有可能会导致无法中断线程。

--* 解释:每个线程在运行过程中都有自己的工作内存,那么线程1在运行的时候,会将stop变量的值拷贝一份放在自己的工作内存当中。那么当线程2更改了stop变量的值之后,但是还没来得及写入主存当中,线程2转去做其他事情了,那么线程1由于不知道线程2对stop变量的更改,因此还会一直循环下去。


--* 但是用volatile修饰之后就变得不一样了:
第一:使用volatile关键字会强制将修改的值立即写入主存;

第二:使用volatile关键字的话,当线程2进行修改时,会导致线程1的工作内存中缓存变量stop的缓存行无效(反映到硬件层的话,就是CPU的L1或者L2缓存中对应的缓存行无效);

第三:由于线程1的工作内存中缓存变量stop的缓存行无效,所以线程1再次读取变量stop的值时会去主存读取。

那么在线程2修改stop值时(当然这里包括2个操作,修改线程2工作内存中的值,然后将修改后的值写入内存),会使得线程1的工作内存中缓存变量stop的缓存行无效,然后线程1读取时,发现自己的缓存行无效,它会等待缓存行对应的主存地址被更新之后,然后去对应的主存读取最新的值。那么线程1读取到的就是最新的正确的值。

2. volatile不保证操作的原子性
下面看一个例子:

public class Test {
    public volatile int inc = 0;

    public void increase() {
        inc++;
    }

    public static void main(String[] args) {
        final Test test = new Test();
        for(int i=0;i<10;i++){
            new Thread(){
                public void run() {
                    for(int j=0;j<1000;j++)
                    test.increase();
              };
          }.start();
      }

      while(Thread.activeCount()>1) //保证前面的线程都执行完
          Thread.yield();
      System.out.println(test.inc);
    }
}    

运行它会发现每次运行结果都不一致,都是一个小于10000的数字。

因为,可见性只能保证每次读取的是最新的值,但是volatile没办法保证对变量的操作的原子性。

--* 假如某个时刻变量inc的值为10,

  线程1对变量进行自增操作,线程1先读取了变量inc的原始值,然后线程1被阻塞了;

  然后线程2对变量进行自增操作,线程2也去读取变量inc的原始值,由于线程1只是对变量inc进行读取操作,而没有对变量进行修改操作,所以不会导致线程2的工作内存中缓存变量inc的缓存行无效,所以线程2会直接去主存读取inc的值;

  线程2发现inc的值时10,然后进行加1操作,并把11写入工作内存,最后写入主存。

  然后线程1接着进行加1操作,由于已经读取了inc的值,注意此时在线程1的工作内存中inc的值仍然为10,所以线程1对inc进行加1操作后inc的值为11,然后将11写入工作内存,最后写入主存。

  那么两个线程分别进行了一次自增操作后,inc只增加了1

--* 前面不是保证一个变量在修改volatile变量时,会让缓存行无效吗?

  注意,线程1对变量进行读取操作之后被阻塞了,并没有对inc值进行修改。所以其它线程并没有从公共内存中读取新的inc值。(总的过程:线程1从内存中读到inc为10,线程2在此时读到10,线程1和2各自对inc+1,各自都写入内存,此时inc为11。)

  --** (自增操作不是原子性的)

  自增操作(i++)分三步:1.从内存中取出i的值;2.计算i的新值;3.将i的新值写入内存。

 posted on 2018-04-12 16:57  lahigh  阅读(104)  评论(0)    收藏  举报