Java多线程间的同步(一)

日常开发中往往会遇到多个线程循环操作某个对象中的某个方法或者间接处理某个对象,如果当前某个对象被多个线程同时操作的话,势必会造成同步的问题,即线程A操作了对象object从而改变了该object的值,此刻线程A获取到的object的值还是之前的值。那么类似这种情况下,我们通常就要用如下几种方式去解决该问题。

1、synchronized 关键字

2、Lock

3、ReadWriteLock

4、其他方式

(一)synchronized

是java中表示同步代码快的关键字。可以放在方法修饰符前,比如private synchronized void test(){},也可以放在方法内部,修饰某一段特定的代码。synchronized有一个地方需要注意,就是在给普通方法加锁与给静态方法加锁机制是不一样的。synchronized在静态方法上表示调用前要获得类的锁,而在非静态方法上表示调用此方法前要获得对象的锁,synchronized可作用于instance变量、object reference(对象引用)、static函数和class literals(类名称字面常量)身上。。

如下代码所示:

public class SynTest {   
  
private static String a="a";   
  
//等同于方法test2
public synchronized void test1(String b){ //调用前要取得SynTest 实例化后对象的锁   
   System.out.println(a+b);   
}   
public void test2(String b){   
   synchronized (this) {//取得SynTest 实例化后对象的锁   
    System.out.println(a+b);   
   }   
}   
//等同于方法test4
public synchronized static void test3(String b){//调用前要取得SynTest .class类的锁   
   System.out.println(b+a);   
}   
public static void test4(String b){   
   synchronized (SynTest .class) { //取得SynTest .class类的锁   
    System.out.println(a+b);   
}   

注意:

1、test1和test2中synchronized 锁定的是SynTest的对象即该类的实例化对象,也可认为是object reference(对象引用), test3和test4中synchronized 锁定的是SynTest对象所属的类(Class,而不再是由这个Class产生的某个具体对象了),SynTest.class和synTest.getClass作用于同步锁是不一样的,不能用synTest.getClass()来达到锁这个Class的目的。synTest指的是由SynTest类产生的对象。

2、对synchronized(this)的一些理解
一、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。

二、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。

三、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。

四、当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。

3、synchronized 方法的缺陷:若将一个大的方法声明为synchronized 将会大大影响效率,典型地,若将线程类的方法 run() 声明为synchronized ,由于在线程的整个生命期内它一直在运行,因此将导致它对本类任何 synchronized 方法的调用都永远不会成功。当然我们可以通过将访问类成员变量的代码放到专门的方法中,将其声明为synchronized ,并在主方法中调用来解决这一问题,但是 Java 为我们提供了更好的解决办法,那就是 synchronized 块。

4、synchronized 块:通过 synchronized关键字来声明synchronized 块。语法如下:

synchronized(syncObject) {  
  //允许访问控制的代码  
}  

synchronized 块是这样一个代码块,其中的代码必须获得对象 syncObject (如前所述,可以是类实例或类)的锁方能执行,具体机制同前所述。由于可以针对任意代码块,且可任意指定上锁的对象,故灵活性较高。

5、如果只是想单纯的锁某段代码块,当有明确的对象作为锁是可以的,如果没有明确的对象作为锁,则可以创建一个特殊的变量。如下:

class Foo implements Runnable
{
        private byte[] lock = new byte[0]; // 特殊的instance变量
        Public void methodA() 
        {
           synchronized(lock) { //… }
        }
        //…..
}

注:零长度的byte数组对象创建起来将比任何对象都经济――查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码。

(二)Lock

是java.util.concurrent.locks包下的接口,Lock实现提供了比使用synchronized方法和语句可获得的更广泛的锁定操作,因为Lock可以锁定任意一段代码

public class LockTest {  
    public static void main(String[] args) {  
        final Outputter output = new Outputter();  
        new Thread() {  
            public void run() {  
                output.output("Alex-Jerry");  
            };  
        }.start();        
        new Thread() {  
            public void run() {  
                output.output("AI-Curry");  
            };  
        }.start();  
    }  
}  
class Outputter {  
    private Lock lock = new ReentrantLock();// 锁对象  
    public void output(String name) {  
        // TODO 线程输出方法  
        lock.lock();// 得到锁  
        try {  
            for(int i = 0; i < name.length(); i++) {  
                System.out.print(name.charAt(i));  
            }  
        } finally {  
            lock.unlock();// 释放锁  
        }  
    }  
}  

这样就实现了和sychronized一样的同步效果,需要注意的是,用sychronized修饰的方法或者语句块在代码执行完之后锁自动释放,而用Lock需要我们手动释放锁,所以为了保证锁最终被释放(发生异常情况),要把互斥区放在try内,释放锁放在finally内。

(三)ReadWriteLock

前两种方式虽然解决了读和写、写和写之间的互斥,但是读和读之间同样也是互斥的(不同得到线程可以同步执行读取),严重影响了效率,这个时候就该ReadWriteLock出场了。

class RWLock{      
    private int data;// 共享数据  
    private ReadWriteLock rwl = new ReentrantReadWriteLock();     
    public void set(int data) {  
        rwl.writeLock().lock();// 取到写锁  
        try {  
            System.out.println(Thread.currentThread().getName() + "准备写入数据");  
            try {  
                Thread.sleep(20);  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
            this.data = data;  
            System.out.println(Thread.currentThread().getName() + "写入" + this.data);  
        } finally {  
            rwl.writeLock().unlock();// 释放写锁  
        }  
    }     
    public void get() {  
        rwl.readLock().lock();// 取到读锁  
        try {  
            System.out.println(Thread.currentThread().getName() + "准备读取数据");  
            try {  
                Thread.sleep(20);  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
            System.out.println(Thread.currentThread().getName() + "读取" + this.data);  
        } finally {  
            rwl.readLock().unlock();// 释放读锁  
        }  
    }  
}  

测试:
public class ReadWriteLockTest {  
    public static void main(String[] args) {  
        final RWLock data = new RWLock();  
        for (int i = 0; i < 3; i++) {  
            new Thread(new Runnable() {  
                public void run() {  
                    for (int j = 0; j < 5; j++) {  
                        data.set(new Random().nextInt(30));  
                    }  
                }  
            }).start();  
        }         
        for (int i = 0; i < 3; i++) {  
            new Thread(new Runnable() {  
                public void run() {  
                    for (int j = 0; j < 5; j++) {  
                        data.get();  
                    }  
                }  
            }).start();  
        }  
    }  
}  

部分测试输出结果如下:

Thread-4准备读取数据  
Thread-3准备读取数据  
Thread-5准备读取数据  
Thread-5读取18  
Thread-4读取18  
Thread-3读取18  
Thread-2准备写入数据  
Thread-2写入6  
Thread-2准备写入数据  
Thread-2写入10  
Thread-1准备写入数据  
Thread-1写入22  

(四)其他方式

线程同步的问题,最上面说的只是一种问题,往往导致同步问题的原因有多种。如线程A和线程B,线程B要等到线程A执行完毕之后才能进行操作,或者子线程执行结束才能让主线程去操作等等,这种情况下,可解决的方案很多,如:使用FutureTask+Callable代替Runnable去执行异步任务(FutureTask 的 get 方法,能够阻塞主线程,直到子线程初始化完成,才继续执行主线程逻辑),使用handler机制,操作线程的wait、notify、join、yield等等。基于这些情况,下篇文章将会逐个去分析和解决。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容