Java多线程学习二 synchronized

前面讲过,线程共享变量是非线程安全的,synchronized关键字可使方法变为线程安全的方法

一、线程安全问题

    private CountNum countNum;

    public MyThread(CountNum countNum) {
        this.countNum = countNum;
    }

    @Override
    public void run() {
        countNum.add("a");
    }
public class MyThread2 extends Thread {
    private CountNum countNum;

    public MyThread2(CountNum countNum) {
        this.countNum = countNum;
    }

    @Override
    public void run() {
       countNum.add("b");
    }
}

public class TestMain {
    public static void main(String[] args)  {
        CountNum countNum=new CountNum();
        MyThread myThread=new MyThread(countNum);
        MyThread2 myThread2=new MyThread2(countNum);
        myThread.start();
        myThread2.start();
    }
}

输出

a set over
b set over
name:b   num:200
name:a   num:200

两个线程同时访问一个没有同步的方法,a修改完num的值睡眠时,b这时又修改了num的值,a打印时的num值其实已经被b给修改了,这就是共享变量被多线程同事访问时候的问题。

二、synchronized

解决上述问题很简单,只需要在方法前加上synchronized关键字即可,即使a睡眠了,b也不会进入方法执行,此时方法是同步顺序执行的,输出如下

a set over
name:a   num:100
b set over
name:b   num:200

三、多个对象多个锁

把TestMain代码改成如下

public class TestMain {
    public static void main(String[] args)  {
        CountNum countNum=new CountNum();
        CountNum countNum2=new CountNum();
        MyThread myThread=new MyThread(countNum);
        MyThread2 myThread2=new MyThread2(countNum2);
        myThread.start();
        myThread2.start();
    }
}
a set over
b set over
name:b   num:200
name:a   num:100

每个线程用不同的对象,此时代码又变为异步执行的,因为此时synchronized的锁是不同的对象。synchronized关键字取得的锁都是对象锁。
注意:
A线程获得某对象的锁时,其他线程可以以异步的方式调用该对象非synchronized类型的方法,但是其他线程调用其他synchronized的方法时需要等待

四、脏读

public class PublicVar {
    private String name="b";
    private String passwd="bb";

    public synchronized void setValue(String name,String passwd){
        try {
            this.name=name;
            Thread.sleep(5000);
            this.passwd=passwd;
            System.out.println("setName "+Thread.currentThread().getName()+" name="+name+" passwd="+passwd);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public String getValue() {
        System.out.println("getName "+Thread.currentThread().getName()+" name="+name+" passwd="+passwd);
        return name;
    }
}

public class MyThread extends Thread {

    private PublicVar publicVar;

    public MyThread(PublicVar publicVar) {
        this.publicVar = publicVar;
    }

    @Override
    public void run() {
        publicVar.setValue("a", "aa");
    }
}

    public static void main(String[] args) throws InterruptedException {
        PublicVar publicVar=new PublicVar();
        MyThread myThread=new MyThread(publicVar);
        myThread.start();
        Thread.sleep(2000);
        publicVar.getValue();
    }
getName main name=a passwd=bb
setName Thread-0 name=a passwd=aa

此处出现脏读的原因是getValue方法并不是同步的,主线程启动完thread线程后休眠了2秒,thread线程在设置完name的值后休眠了5秒,主线程醒来后继续执行getValue方法,在休眠的两秒时间里,name已经被thread线程设置新值,但passwd还没有,所以出现了这种情况。
解决此问题的方法当然是给getValue方法也加上同步synchronized关键字

总结:
当A线程调用anyObject对象加入synchronized关键字的X方法时,A线程就获得了X方法锁,更准确的将,是获得了对象的锁,所以其他线程必须等A线程执行完毕才可以调用X方法,但其他线程可以随意调用其他非synchronized的方法,而其他线程调用声明synchronized关键字的非X方法时,也必须等A线程将X方法执行完,也就是释放对象锁后才可以调用。

五、synchronized锁重入

可重入锁:比如A线程获得了某对象的锁,此时这个对象锁还没有释放,当其再次想获取这个对象的锁时还说可以获取的,一个synchronized方法/块的内部调用本类或父类的其他synchronized方法/块时,是永远可以得到锁的。如果不可锁重入的话,就会造成死锁。
当存在父子类继承关系时,子类是完全可以通过“可重入锁”调用父类的同步方法的。
注意:

重写父类的synchronized方法不具有同步效果,如需同步仍要手动加上synchronized关键字

六、死锁

public class DealThread implements Runnable {

    public String name;
    public Object lock1 = new Object();
    public Object lock2 = new Object();

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        if ("a".equals(name)) {
            synchronized (lock1) {
                try {
                    System.out.println("name=" + name);
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock2) {
                    System.out.println("按lock1->lock2代码顺序执行了");
                }
            }
        }
        if ("b".equals(name)) {
            synchronized (lock2) {
                try {
                    System.out.println("name=" + name);
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock1) {
                    System.out.println("按lock2->lock1代码顺序执行了");
                }
            }
        }
    }
}

    public static void main(String[] args) throws InterruptedException {
        DealThread dealThread=new DealThread();
        dealThread.setName("a");
        Thread thread=new Thread(dealThread);
        thread.start();
        Thread.sleep(1000);
        dealThread.setName("b");
        Thread thread2=new Thread(dealThread);
        thread2.start();
    }

查看死锁信息,进入jdk的bin目录执行jps命令,输出如下,发现TestMain线程的id为8196

3904 Jps
10628 Launcher
8196 TestMain
9892 RemoteMavenServer
11432

使用jstack命令查看结果jstack -l 8196

Found one Java-level deadlock:
=============================
"Thread-1":
  waiting to lock monitor 0x0000000002f1c978 (object 0x00000000d59a1d30, a java.lang.Object),
  which is held by "Thread-0"
"Thread-0":
  waiting to lock monitor 0x0000000002f188d8 (object 0x00000000d59a1d40, a java.lang.Object),
  which is held by "Thread-1"

Java stack information for the threads listed above:
===================================================
"Thread-1":
        at com.yjj.chapter02.test04_syn4.DealThread.run(DealThread.java:43)
        - waiting to lock <0x00000000d59a1d30> (a java.lang.Object)
        - locked <0x00000000d59a1d40> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:748)
"Thread-0":
        at com.yjj.chapter02.test04_syn4.DealThread.run(DealThread.java:30)
        - waiting to lock <0x00000000d59a1d40> (a java.lang.Object)
        - locked <0x00000000d59a1d30> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock.

七、锁对象改变

public class MyService {
    private String lock="123";

    public void testMethod(){
        synchronized (lock){
            try {
                System.out.println(Thread.currentThread().getName()+" begin "+System.currentTimeMillis());
                lock="456";
                Thread.sleep(2000);
                System.out.println(Thread.currentThread().getName()+" end "+System.currentTimeMillis());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class ThreadA extends Thread{
    private MyService myService;

    public ThreadA(MyService myService) {
        this.myService = myService;
    }

    @Override
    public void run() {
        myService.testMethod();
    }
}

ThreadA同ThreadB

public class TestMain {
    public static void main(String[] args) throws InterruptedException {
        MyService service=new MyService();
        ThreadA a =new ThreadA(service);
        a.setName("A");
        ThreadB b =new ThreadB(service);
        b.setName("B");
        a.start();
        Thread.sleep(50);
        b.start();
    }
}

可以看到A线程和B线程是异步执行的,虽然加了synchronized关键字,但是由于A线程竞争的是锁是“123”,得到锁后把锁改为了“456”,而B线程竞争的是“456”,此时并没有线程占用“456”的锁,于是B线程也得到了锁,就出现了如下的情况。

A begin 1537950658147
B begin 1537950658196
A end 1537950660147
B end 1537950660196

要注意的是:只要对象不变,即使对象的属性被改变,运行结果仍是同步的,也就是说把一个User对象当成锁,如果user的name属性发生变化,但是仍然是同一个user的话,仍会同步执行

八、volatile关键字

关于volatile以后写一篇专门的文章吧,现在只需要知道 volatile只保证可见性、有序性,并不保证原子性,i++并不是一个原子操作

九、原子类用不好也不完全安全

public class MyService {
    public static AtomicLong atomicLong=new AtomicLong();

    public void addNum(){
        System.out.println(Thread.currentThread().getName()+"加了100后值="+atomicLong.addAndGet(100));
        atomicLong.addAndGet(1);
    }
}
    @Override
    public void run() {
        myService.addNum();
    }
    public static void main(String[] args) {
        MyService myService=new MyService();
        AddCountThread addCountThread=new AddCountThread(myService);
        Thread t1=new Thread(addCountThread);
        Thread t2=new Thread(addCountThread);
        Thread t3=new Thread(addCountThread);
        Thread t4=new Thread(addCountThread);
        Thread t5=new Thread(addCountThread);
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }
Thread-1加了100后值=100
Thread-3加了100后值=300
Thread-2加了100后值=200
Thread-4加了100后值=403
Thread-5加了100后值=503

输出结果如上,发现并不是我们想要的结果,造成这个的原因是addAndGet()方法调用不是原子的,虽然这个方法是一个原子方法,但是两个addAndGet()方法之间的调用却不是原子的,解决这一的问题必须要用同步,在addNum方法上synchronized或者用同步代码块包括在两个addAndGet()方法外

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

推荐阅读更多精彩内容