Java中线程同步的知识及Object中wait,notify方法的使用

关于Java线程同步的基本知识

        Java中线程同步,主要通过synchronized关键字,此关键字可用于修饰方法和代码块,在实际运行过程中表示对方法和对象上加锁,被加锁的方法仅能被一个线程同时调用,一个线程调用此方法的过程中,其他线程再调用此方法时会被等待,直到调用方法结束,用于代码块时也是类似,只有代码块的对象锁被释放时,才能执行代码块,否则线程将保持等待状态。

synchronized在不同应用方式下的加锁对象:

  1. 普通同步方法(使用实例调用的非静态方法),加锁对象为当前方法的实例对象;
  2. 静态同步方法(调用类的静态方法),加锁对象为当前类对象;
  3. 静态代码块,加锁对象为括号内的对象;

执行synchronized的方法和代码块时,会对相应的锁对象加锁,以阻止其他线程调用,这样就可能得出一个结论,当一个对象中有多个方法被synchronized修饰时,这个实例对象的多个方法同一时刻仅能被一个线程调用并运行,如果是静态方法被synchronized修饰时,则同一个类的多个静态同步方法在同一个JVM的(自定义ClassLoader加载类的情况特殊考虑)中同一时间仅能被一个线程调用并运行。

线程的阻塞与通知

在实际应用场景下,经常会碰到某些线程因为资源不具备而需要等待,此时就要释放锁让其他线程进行资源生产和准备的工作,在资源具备的条件再再唤醒当前线程,常规的思路是对加锁变量进行轮询,当资源具备时进行操作,不具备时休眠一定时间,这个休眠时间可以由开发人员指定,但这种方法有一个缺点,那就是休眠时间过长时响应不及时,休眠时间过短时占用系统资源过大,那能不能在持有锁的情况下暂时释放锁,等待条件具备时由其他线程唤醒当前代码的执行吗?

Object的wait方法

这就要用到Object.wait()方法了,Java给Object对象提供了wait、notify、notifyAll方法来实现线程的等待唤醒功能,经过查看JAVA源代码,发现这3个方法都是本地方法,且都是final方法,不能被重写,那这3个方法怎么用呢?

  1. wait方法:调用加锁对象的wait方法时可以阻塞当前线程的运行,直至其他线程调用本对象的nofity方法时可以唤醒本线程并继续执行剩余的代码;
  2. notify方法:调用加锁对象的notify方法时,可以唤醒等待这个对象的其他线程(即调用了加锁对象wait方法的线程),如果有多个线程正在等待,则只能唤醒一个线程,具体唤醒哪个线程就要看JVM的线程调度了
  3. notifyAll方法:与notify方法类似,只是在调用时可以唤醒所有等待这个对象的线程,至于哪个线程可以得到加锁对象的锁,就要看开发人员对程序逻辑的控制了

需要注意的地方

  • 执行完notify和notifyAll方法后,并不会立即释放锁,需要等到synchronized块的结束或者方法执行完成后才会释放锁,其他线程才能获得锁并继续执行代码。
  • 这3个方法的调用均需要在synchronized关键字所修饰的方法或代码块中,而且要保证调用的是当前加锁对象的方法,否则会抛出异常

例子

package test;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedList;
import java.util.Queue;

public class ObjectWaitTest {
    public static Queue<String> queue = new LinkedList<String>();
    public static void main(String[] args) {
    	Producer producer = new Producer();
        new Consumer().start();
        new Consumer().start();
        producer.start();
    }
    
    //生产线程
    static class Producer extends Thread{
        @Override
        public void run() {
        	setName("Producer Thread");
            println("生产者线程已启动");
            int i=0;
            while(i++<6){
                synchronized (queue) {
            		String product = "product "+sdf.format(new Date());
					println("生产产品"+product);
                	queue.add(product);
					println("通知消费者来消费");
                	queue.notify();
                	try {
    					sleep(1000);
    				} catch (InterruptedException e) {
    					e.printStackTrace();
    				}
					println("生产者释放锁");
                }
            	try {
					sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
            }
        }

    }
     
    //消费线程
    static class Consumer extends Thread{
    	public Consumer(){
    		this.setDaemon(true);//设置为守护线程,当所有非守护线程结束后,进程结束
    	}
    	static int idx = 1;
    	int myidx;
        @Override
        public void run() {
        	setName("Consumer Thread");
        	myidx = idx++;
            println("消费者"+myidx+" 线程已启动");
        	while(true){
                synchronized (queue) {
            		try {
    					queue.wait();//等待通知,有数据时才执行下面语句
    				} catch (InterruptedException e1) {
    					e1.printStackTrace();
    				}
            		String product = queue.poll();
            		println("消费者"+myidx+" 消费产品"+product);
                }
        	}
        }
    }
    static SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
	static private void println(String string) {
		System.out.println(sdf.format(new Date())+" "+string);
	}
}

输出的结果:

08:32:43 消费者1 线程已启动
08:32:43 消费者2 线程已启动
08:32:43 生产者线程已启动
08:32:43 生产产品product 08:32:43
08:32:43 通知消费者来消费
08:32:44 生产者释放锁
08:32:44 消费者1 消费产品product 08:32:43
08:32:45 生产产品product 08:32:45
08:32:45 通知消费者来消费
08:32:46 生产者释放锁
08:32:46 消费者2 消费产品product 08:32:45
08:32:47 生产产品product 08:32:47
08:32:47 通知消费者来消费
08:32:48 生产者释放锁
08:32:48 消费者1 消费产品product 08:32:47
08:32:49 生产产品product 08:32:49
08:32:49 通知消费者来消费
08:32:50 生产者释放锁
08:32:50 消费者2 消费产品product 08:32:49
08:32:51 生产产品product 08:32:51
08:32:51 通知消费者来消费
08:32:52 生产者释放锁
08:32:52 消费者1 消费产品product 08:32:51
08:32:53 生产产品product 08:32:53
08:32:53 通知消费者来消费
08:32:54 生产者释放锁
08:32:54 消费者2 消费产品product 08:32:53

结论

由此可见,当消费线程不知道何时才会具备资源时,可以调用加锁对象的wait方法交出加锁对象的锁,让生产线程去生产资源,等待生产线程生产资源后,可以调用加锁对象的notify线程,这个例子中是每次生产一个资源,所以调用notify方法即可,如果每次生产多个资源,而同时又有多个消费线程时,可以调用notifyAll方法,通知所有消费线程去消费产品,此时也要对消费线程进行改造,获取资源时增加判断,如果没有资源时继续下一个循环并调用加锁对象的wait方法进行阻塞等待状态。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shuaijie506

您的打赏是我继续分享的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值