线程入门-线程同步浅析

前言

刚实习的时候,当遇到数据量大并对效率要求高的业务时,就开始尝试学习如何使用多线程来处理。现在与大家分享一下。大家说到多线程,总有一个绕不开的问题,就是如何实现多线程的同步。大致总结了2个大家常用的方式:synchronized关键字与java.util.concurrent.locks.Lock接口。

synchronized关键字

synchronized关键字一般作用于代码块或者方法。根据场景又有所不同。

synchronized作用于代码块时

synchronized(this)

这种方式锁的是当前的对象,如以下代码

package thread;

      public class Thread1 implements Runnable {  
          public void run() {  
               synchronized(this) {  
                    for (int i = 0; i < 5; i++) {  
                         System.out.println(Thread.currentThread().getName() + " synchronized loop " + i); 
                         try {
                          Thread.sleep(100);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                    }  
               }  
          }  
          public static void main(String[] args) {  
               Thread1 target = new Thread1();  
               Thread threadA = new Thread(target, "threadA");  
               Thread threadB = new Thread(target, "threadB");  
               threadA.start();  
               threadB.start(); 
          } 
      }
  
      输出结果:

      threadA synchronized loop 0
      threadA synchronized loop 1
      threadA synchronized loop 2
      threadA synchronized loop 3
      threadA synchronized loop 4
      threadB synchronized loop 0
      threadB synchronized loop 1
      threadB synchronized loop 2
      threadB synchronized loop 3
      threadB synchronized loop 4

synchronized(object)

这种方式锁的是object存在heap中的内容,而非stack上的引用.我们来看两段代码

package thread;

public class Thread2 implements Runnable {
    private Vsrsion version;

    public void run() {
        synchronized (version) {
            for (int i = 0; i < 5; i++) {
                //version = new Vsrsion(i);
                version.content = i;
                System.out.println(Thread.currentThread().getName() + " synchronized loop " + i + "version" + version.content);
            }
        }
    }

    public static void main(String[] args) {
         Vsrsion a = new Vsrsion(0);
         Thread2 target = new Thread2(); 
         target.version = a;
         Thread threadA = new Thread(target, "threadA");  
         Thread threadB = new Thread(target, "threadB");  
         threadA.start();  
         threadB.start(); 
    }
}

class Vsrsion {
    int content;
    public Vsrsion(int content){
        this.content = content;
    }
}
输出结果:
threadA synchronized loop 0version0
threadA synchronized loop 1version1
threadA synchronized loop 2version2
threadA synchronized loop 3version3
threadA synchronized loop 4version4
threadB synchronized loop 0version0
threadB synchronized loop 1version1
threadB synchronized loop 2version2
threadB synchronized loop 3version3
threadB synchronized loop 4version4

因为2线程个线程是只是对version对象内的值修改,所以2个线程有序进行执行。

package thread;

public class Thread2 implements Runnable {
    private Vsrsion version;

    public void run() {
        synchronized (version) {
            for (int i = 0; i < 5; i++) {
                version = new Vsrsion(i);
                //version.content = i;
                System.out.println(Thread.currentThread().getName() + " synchronized loop " + i + "version" + version.content);
            }
        }
    }

    public static void main(String[] args) {
         Vsrsion a = new Vsrsion(0);
         Thread2 target = new Thread2(); 
         target.version = a;
         Thread threadA = new Thread(target, "threadA");  
         Thread threadB = new Thread(target, "threadB");  
         threadA.start();  
         threadB.start(); 
    }
}

class Vsrsion {
    int content;
    public Vsrsion(int content){
        this.content = content;
    }
}

输出结果:

threadB synchronized loop 0version0
threadB synchronized loop 1version1
threadA synchronized loop 0version0
threadB synchronized loop 2version2
threadB synchronized loop 3version3
threadA synchronized loop 1version1
threadB synchronized loop 4version4
threadA synchronized loop 2version2
threadA synchronized loop 3version3
threadA synchronized loop 4version4

从这里就能看出区别来了,因为线程里面的循环在heap创建新的对象,并将新的地址付给stack上的引用。所以2个线程其实只同步了第一次的version0,2个线程最后就乱序执行了。

synchronized何时产生作用

当一个synchronized(object)锁住object时,非synchronized代码块照样能操作这段代码块

package thread;

public class Thread2 implements Runnable {
    private Vsrsion version;

    public void run() {
        synchronized (version) {
            for (int i = 0; i < 5; i++) {
                version.content = i;
                try {
                    Thread.sleep((long) (Math.random()*100));
                } catch (InterruptedException e) {
                }
                System.out.println(Thread.currentThread().getName() + " version " + version.content);
            }
        }
    }

    public static void main(String[] args) {
         Vsrsion a = new Vsrsion(0);
         Thread2 target = new Thread2(); 
         target.version = a;
         Thread threadA = new Thread(target, "threadA");  
         threadA.start();  
         
         for (int i = 0; i<5; i++ ){
             a.content = i;
             System.out.println("loop version " + a.content);
             try {
                 Thread.sleep((long) (Math.random()*100));
             } catch (InterruptedException e) {
             }
         }
    }
}

class Vsrsion {
    int content;
    public Vsrsion(int content) {
        this.content = content;
    }
}

输出结果:

loop version 0
threadA version 0
loop version 1
threadA version 1
loop version 2
threadA version 2
loop version 3
loop version 4
threadA version 4
threadA version 4

所以当多个线程对某一资源做同步时,必须都加上synchronized关键字

1.2当synchronized作用于方法时

1.2.1.当方法为静态

例如:

class A{
    public static synchronized void medthod(){}
}
并等价于
class A{
    public void medthod(){
        synchonized(A.class){
            ....................
         }
    }
}

并未创建A对象并且要同步A中的static变量时,可以使用这种方式

1.2.2.该方法为非静态

package thread;

public class Thread3 implements Runnable {

    public synchronized void print(){
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + " synchronized loop " + i);
            try {
                Thread.sleep((long) (Math.random()*100));
            } catch (InterruptedException e) {
            }
        }
    }
    
    public void run() {
        print();
    }

    public static void main(String[] args) {
         Thread3 target = new Thread3(); 
         Thread threadA = new Thread(target, "threadA");  
         Thread threadB = new Thread(target, "threadB");  
         threadA.start();  
         threadB.start(); 
    }
}

输出结果:
threadA synchronized loop 0
threadA synchronized loop 1
threadA synchronized loop 2
threadA synchronized loop 3
threadA synchronized loop 4
threadB synchronized loop 0
threadB synchronized loop 1
threadB synchronized loop 2
threadB synchronized loop 3
threadB synchronized loop 4

保证了这个类的方法在多个线程内得到同步。把 public synchronized void print()改成public static synchronized void print()结果不变。

java.util.concurrent.locks.Lock

关于这个接口的三个实现类,这里简单的讲下

ReentrantLock, 
Lock.lock()
Lock.unlock()
这个锁保证只有一个线程能对这块加锁的代码进行读和写操作

ReentrantReadWriteLock.ReadLock, 
lock.readLock()
lock.unlock()

ReentrantReadWriteLock.WriteLock
lock.wirteLock()
lock.unlock()
   /**
     * ReadWriteLock内置两个Lock,一个是读的Lock,一个是写的Lock。
     * 多个线程可同时得到读的Lock,但只有一个线程能得到写的Lock,
     * 而且写的Lock被锁定后,任何线程都不能得到Lock。ReadWriteLock提供的方法有:
     * readLock(): 返回一个读的lock 
     * writeLock(): 返回一个写的lock, 此lock是排他的。
     * ReadWriteLockTest很适合处理类似文件的读写操作。
     * 读的时候可以同时读,但不能写;写的时候既不能同时写也不能读。
     */  
上一篇:HDU 4539 郑厂长系列故事——排兵布阵


下一篇:puppet连载15:搭建zabbix服务端、客户端