• Java多线程


    进程:正在进行中的程序。其实进程就是一个应用程序运行时的内存分配空间。进程负责的是应用程序的空间的标示。

    线程:其实就是进程中一个程序执行控制单元,一条执行路径。线程负责的是应用程序的执行顺序。

    • 一个进程至少有一个线程在运行,当一个进程中出现多个线程时,就称这个应用程序是多线程应用程序。
    • 每个线程在栈区中都有自己的执行空间,自己的方法区、自己的变量。

    jvm在启动的时,首先有一个主线程,负责程序的执行,调用的是main函数,主线程执行的代码都在main方法中。当产生垃圾时,收垃圾的动作,是不需要主线程来完成,因为这样主线程中的代码执行会停止,而去运行垃圾回收器代码,效率较低,所以由单独一个线程来负责垃圾回收。 

    随机性的原理:哪个线程获取到了cpu的执行权,哪个线程就执行,实质是cpu的快速切换造成。

    返回当前线程的名称:Thread.currentThread().getName();线程的名称是由:Thread-编号定义的。编号从0开始。线程要运行的代码都统一存放在了run方法中。

    线程要运行必须要通过类中指定的方法【start方法】开启。(启动后,就多了一条执行路径)

    start方法

    1. 启动了线程
    2. 让jvm调用了run方法。

    创建线程的第一种方式:继承Thread ,由子类复写run方法:

    步骤:

    1. 定义类继承Thread类;
    2. 目的是复写run方法,将要让线程运行的代码都存储到run方法中;
    3. 通过创建Thread类的子类对象,创建线程对象;
    4. 调用线程的start方法,开启线程,并执行run方法。
        class Show extends Thread   
        {  
            public void run()  
            {  
                for (int i =0;i<5 ;i++ )  
                {  
                    System.out.println(name +"_" + i);  
                }  
            }  
          
            public Show(){}  
            public Show(String name)  
            {  
                this.name = name;  
            }  
            private String name;  
          
            public static void main(String[] args)   
            {  
                new Show("csdn").start();  
                new Show("黑马").start();  
            }  
        }  
    

    【可能的运行结果】:

    为什么我们不能直接调用run()方法呢?原因是线程的运行需要本地操作系统的支持。

    查看start的源代码发现:

        public synchronized void start() {  
             
               if (threadStatus != 0)  
                   throw new IllegalThreadStateException();  
          
               group.add(this);  
          
               boolean started = false;  
               try {  
                   start0();  
                   started = true;  
               } finally {  
                   try {  
                       if (!started) {  
                           group.threadStartFailed(this);  
                       }  
                   } catch (Throwable ignore) {  
                       /* do nothing. If start0 threw a Throwable then 
                         it will be passed up the call stack */  
                   }  
               }  
           }  
          
        private native void start0();  
    

    这个方法用了native关键字,native表示调用本地操作系统的函数,多线程的实现需要本地操作系统的支持。

    线程状态

    • 被创建:start()
    • 运行:具备执行资格,同时具备执行权;
    • 冻结:sleep(time),wait()—notify()唤醒;线程释放了执行权,同时释放执行资格;
    • 临时阻塞状态:线程具备cpu的执行资格,没有cpu的执行权;
    • 消亡:stop()

    创建线程的第二种方式:实现一个接口Runnable:

    步骤:

    1. 定义类实现Runnable接口。
    2. 覆盖接口中的run方法(用于封装线程要运行的代码)。
    3. 通过Thread类创建线程对象;
    4. 将实现了Runnable接口的子类对象作为实际参数传递给Thread类中的构造函数。【为什么要传递呢?因为要让线程对象明确要运行的run方法所属的对象】
    5. 调用Thread对象的start方法,开启线程,并运行Runnable接口子类中的run方法。

    Ticket t = new Ticket();

    直接创建Ticket对象,并不是创建线程对象。【因为创建线程对象只能通过new Thread类,或者new Thread类的子类才可以

    Thread t1 = new Thread(t); //创建线程。

    只要将t作为Thread类的构造函数的实际参数传入即可完成线程对象和t之间的关联。【为什么要将t传给Thread类的构造函数呢?其实就是为了明确线程要运行的代码run方法】

    t1.start();//开启线程

    为什么要有Runnable接口的出现?

    1:通过继承Thread类的方式,可以完成多线程的建立。但是这种方式有一个局限性,如果一个类已经有了自己的父类,就不可以继承Thread类,因为java单继承的局限性。

    可是该类中的还有部分代码需要被多个线程同时执行,这时怎么办呢?

    只有对该类进行额外的功能扩展,java就提供了一个接口Runnable。这个接口中定义了run方法,其实run方法的定义就是为了存储多线程要运行的代码。

    所以,通常创建线程都用第二种方式。【因为实现Runnable接口可以避免单继承的局限性】

    2:其实是将不同类中需要被多线程执行的代码进行抽取。将多线程要运行的代码的位置单独定义到接口中。为其他类进行功能扩展提供了前提。

    所以Thread类在描述线程时,内部定义的run方法,也来自于Runnable接口。

    实现Runnable接口可以避免单继承的局限性。而且,继承Thread,是可以对Thread类中的方法,进行子类复写的。但是不需要做这个复写动作的话,只为定义线程代码存放位置,实现Runnable接口更方便一些。所以Runnable接口将线程要执行的任务封装成了对象。

    new Thread(new Runnable(){  //匿名
    public void run()
    {
        System.out.println("runnable run");
    }
    })
    
    {
    public void run()
    {
        System.out.println("subthread run");
    }
    
    }.start();  //结果:subthread run
    
    Try {
    Thread.sleep(10);
    }catch(InterruptedException e){}// 当刻意让线程稍微停一下,模拟cpu 切换情况。
    

    Thread和Runnable的区别:

    如果一个类继承Thread,则不能资源共享(有可能是操作的实体不是唯一的);但是如果实现了Runable接口的话,则可以实现资源共享。

        class Show implements Runnable  
        {  
            private int count = 10;//假设有10张票  
            @Override  
            public void run()  
            {  
                for (int i = 0; i < 5 ; i++ )  
                {  
                    if (this.count > 0)  
                    {  
                        System.out.println(Thread.currentThread().getName()+"正在卖票" + this.count--);  
                    }  
                }  
            }  
          
            public static void main(String[] args)   
            {  
                Show s = new Show(); //注意必须保证只对1个实体s操作  
                new Thread(s,"窗口1").start();  
                new Thread(s,"窗口2").start();  
                new Thread(s,"窗口3").start();  
            }  
        }  
    

    实现Runnable接口比继承Thread类所具有的优势:

    • 适合多个相同的程序代码的线程去处理同一个资源
    • 可以避免java中的单继承的限制
    • 增加程序的健壮性,代码可以被多个线程共享,代码和数据独立。

    设置线程优先级

    Thread t = new Thread(myRunnable);  
    t.setPriority(Thread.MAX_PRIORITY);//一共10个等级,Thread.MAX_PRIORITY表示最高级10  
    t.start();

    //MAX_PRIORITY : 其值是 10 
    //MIN_PRIORITY : 其值是 1
    //NORM_PRIORITY: 其值是 5

    提示:主线程的优先级是5,不要误以为优先级越高就先执行。谁先执行还是取决于谁先去的CPU的资源

    控制线程的方法:

    • join方法:假如你在A线程中调用了B线程的join方法B.join();,这时B线程继续运行,A线程停止(进入阻塞状态)。等B运行完毕A再继续运行。
    • sleep方法:线程中调用sleep方法后,本线程停止(进入阻塞状态),运行权交给其他线程。
    • yield方法:线程中调用yield方法后本线程并不停止,运行权由本线程和优先级不低于本线程的线程来抢。(不一定优先级高的能先抢到,只是优先级高的抢到的时间长)
    package heimablog;
    
    // 定义Runnable 接口的实现类
    public class JoinTest implements Runnable {
    	// 重写run() 方法
    	public void run() {
    		for (int i = 0; i <= 10; ++i)
    			System.out.println(Thread.currentThread().getName() + "..." + i);
    	}
    
    	public static void main(String args[]) throws InterruptedException {
    		// 创建Runnable 接口实现类的实例
    		JoinTest t = new JoinTest();
    		// 通过 Thread(Runnable target) 创建新线程
    		Thread thread = new Thread(t);
    		Thread thread1 = new Thread(t);
    		// 启动线程
    		thread.start();
    		// 只有等thread 线程执行结束,main 线程才会向下执行;
    		thread.join();
    		System.out.println("thread1 线程将要启动");
    		thread1.start();
    	}
    } 
    • wait方法:当前线程转入阻塞状态,让出cpu的控制权,解除锁定。
    • notify方法:唤醒因为wait()进入阻塞状态的其中一个线程。
    • notifyAll方法: 唤醒因为wait()进入阻塞状态的所有线程。

    这三个方法都必须用synchronized块来包装,而且必须是同一把锁,不然会抛出java.lang.IllegalMonitorStateException异常。

    多线程安全问题的原因:

    发现一个线程在执行多条语句时,并运算同一个数据时,在执行过程中,其他线程参与进来,并操作了这个数据,导致了错误数据的产生。

    涉及到两个因素:

    1. 多个线程在操作共享数据。
    2. 有多条语句对共享数据进行运算。

    如下面程序:

    package heimablog;
    
    /* 
     * 多个线程同时访问一个数据时,出现的安全问题。
     * 模拟一个卖火车票系统:一共有100张票,多个窗口同时卖票
     */
    class Ticks implements Runnable 
    {
        private int ticks = 100 ; 
        public void run()
        {
            while (ticks > 0)
            {
            	//加入sleep 方法是为了更明显的看到该程序中出现的安全问题。
                try{Thread.sleep(10);}catch (Exception e) {} 
                System.out.println(Thread.currentThread().getName()
                        +"...卖出了第"+ticks+"张票");
                ticks -- ;
            }
        }
    }
    public class ShowTest {
        public static void main(String args[])
        {
        	//创建 Runnable 实现类 Ticks 的对象。
            Ticks t = new Ticks() ;  
            //开启4个线程处理同一个 t 对象。
            new Thread(t , "一号窗口").start() ; 
            new Thread(t , "二号窗口").start() ; 
            new Thread(t , "三号窗口").start() ; 
            new Thread(t , "四号窗口").start() ; 
        }
    }
    

    运行的结果会出现"一号窗口...卖出了第0张票,四号窗口...卖出了第-1张票,二号窗口...卖出了第-2张票"这样的安全问题。

    解决安全问题的原理

    只要将操作共享数据的语句在某一时段让一个线程执行完,在执行过程中,其他线程不能进来执行就可以解决这个问题。

    解决这类问题的方法:

      1、同步代码块。

      2、同步函数。

    同步代码块

    synchronized(obj)  // 任意对象都可以,这个对象就是锁。
    {
        //此处的代码就是同步代码块,也就是需要被同步的代码;
    }
    

    synchronized 后括号里面的 obj 就是同步监视器。代码含义:线程开始执行同步代码块之前,必须先获得对同步监视器的锁定。即只有获得对同步监视器的锁定的线程可以在同步中执行,没有锁定的线程即使获得执行权,也不能在同步代码块中执行。

    注意:虽然JAVA 程序允许使用任何对象来作为同步监视器。但是还是推荐使用可能被并发访问的共享资源来充当同步监视器。

    修改代码如下:

    package heimablog;
    
    /* 
     * 多个线程同时访问一个数据时,出现的安全问题。
     * 模拟一个卖火车票系统:一共有100张票,多个窗口同时卖票
     */
    class Ticks implements Runnable {
    	private int ticks = 100;
    
    	public void run() {
    		while (ticks > 0) {
    			synchronized (Ticks.class) {
    				if (ticks > 0) {
    					try {
    						Thread.sleep(10);
    					} catch (Exception e) {}
    					System.out.println(Thread.currentThread().getName()
    							+ "...卖出了第" + ticks + "张票");
    					ticks--;
    				}
    			}
    		}
    	}
    }
    
    public class ShowTest {
    	public static void main(String args[]) {
    		// 创建 Runnable 实现类 Ticks 的对象。
    		Ticks t = new Ticks();
    		// 开启4个线程处理同一个 t 对象。
    		new Thread(t, "一号窗口").start();
    		new Thread(t, "二号窗口").start();
    		new Thread(t, "三号窗口").start();
    		new Thread(t, "四号窗口").start();
    	}
    }
    

    加入同步监视器之后的程序就不会出现数据上的错误了。

      虽然同步监视器的好处是解决了多线程的安全问题。但也也因为多个线程需要判断锁,较为消耗资源。

    同步前提:

    1. 必须要有两个或者两个以上的线程,才需要同步。
    2. 多个线程必须保证使用的是同一个锁。

    如果加入了synchronized 同步监视器,还出现了安全问题,则可以按照如下步骤找寻错误:

    1. 明确那些代码是多线程代码。
    2. 明确共享数据。
    3. 明确多线程运行代码中那些代码是操作共享数据的。

    同步函数

      把 synchronized 作为修饰符修饰函数。则该函数称为同步函数。

      注意:同步函数无需显示的指定同步监视器,函数都有自己所属的对象this,同步函数的同步监视器是this,也就是该对象本身。

      注意:synchronized 关键字可以修饰方法,可以修饰代码块,但不能修饰构造器、属性等。

    上面通过模拟火车卖票系统的小程序,通过加入 synchronized 同步监视器,来解决多线程中的安全问题。下面模拟银行取钱问题,通过同步函数来解决多线程的安全问题。

    package heimablog;
    
    class Account {
    	// 账户余额
    	private double balance;
    
    	public Account(double balance) {
    		this.balance = balance;
    	}
    
    	// get和set方法
    	public double getBalance() {
    		return balance;
    	}
    
    	public void setBalance(double balance) {
    		this.balance = balance;
    	}
    
    	// 同步函数
    	// 提供一个线程安全的draw的方法来完成取钱操作。
    	public synchronized void Draw(double drawAmount) {
    		if (balance >= drawAmount) {
    			System.out.println(Thread.currentThread().getName() + "取钱成功!吐出金额"
    					+ drawAmount);
    
    			try {
    				Thread.sleep(10);
    			} catch (Exception e) {
    			}
    			balance -= drawAmount;
    			System.out.println("卡上余额:" + balance);
    		} else {
    			System.out.println(Thread.currentThread().getName() + "取钱失败!卡上余额:"
    					+ balance);
    		}
    	}
    }
    
    class DrawThread implements Runnable {
    	// 模拟账户
    	private Account account;
    	// 希望所取钱的金额
    	private double drawAmount;
    
    	private boolean flag = true;
    
    	public DrawThread(Account account, double drawAmount) {
    		this.account = account;
    		this.drawAmount = drawAmount;
    	}
    
    	// 当前取钱
    	public void run() {
    		try {
    			while (flag) {
    				// 调用取钱函数
    				account.Draw(drawAmount);
    			}
    		} catch (Exception e) {
    			flag = false;
    		}
    	}
    }
    
    public class ShowTest {
    	public static void main(String args[]) {
    		Account account = new Account(10000);
    		DrawThread draw = new DrawThread(account, 50);
    		Thread t = new Thread(draw, "A.....");
    		Thread t1 = new Thread(draw, "B.");
    		t.start();
    		t1.start();
    	}
    }
    

    同步方法的监视器是 this ,因此对于同一个 Account 而言,任意时刻只能有一条线程获得 Account 对象的锁定。

    提示:可变类的线程安全是以降低运行程序的运行效率作为代价,为了减少线程安全所带来的负面影响,程序可以采用如下策略:

    • 只对会改变竞争资源的方法进行同步。
    • 在两个或两个以上的线程操作同一个锁的环境中使用同步。

    当如下情况发生时会释放同步监视器的锁定:

    • 当前线程的同步方法、同步代码块执行结束。
    • 当前线程的同步方法、同步代码块中遇到break 、 return终止了该代码块、该方法的继续执行。
    • 当前线程的同步方法、同步代码块出现了未处理的Error或Exception,导致该代码块、该方法异常结束时会释放同步锁。
    • 当线程执行同步方法、同步代码块,程序执行了同步监视器对象的wait() 方法时。

    当同步函数被static修饰时,这时的同步用的是哪个锁呢?

    静态函数在加载时所属于类,这时有可能还没有该类产生的对象,但是该类的字节码文件加载进内存就已经被封装成了对象,这个对象就是该类的字节码文件对象。

    所以静态加载时,只有一个对象存在,那么静态同步函数就使用的这个对象。这个对象就是 类名.class

    同步代码块和同步函数的区别?

    • 同步代码块使用的锁可以是任意对象。
    • 同步函数使用的锁是this,静态同步函数的锁是该类的字节码文件对象。

    在一个类中只有一个同步,可以使用同步函数。如果有多同步,必须使用同步代码块,来确定不同的锁。所以同步代码块相对灵活一些

    死锁

      当两线程相互等待对方释放锁时,就会发生死锁。由于JVM没有监测,也没有采用措施来处理死锁,所以多线程编成时应该采取措施来避免死锁。

    单例模式之懒汉式

    懒汉式:延迟加载方式。

    当多线程访问懒汉式时,因为懒汉式的方法内对共性数据进行多条语句的操作,所以容易出现线程安全问题。

    • 为了解决安全问题,加入同步机制。但是却带来了效率降低。
    • 为了解决效率问题,通过双重判断的形式解决。
    class Single {
    
    	private static Single s = null;
         private Single() {
    	
         } public static Single getInstance() { if (s == null) { synchronized (Single.class) {//用字节码文件对象作为锁; if (s == null) s = new Single(); } } return s; } }

    同步死锁:通常只要将同步进行嵌套,就可以看到现象。

    线程通信

    思路:多个线程在操作同一个资源,但是操作的动作却不一样。

    1. 将资源封装成对象。
    2. 将线程执行的任务(任务其实就是run方法。)也封装成对象。

    模拟生产消费者:系统在有两条线程,分别代表生成者和消费者。

      程序的基本流程:

    1. 生成者生产出一件商品。
    2. 消费者消费生成出的商品。

      通过上诉流程要了解:生成者和消费者不能连续生成或消费商品,同时消费者只能消费已经生产出的商品。

    package heimablog;
    
    class Phone {
    	// 定义商品的编号
    	private int No;
    	// 定义商品的名字
    	private String name;
    	private boolean flag = true;
    
    	// 初始化商品的名字和编号,同时编号是自增的
    	public Phone(String name, int No) {
    		this.name = name;
    		this.No = No;
    	}
    
    	// 定义商品中的消费方法和生产方法。用synchronized 修饰符修饰
    	public synchronized void Production() {
    
    		// 导致当前线程等待,知道其他线程调用notify()或notifyAll()方法来唤醒
    		// if (!flag)
    		while (!flag)
    			try {
    				this.wait();
    			} catch (Exception e) {
    			}
    		System.out.println(Thread.currentThread().getName() + ":生产" + name
    				+ ";编号为:" + ++No);
    		// 唤醒在此同步监视器上等待的单个线程。
    		// this.notify() ;
    		// 唤醒在此同步监视器上等待的所有线程。
    		this.notifyAll();
    		flag = false;
    	}
    
    	public synchronized void Consumption() {
    		// if(flag)
    		while (flag)
    			try {this.wait();} catch (Exception e) {}
    		System.out.println(Thread.currentThread().getName() + ";消费商品:" + name
    				+ "商品的编号为" + No);
    		// this.notify() ;
    		this.notifyAll();
    		flag = true;
    	}
    }
    
    class ProducerThread implements Runnable {
    	Phone phone;
    	private boolean flag = true;
    
    	// 同步监视器的对象
    	public ProducerThread(Phone phone) {
    		this.phone = phone;
    	}
    
    	public void run() {
    		try {
    			while (flag)
    				phone.Production();
    		} catch (Exception e) {
    			flag = false;
    		}
    	}
    }
    
    class ConsumptionThread implements Runnable {
    	Phone phone;
    	private boolean flag = true;
    
    	// 同步监视器的对象
    	public ConsumptionThread(Phone phone) {
    		this.phone = phone;
    	}
    
    	public void run() {
    		try {
    			while (flag)
    				phone.Consumption();
    		} catch (Exception e) {
    			flag = false;
    		}
    	}
    }
    
    public class ShowTest {
    	public static void main(String args[]) {
    		Phone phone = new Phone("iPhone 5", 0);
    		new Thread(new ProducerThread(phone), "生成者000").start();
    		new Thread(new ProducerThread(phone), "生成者111").start();
    		new Thread(new ConsumptionThread(phone), "消费者000").start();
    		new Thread(new ConsumptionThread(phone), "消费者111").start();
    	}
    }
    

    上面的程序中:flag 标志位 是判断 是由生产者生成还是由消费者进行消费。其实,在现实生活中,不可能只有一个生成者和消费者,而是多个生成者和消费者。所以用 while 循环来进行 flag 的判断 , 而不是用 if 。如果用if 容易出现线程安全问题;而且在用while 循环进行flag的判断时,则必须用 notifyAll() 方法来唤醒同步监视器中所有等待中的线程,而不是 用notify() 方法。用notify()  则会导致所有线程进入等待状态。

    上面的小程序借助Object 类提供的 wait()、notify()、notifyAll 三个方法【等待唤醒机制涉及的方法】

    • wait() :导致当前线程等待,知道其他线程调用该同步监视器的notify()或notifyAll() 方法来唤醒线程。
    • notify() : 唤醒在此同步监视器上等待的单个线程。如果所有线程都在此同步监视器上等待,则会选择一个其中一个唤醒。
    • notifyAll() :唤醒此同步监视器上等待的所有单个线程。

    注意

    • 这三个方法必须用同步监视器对象来调用:
      1. 同步函数:因为该类的默认实例(this)就是同步监视器,所以可以在同步方法中直接调用。
      2. 同步代码块:必须使用 synchronized 括号中的对象来调用。
    • 这些方法都需要定义在同步中:因为这些方法必须要标示所属的锁。你要知道 A锁上的线程被wait了,那这个线程就相当于处于A锁的线程池中,只能A锁的notify唤醒。
    • 这三个方法都定义在Object类中。为什么操作线程的方法定义在Object类中?因为这三个方法都需要定义同步内,并标示所属的同步锁,既然被锁调用,而锁又可以是任意对象,那么能被任意对象调用的方法一定定义在Object类中。

    wait和sleep区别

    分析这两个方法从执行权和锁上来分析:

    wait:可以指定时间也可以不指定时间。不指定时间,只能由对应的notify或者notifyAll来唤醒。

    sleep:必须指定时间,时间到自动从冻结状态转成运行状态(临时阻塞状态)。

    wait:线程会释放执行权,而且线程会释放锁。

    Sleep:线程会释放执行权,但不是不释放锁。

    线程的停止【stop方法已过时】

    原理:让线程运行的代码结束,也就是结束run方法。

    怎么结束run方法?一般run方法里肯定定义循环。所以只要结束循环即可。

    • 第一种方式:定义循环的结束标记。
    • 第二种方式:如果线程处于了冻结状态,是不可能读到标记的,这时就需要通过Thread类中的interrupt方法,将其冻结状态强制清除。让线程恢复具备执行资格的状态,让线程可以读到标记,并结束。

    Thread's functions

    • interrupt():中断线程。
    • setPriority(int newPriority):更改线程的优先级。
    • getPriority():返回线程的优先级。
    • toString():返回该线程的字符串表示形式,包括线程名称、优先级和线程组。
    • Thread.yield():暂停当前正在执行的线程对象,并执行其他线程。
    • setDaemon(true):将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。该方法必须在启动线程前调用。
    • join:临时加入一个线程的时候可以使用join方法。当A线程执行到了B线程的join方式。A线程处于冻结状态,释放了执行权,B开始执行。A什么时候执行呢?只有当B线程运行结束后,A才从冻结状态恢复运行状态执行。

    同步锁LOCK

      JDK 1.5之后,JAVA提供了另外一种线程同步机制:显示定义同步锁来实现同步,解决线程安全问题使用同步的形式,(同步代码块,要么同步函数)其实最终使用的都是锁机制。同步锁应该使用Lock对象充当。在面向对象中谁拥有数据谁就对外提供操作这些数据的方法 ,发现获取锁,或者释放锁的动作应该是锁这个事物更清楚。所以将这些动作定义在了锁当中,并把锁定义成对象。线程进入同步就是具备了锁,执行完,离开同步,就是释放了锁。

    class X {
    	// 定义锁对象
    	private final ReentrantLock lock = new ReentrantLock();
    
    	// 定义需要保证线程安全的方法
    	public void m() {
    		// 加锁
    		lock.lock();
    		try {
    			// 需要保证线程安全的代码
    		} finally {
    			lock.unlock();
    		}
    	}
    }
    

    所以同步是隐示的锁操作,而Lock对象是显示的锁操作,它的出现就替代了同步。

    现在锁是指定对象Lock。所以查找等待唤醒机制方式需要通过Lock接口来完成。而Lock接口中并没有直接操作等待唤醒的方法,而是将这些方式又单独封装到了一个对象中。这个对象就是Condition,将Object中的三个方法进行单独的封装,并提供了功能一致的方法 await()、signal()、signalAll().

    Condition接口:await()、signal()、signalAll();

    Condition 实例实质上被绑定在一个Lock 对象上。如:

    //定义锁对象
    private final ReentrantLock lock = new ReentrantLock() ; 
    //指定Lock 对象对应的条件变量
    private final Condition condition = lock.newCondition() ; 
    
    • await() : 类似 wait() 方法。
    • signal() : 类似 notify() 方法。
    • signalAll() : 类似 notifyAll() 方法。
  • 相关阅读:
    【分布式锁】RedLock 实现分布式锁
    【反射】遍历对象属性名与值
    【ABP.Net】2.多数据库支持&&初始化数据库
    【ABP.Net】1.创建项目&介绍框架结构
    【Vue-Cli3.0】【2】渲染
    【nuget】PackageReference
    【Vue-Cli3.0】【1】创建一个Vue-Cli3.0的项目
    【干货】干货篇
    踩坑记录-Redis(Windows)的getshell
    极致CMS存储XSS|前台打后台COOKIE漏洞复现
  • 原文地址:https://www.cnblogs.com/iadanac/p/3828822.html
Copyright © 2020-2023  润新知