zoukankan      html  css  js  c++  java
  • 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() 方法。
  • 相关阅读:
    进程池,线程池,协程,gevent模块,协程实现单线程服务端与多线程客户端通信,IO模型
    线程相关 GIL queue event 死锁与递归锁 信号量l
    生产者消费者模型 线程相关
    进程的开启方式 进程的join方法 进程间的内存隔离 其他相关方法 守护进程 互斥锁
    udp协议 及相关 利用tcp上传文件 socketserver服务
    socket套接字 tcp协议下的粘包处理
    常用模块的完善 random shutil shevle 三流 logging
    day 29 元类
    Django入门
    MySQL多表查询
  • 原文地址:https://www.cnblogs.com/iadanac/p/3828822.html
Copyright © 2011-2022 走看看