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();
 }
 }
 }
 }
 }

你可能感兴趣的:(Java多线程---同步)