Java多线程---同步

同步:就是开启多线程的时候,如果需要对同一个对象进行操作,这个时候可能会同时对其进行修改,那么需要先把这个对象进行锁定,然后进行操作,这个过程就是同步。
首先,我们想给一个int对象+1,总共加5次,初始化为0,每次输出加 1 后的值。那么我们如果在单线程的方式中我们直接一个for循环,从1加到5,即可实现。如果使用多线程不加锁的情况下,可能产生问题

public class MainThread {
    static int index=0;
    public static void main(String[] args) {
        for(int i=0;i<5;i++){
            new Thread(new Runnable() {
                public void run() {
                    index+=1;
                    System.out.println(index);
                }
            }).start();
        }
    }
}

结果可能出现错误:


加锁前的结果

1.Synchronized

仅仅使用synchronized进行锁定。

A: 锁定对象

public class MainThread {
    static int index = 0;

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                public void run() {
                   synchronized (MyConstants.lock) { //这里加锁之后只会去先去获得所锁的所有权,不然一直处于等待状态
                        index += 1;
                        System.out.println(index);
                    }
                }
            }).start();
        }
    }
}
加锁后的结果

B.锁定方法

例如:你新建了一个老师对象,有一个方法用于看学生的试卷,假设学生在任意时间都有可能在交试卷,而老师只能一次看一个,所以一个批改试卷的方法只能同步执行,如果改了一个试卷就给老师改试卷的数加1

public class Teacher {
    private int checkPaperNumber=0;

    public void checkStudentPaper(){ //没有加synchronized关键字
        checkPaperNumber+=1;
        System.out.println(checkPaperNumber);
    }
}
public class MainThread {
    static Teacher teacherWang = new Teacher();
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {    //开启10个线程
            new Thread(new Runnable() {
                public void run() {
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    teacherWang.checkStudentPaper(); //执行老师批卷子的方法
                }
            }).start();
        }
    }
}

可能出现的结果


结果

添加synchronized后:

 public synchronized void checkStudentPaper(){
image.png

C:与wait()和nitify()方法使用

假设有一个仓库WAREHOUSE,容积为10,现在有一个Producer,生产过程需要200ms,每次生产一个,放到仓库,有一个Consumer赶到仓库需要200ms,每次消费一个。现在假设有
生产线程和10个消费线程。只有一个仓库,约定:只要阻塞线程大于5,唤醒所有阻塞线程,小于5唤醒一个阻塞线程

public class MyConstants {
    public static WareHouse wareHouse = new WareHouse(10); //定义的共有仓库
    public static int thread_wait_number = 0;  //现在阻塞线程数

Producer.java

public class Producer extends Thread{
    private String name;
    public Producer(String name){
        this.name = name;
    }
    @Override
    public void run() {
        super.run();
        while(true){
            try {
                Thread.sleep(200);   //生产过程与仓库无关
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (wareHouse){   //需要向仓库记账就锁定仓库,拿出账本完成记账过程
                if(wareHouse.getCubage() <10){  //如果仓库有空余的地方那么就直接放入仓库并查看阻塞线线程数
                    wareHouse.produceGoods();
                    System.out.println("Producer Name"+name+":"+wareHouse.getCubage());
                    if(wareHouse.getCubage()<10 && wareHouse.getCubage()>0 ){
                        if( MyConstants.thread_wait_number >5) {  //阻塞线程大于5时,就可以唤醒所有线程
                            System.out.println("消费者"+name+"唤醒所有阻塞线程");
                            wareHouse.notifyAll();  
                            thread_wait_number=0;
                        }else if(MyConstants.thread_wait_number>0){  //小于5时就唤醒一个线程
                            System.out.println("消费者"+name+"唤醒单个阻塞线程");
                            wareHouse.notify();
                            thread_wait_number-=1;
                        }
                    }
                }else{
                    try {
                        MyConstants.thread_wait_number +=1;
                        System.out.println("Producer Name"+name+":"+"阻塞中...");
                        wareHouse.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

Consumer.java

public class Consumer extends Thread{
    private String name;

    public Consumer(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        super.run();
        while(true){
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (wareHouse){
                if(wareHouse.getCubage() >0){
                    wareHouse.consumerGoods();
                    System.out.println("Consumer Name"+name+":"+wareHouse.getCubage());
                    if(wareHouse.getCubage()<10 && wareHouse.getCubage()>0 ){
                        if( MyConstants.thread_wait_number >5) {
                            System.out.println("消费者"+name+"唤醒所有阻塞线程");
                            wareHouse.notifyAll();
                        }else if(MyConstants.thread_wait_number>0){
                            System.out.println("消费者"+name+"唤醒单个阻塞线程。");
                            wareHouse.notify();
                        }
                    }
                }else{
                    try {
                        MyConstants.thread_wait_number +=1;
                        System.out.println("Consumer Name"+name+":"+"阻塞中...");
                        wareHouse.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

Main函数

public class MyThread {
    public static void main(String[] args) {
        for(int i=0;i<10;i++){
            Producer producer = new Producer(String.valueOf(i));
            producer.start();
        }
        for(int i=0;i<10;i++){
            Consumer consumer = new Consumer(String.valueOf(i));
            consumer.start();
        }

    }
}

运行结果:

image.png

总结:synchronized关键字是获取对象的锁,当自己代码所包裹的区域执行完毕可以释放对对象的控制权,而wait()方法是将对象的持有权放弃,并将本线程放入阻塞线程中。notify()方法是将想持有本对象的所有阻塞线程中的一个唤醒,notifyAll()是唤醒所有阻塞线程。光使用synchronized关键字是可以达到目的的,但是可能造成资源的浪费,长期霸占CPU运行时间,也可能因为持有其他对象导致其他线程无法正常运行。

2.Volatile关键字

一个变量int count=0,开启5个线程,每个线程都等待count的值不等于1的时候,另开启一个线程用于改变count的值

public class VolatileThread {
    private static int count = 0;
    public static void main(String[] args) throws InterruptedException {
        for(int i=0;i<5;i++){
            new Thread(new Runnable() {
                public void run() {
                    while(count == 0){   //监听count的值是否为

                    }
                    System.out.println(Thread.currentThread().getName()+"执行完毕");
                }
            }).start();
        }
        Thread.sleep(200);
        new Thread(new Runnable() {    //用于改变count的值
            public void run() {
                count +=1;
            }
        }).start();
    }
}

结果一直处于运行
private volatile static int count = 0;                  //添加volatile关键字后
image.png

可使用的场景多个线程读,一个线程写。

3.ReetrantLock解析

1.简述

1.方法lock()

直接锁定声明的lock对象,如果锁对象可用就直接锁定,不可用进入等待,线程阻塞

2.方法unlock()

接触锁定的锁,没有锁定锁而进行的unlock会报错

[图片上传失败...(image-6da169-1552810272527)]

3.方法tryLock()

获取锁的状态,如果锁可用直接锁定,不可用直接返回不等待,不用再进行锁定,tryLcok返回true后直接就已经锁定了锁对象

4.方法tryLock(时间,时间单位)

在给定的时间内去等待锁如果锁获取到就执行下面的东西,没有在给定时间内尝试获得锁,过了时间就直接返货false不在继续等待

同样的同步的前提是锁定同样一个锁

5.Condition

1.Lock.newCondition

创建一个condition

2.Condition的阻塞和释放

MyReentrantLock程序有两个方法,1.对count+1 2.对count-1

约定如果加方法可以获取到锁,那么就判断是否现在的总数大于10, 1.大于10对addcondition进行阻塞。2.小于10,对总数加一并且释放一个减的程序,同样的减程序如果发现总数小于0,那么阻塞,否则减一并释放一个加程序

public class MyReentrantLock {

    public void addMethod(){
        try{
            while(true) {
                if (LockTest.lock.tryLock(100,TimeUnit.SECONDS)) {
                    TimeUnit.SECONDS.sleep(1);
                    if (LockTest.count >= 10) {
                        System.out.println(Thread.currentThread().getName()+":"+"加----功能阻塞");
                        LockTest.addcondition.await();
                    }else {
                        LockTest.count++;
                        if(LockTest.count>0){
                            System.out.println(Thread.currentThread().getName()+":"+"释放减功能");
                            LockTest.mincondition.signal();
                        }
                        System.out.println(Thread.currentThread().getName()+":"+LockTest.count);
                        LockTest.lock.unlock();
                    }
                } else {
                    System.out.println("没获取到锁");
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public void minMethod(){
        try{
            while(true) {
                if (LockTest.lock.tryLock(100,TimeUnit.SECONDS)) {
                    TimeUnit.SECONDS.sleep(1);
                    if (LockTest.count < 0) {
                        System.out.println(Thread.currentThread().getName()+":"+"减---开始阻塞!");
                        LockTest.mincondition.await();
                    }
                    else {
                        LockTest.count--;
                        if(LockTest.count<10) {
                            System.out.println(Thread.currentThread().getName()+":"+"释放加功能");
                            LockTest.addcondition.signal();
                        }
                        System.out.println(Thread.currentThread().getName()+":"+LockTest.count);
                        LockTest.lock.unlock();
                    }
                } else {
                    System.out.println("没获取到锁");
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

我们开启5个线程用于加开启5个线程用于减

public class LockTest {
 public static ReentrantLock lock = new ReentrantLock();
 public static Condition addcondition = lock.newCondition();
 public static Condition mincondition = lock.newCondition();
 public static int count=0;
 public static void main(String[] args) {
​
 for(int i=0;i<5;i++){
 new Thread(new Runnable() {
 @Override
 public void run() {
 MyReentrantLock myReentrantLock = new MyReentrantLock();
 myReentrantLock.addMethod();   //add
 }
 }).start();
 }
 for(int i=0;i<5;i++){
 new Thread(new Runnable() {
 @Override
 public void run() {
 MyReentrantLock myReentrantLock = new MyReentrantLock();
 myReentrantLock.minMethod();   //min
 }
 }).start();
 }
 }
}

2.ReentrantLock与synchronized关键字的区别

1、增加了tryLock方法不至于一直使程序进入等待状态

2、可实现公平锁:多个线程在等待同一个锁时,必须按照申请锁的时间顺序排队等待,而非公平锁则不保证这点,在锁释放时,任何一个等待锁的线程都有机会获得锁。synchronized中的锁时非公平锁,ReentrantLock默认情况下也是非公平锁,但可以通过构造方法ReentrantLock(ture)来要求使用公平锁。

3、可以声明多个condition每个condition直接来源于lock的newCondition(),不用像synchronized去先使用synchronized锁定这个对象然后再执行wait方法

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

推荐阅读更多精彩内容