Synchronized及线程通信

Synchronized及线程通信

Synchronized介绍

一、Synchronized的使用

可以用来修饰方法和代码块,在多线程情况下,同一时刻只能有一个线程来访问Synchronized所修饰的方法或代码。

① Synchronized修饰代码块

    public void synchronized1(Object o) {
        //todo1
        synchronized(o){
            //todo
        }
        //todo2
    }

Synchronized加载O对象实例上,表示锁的作用在代码块上。

② Synchronized修饰静态方法

    //修饰静态方法
    public  synchronized static void synchronized2(){
        //todo
    }

Synchronized加载静态方法上,锁住的是类实例,因为class数据存储方法区,因此静态方法锁相当于类的全局锁。

③ Synchronized修饰普通方法

    //修饰普通方法
    public synchronized void synchronized3(){
        //todo
    }

锁住的是当前的实例对象。

二、Synchronized的特点

Synchronized修饰的方法或代码块,在同一时间,JVM只允许一个线程来进入,通过锁机制达到同一时刻只能一个线程访问的效率。
Synchronized是可以做到并发的有序性、原子性、可见性。

示例:统计1秒中count++的次数:

    private static  boolean flag = true;

    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " 计数线程执行开始" );
                int count = 0;
                while (isTrue()) {
                    count++;
                }
                System.out.println(Thread.currentThread().getName() + " 1秒打印count结果:" + count);
            }
        });

        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " 休眠线程执行开始");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();

                }
                Count212.setFlag(false);
                System.out.println(Thread.currentThread().getName() + " 线程睡眠时间到");

            }
        });

        thread.start();
        thread1.start();
    }



    public synchronized static  boolean isTrue(){
        return Count212.flag ;
    }

    public synchronized static  void setFlag(boolean flag) {
        Count212.flag = false;
    }

三、Synchronized的原理

研究Synchronized的使用在字节码上的特点
javac java
javap
Synchronized及线程通信
Synchronized及线程通信
大致可以看出,对于同步代码块的实现是通过monitorenter和monitorexit指令来告诉底层支持同步 ,
对于修饰方法,在方法上flag标志位上会有一个ACC_SYNCHRONIZED,
无论是哪一种形式,都是来告诉对象的监视器(Monitor)。
Synchronized及线程通信
任何的线程都是可以来抢夺对象的锁,实质上是要获取对象的Monitor对象,如果线程获取失败,则进入到同步队列,线程会进入到blocked状态,当线程抢到锁,就可以获取到对象的访问,该Monitor监视器是借助操作系统提供的互斥锁机制来准许线程的访问,这个过程会导致用户线程阻塞。

线程间通信

线程间通信的方法wait、notify和notifyAll是Object类具有的方法,即所有的对象实例都具有这个方法。
wait():当对象调用wait方法,会导致当前持有该对象锁的线程等待。直到该对象的另一个持有锁的线程调用notify、notifyAll唤醒。
notify():调用对象的notify方法,会导致持有该对象锁的所有线程中随机的某一个线程被唤醒nitifyall();调用对象的notify方法,会导致持有该对象锁的所有线程中所有线程被唤醒。

使用demo:
wait线程执行:

public class WaitDemo212 implements Runnable {
    public Object o;
    public WaitDemo212(Object o) {
        this.o = o;
    }

    @Override
    public void run() {
        //锁住代码块
        synchronized (o) {
            System.out.println("wait线程执行开始");
            try {
                //线程等待被唤醒
                o.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("wait线程执行结束");
        }
    }
}

notify线程:

public class NotifyDemo212 implements Runnable {
    private Object o;
    public NotifyDemo212(Object o) {
        this.o = o;
    }
    @Override
    public void run() {
        //锁代码块
        synchronized (o) {
            System.out.println("notify线程开始执行");
            //睡眠5秒
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //通知处于wait状态的线程
            o.notify();

            System.out.println("notify线程执行结束");

        }
    }
}

注意点1:
notify和wait方法要实现通信,必须作用于同一个对象。
调用wait方法的对象和通过Synchronized锁的对象必须是同一个对象,否则会抛出IllegalMonitorStateException异常。

演示1:调用wait方法(或者是notify方法)的对象和通过Synchronized锁的对象必须是同一个对象,否则会抛出IllegalMonitorStateException异常。
正确调用:
Synchronized及线程通信
当对象不一致时抛出异常:
Synchronized及线程通信
注意:
对于wait、notify、notifyAll的调用,必须在该对象的同步方法或者同步代码块中,锁的对象和wait等方法必须作用于同一个对象。

演示2:notify和wait方法要实现通信,必须作用于同一个对象:
Synchronized及线程通信
Synchronized及线程通信
基于分析,一旦wait先调用和则线程因为锁无法继续执行先去会一直阻塞下去?
通过运行结果可知,程序是可以执行的?为什么??
因为wait方法在调用进入阻塞之前会释放锁,则notify操作的线程就可以来抢夺Object对象锁,进而调用notify方法,唤醒wait线程,需要先获取锁才能继续执行。

锁池和等待池

锁池:假设线程A拥有了某个对象的锁,而其他的线程想要调用该对象的某个Synchronize的方法或者Synchronized块,需要这些线程线获取该对象的锁的使用权,但是对象的锁目前是线程A在使用,所以这些线程就进入到该对象的锁池。
等待池:假设一个线程A调用了某个对象的wait方法,线程A就会释放该对象的锁后,进入到该对象的等待池中。
Synchronized及线程通信
notify和notifyAll的区别:
notify():唤醒的是对象等待池中的一个线程,这个线程进入blocked状态,开始抢锁,即线程进入到锁池中等待抢锁。
notifyAll():唤醒的是对象等待池中的所有线程,所有线程进入到锁池中,等待抢锁。

生产者消费者模型

生产者消费者(producer-consumer)问题:也称之为有界缓冲区问题,两个线程共享一个公共的固定大小的缓冲区。
其中一个是生产者,用于将生产的产品放入缓冲区,另一个是消费者,从缓冲区取出产品消费
问题在于:
当缓冲区满了,此时生产者就无法往缓冲区生产产品,需要等待至少有一个空闲的缓冲区才能继续生产,空闲的缓冲区是在消费者消费后去通知唤醒等待的生产者.
当缓冲区空了,消费者就需要进入等待,等待至少一个产品才能继续消费,需要在生产者生产一个或者多个产品后通知唤醒消费者.
明确:
1、生产者生产数据到缓冲区,消费者从缓冲区消费数据
2、如果缓冲区满了,生产者线程会阻塞
3、如果缓冲区空了,消费者线程会则塞

举例子:
Synchronized及线程通信
代码实现:
编写一个生产者,一个消费者,仓库的大小是3
消费者:

/**
 * 消费者
 */
public class Producer implements Runnable {
    //仓库,容量限制为3
    private LinkedList <Integer> cap;
    private Random random =  new Random();

    public Producer(LinkedList <Integer> cap) {
        this.cap = cap;
    }

    @Override
    public void run() {
        while (true) {
            //仓库是作为生产者消费者共享区域,一个方数据,一个消费数据,需要互斥访问
            synchronized (cap) {
                try {
                    while (cap.size() == 0) {
                        //当前仓库为空,消费者需要等待生产者通知
                        System.out.println("仓库空了,消费者需要等待");
                        cap.wait();
                    }

                    //当前步骤表示仓库不为空,可以消费
                    Integer value = cap.remove();
                    System.out.println("消费者消费产品为:"+value);

                    Thread.sleep(random.nextInt(1000)+1000);//至少休眠1秒

                    //通知生产者(生产者在仓库满的情况下才会对该通知起作用,否则不起作用)
                    cap.notifyAll();

                } catch (Exception e) {
                    e.printStackTrace();
                }

            }
        }

    }
}

生产者:

/**
 * 生产者
 */
public class Consumer implements Runnable{
    //仓库,容量限制为3
    private LinkedList<Integer> cap;
    private Random random =  new Random();

    public Consumer(LinkedList<Integer> cap) {
        this.cap = cap;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (cap) {
                try {
                    //当仓库满了,需要等待,直至消费者通知
                    while (cap.size() == 3) {
                        System.out.println("仓库满了,生产者需要等待");
                        cap.wait();
                    }

                    //此处说明仓库不满,可以继续生产
                    int value = random.nextInt(1000);
                    //生产的数据放入仓库
                    cap.add(value);
                    System.out.println("生产者生产数据:"+value);

                    Thread.sleep(value);

                    //通知消费者(每生产一个数据都会通知消费者,只有在仓库为空的情况下,通知才有效)
                    cap.notifyAll();


                } catch (Exception e) {

                }
            }
        }
    }
}

产生仓库,以及生产者、消费者线程:

        //给定一个三个容量的仓库
        LinkedList <Integer> cap = new LinkedList <>();
        new Thread(new Producer(cap)).start();
        new Thread(new Consumer(cap)).start();
上一篇:防抖与节流


下一篇:GBase 8c的日期和时间类型