Java学习笔记-Day37 Java 多线程(二)



一、Thread常用的方法

(1)public static Thread currentThread():返回的是当前正在执行的线程。

(2)public Thread.State getState():返回此线程的状态。

(3)public long getId():返回此线程ID。 线程ID是创建此线程时生成的正数long值。 线程ID是唯一的,并且在其生命周期内保持不变。 当线程被终止时,该线程ID可以被重用。

(4)public final void join() throws InterruptedException:让当前线程等待,直到调用join方法的线程结束后,当前线程才能继续运行。通常用于在main()主线程内,等待其它线程完成再结束main()主线程

	public class TestJoin {
    
    
		public static void main(String[] args) throws InterruptedException {
    
    
			JoinThread jt = new JoinThread();
			jt.start();
			
			for (int i = 0; i < 10; i++) {
    
    
				if(i == 5) {
    
    
					jt.join();
				}
				System.out.println(Thread.currentThread().getName()+":"+i);
			}
		}
	}
	
	class JoinThread extends Thread{
    
    
		@Override
		public void run() {
    
    
			for (int i = 0; i < 10; i++) {
    
    
				try {
    
    
					Thread.sleep(1000);
				} catch (InterruptedException e) {
    
    
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName()+":"+i);
			}
		}
	}

二、线程的种类

1、守护线程


守护线程是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程就是一个守护线程,并且这种线程并不属于程序中不可或缺的部分。因此,当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。反过来说,只要任何非守护线程还在运行,程序就不会终止。

将线程转换为守护线程可以通过调用Thread对象的 setDaemon(true) 方法来实现(默认守护线程的属性为false,即默认创建的线程对象为非守护的用户线程)。

public final void setDaemon(boolean on):将此线程标记为daemon线程或用户线程。 当运行的唯一线程都是守护进程线程时,Java虚拟机将退出。

thread.setDaemon(true) 必须在 thread.start() 之前设置,否则会抛出一个IllegalThreadStateException异常,因为不能把正在运行的常规线程设置为守护线程。

在守护线程中产生的新线程也是守护线程。

不是所有的应用都可以分配给守护线程来进行服务,比如读写操作或者计算逻辑。因为在守护线程还没来的及进行操作时,虚拟机可能已经退出了

可以通过线程对象的 isDaemon() 方法判定该线程是否守护线程。

public final boolean isDaemon():测试这个线程是否是守护线程。
结果如果是 true 表示这个线程是一个守护线程; 如果是 false 表示不是守护线程。

2、用户线程


平时用到的普通线程均是用户线程,当在Java程序中创建一个线程,它就被称为用户线程。

用户线程和守护线程两者几乎没有区别,唯一的不同之处就在于虚拟机的退出:如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。 因为没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了。

三、线程的名字


每个线程可以存在一个给定的名字,线程名字除了Thread类的构造方法可以提供以外,也可以通过 setName(String name) 方法给定,通过 getName() 方法可以获取特定线程的名字。如果没有为线程显式提供名字,Java将按照 thread-0,thread-1…thread-n 的方式为线程提供默认的名字(由main方法启动的主线程名字为:main)。

public final void setName(String name):将此线程的名称更改为等于参数name 。

public final String getName():返回此线程的名称。

四、线程的优先级


Java 线程有优先级,高优先级的线程比低优先级的线程有更高的几率得到执行(注意:优先级高只是拥有更高的几率得到执行,而不是优先级高就先执行,但是优先级在某一些线程调度方法中有特定的作用)。

Java线程的优先级是一个整数,其取值范围是 Thread.MIN_PRIORITY (1) 到 Thread.MAX_PRIORITY (10)。除了 Thread.MIN_PRIORITYThread.MAX_PRIORITY 外,Thread 还提供了另一个常量 Thread.NORM_PRIORITY (5) ,但是需要注意的是,通过对Thread类源码的解析,会发现事实上Java线程在没有明确指定的情况下,其优先级并不一定是 NORM_PRIORITY (5),而是和父线程(创建本线程的线程)的优先级保持一致,main线程的优先级是 NORM_PRIORITY (5) 。

可以通过Thread类的setPriority()方法更改优先级。

public final void setPriority(int newPriority):更改此线程的优先级。

可以通过Thread类的getPriority()方法h获取当前线程的优先级。

public final int getPriority():返回此线程的优先级。

优先级不能超出1-10的取值范围,否则抛出 IllegalArgumentException,建议使用Thread提供的三个优先级常量来确定线程优先级的高取值、常规取值和低取值。

如果该线程已经属于一个线程组(ThreadGroup),该线程的优先级不能超过该线程组的优先级。

五、使用方法退出运行状态


线程可以通过调用特殊方法来退出运行状态,放弃自己占据的部分资源。

1、sleep方法


sleep方法使当前线程进入阻塞状态,执行sleep()的线程在指定的时间内肯定不会执行, 同时sleep方法不会释放锁资源(即如果正在运行的线程占有某个资源的同步锁,它不会释放掉这个同步锁,其他线程仍然不能访问该资源)。

sleep方法可使优先级低、同优先级和高优先级的线程有执行的机会。

sleep方法有两个版本:sleep(long millis)和sleep(long millis,int nanos),第一个版本需要提供以毫秒为单位的阻塞时间,第二个版本在毫秒的基础上可以附加0-999999的纳秒值。

public static void sleep(long millis) throws InterruptedException:使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。

public static void sleep(long millis,int nanos) throws InterruptedException:导致正在执行的线程以指定的毫秒数加上指定的纳秒数来暂停(临时停止执行)。

Java并不保证线程在阻塞给定的时间后能够马上执行,在阻塞时间到了之后,线程进入就绪状态,继续执行的时机取决于Java虚拟机的线程调度机制,唯一能够确定的是,线程中断执行的时间是大于等于给定的阻塞时长的,因此不要将sleep用作精确度要求非常高的定时任务调度。

	public class TestThread {
    
    
		public static void main(String[] args) {
    
    
			ThreadSleep t1 = new ThreadSleep();
			new Thread(t1).start();
			ThreadSleep t2 = new ThreadSleep();
			new Thread(t2).start();
		}
	}
	
	class ThreadSleep implements Runnable{
    
    
		@Override
		public void run() {
    
    
			for (int i = 0; i < 20; i++) {
    
    
				if(i == 5) {
    
    
					try {
    
    
						Thread.sleep(2000);
					} catch (InterruptedException e) {
    
    
						e.printStackTrace();
					}
				}
				System.out.println(Thread.currentThread().getName()+":"+i);
			}
		}
	}

2、yield方法


yield方法只是使当前线程重新回到就绪可执行状态,所以执行yield线程有可能在进入到就绪状态后马上又被执行,只能使相同或更高优先级的线程有执行的机会。同样, yield也不会释放锁资源。

public static void yield():让当前线程从“运行状态”进入到“就绪状态”,从而让其他等待线程获取执行权,但是不能保证在当前线程调用yield()之后,其他线程就一定能获得执行权,也有可能是当前线程又回到“运行状态”继续运行。

sleep和yield的区别在于sleep需要提供阻塞时长,可以使优先级低的线程得到执行的机会, 而yield由于使线程直接进入就绪状态,没有阻塞时长,而且只能使相同或更高优先级的线程有执行的机会,甚至于某些时候 JVM 认为不符合最优资源调度的情况下会忽略该方法的调用。

注意:虽然很多资料写的都是让具有相同或更高优先级的线程竞争,但其实优先级低的线程在拿到CPU的执行权后也是可以执行,只不过优先级高的线程拿到CPU执行权的概率比较高,并不是一定能拿到。

	public class TestThread {
    
    
		public static void main(String[] args) {
    
    
			ThreadYield t1 = new ThreadYield();
			new Thread(t1).start();
			ThreadYield t2 = new ThreadYield();
			new Thread(t2).start();
		}
	}
	
	class ThreadYield implements Runnable {
    
    
		@Override
		public void run() {
    
    
			for (int i = 0; i < 10; i++) {
    
    
				if (i == 5) {
    
    
					Thread.yield();
				}
				System.out.println(Thread.currentThread().getName() + ":" + i);
			}
		}
	}

六、线程安全与不安全

1、线程安全


线程安全是指代码所在的进程中有多个线程同时运行,而这些线程可能会同时运行这段代码,但是这些线程之间互不干扰,也无法干扰,无论有多少线程执行,结果都是预期。

2、线程不安全


线程不安全是指多个线程同时访问同一个对象,发生数据的错误和丢失。线程不安全是不提供代码数据访问保护的,在单个线程下不会有问题,但在多线程环境中,结果会受到随机干扰,即线程的执行顺序会影响到其他线程的结果。

七、实现线程安全

1、synchronized关键字


synchronized关键字可以保证在同一时刻,只有一个线程可以执行某个方法或某个代码块。

如果需要将多条代码视作一个整体调度单元,希望这个调度单元在多线程环境中的调度顺序不影响任何结果,除了保证可见性、防止重排序改变语义之外,还要将该代码段进行原子保护,这种保护我们称为线程同步,其主要的作用是实现线程安全的类。在Java中使用synchronized关键字来对操作进行同步处理。

在使用synchronized关键字之前,需要理解JVM中的几个规范:在JVM中,每个对象和类在逻辑上都是和一个监视器相关联的,为了实现监视器的排他性监视能力(即保证资源只能同时被一个线程访问),JVM为每一个对象和类都关联一个锁,锁住了一个对象,就是获得对象相关联的监视器。监视器即类似令牌的概念,只有获取了令牌的线程才能操作资源,操作完成后将令牌释放,下一个线程才有重新获取令牌并进行资源操作的机会。

Java中使用 synchronized 关键字获得对象锁,实现线程同步。但是 synchronized 关键字会降低程序的性能。

1.1、普通同步方法


在同一个类中的同步方法使用同一个对象的监视器,当一个线程获取监视器锁定对象后,其他线程即便访问的是其他的同步方法,也需要排队等候锁。

如果当两个线程同时对一个对象的一个方法进行操作,只有一个线程能够抢到锁。因为一个对象只有一把锁,一个线程获取了该对象的锁之后,其他线程无法获取该对象的锁,就不能访问该对象的其他同步方法,需要等到对象被释放后才能获取,但是在对象没有被释放前,其他线程可以访问非同步方法。

如果两个线程作用于不同的对象,获得的是不同的锁,互相并不影响。

	/**
	 * 模拟卖票
	 * @author Administrator
	 *
	 */
	public class TestTicket {
    
    
		public static void main(String[] args) {
    
    
			TicketThread t = new TicketThread();
			new Thread(t).start();
			new Thread(t).start();
			new Thread(t).start();
			new Thread(t).start();
			new Thread(t).start();
			new Thread(t).start();
			new Thread(t).start();
			new Thread(t).start();
			new Thread(t).start();
			new Thread(t).start();
			
		}
	}
	
	class TicketThread implements Runnable {
    
    
		private int ticketCount = 100;
		/**
		 * 线程不安全:会有多个线程访问同一个资源
		 */
		private void sale() {
    
    
			if (ticketCount > 0) {
    
    
				System.out.println(Thread.currentThread().getName() + " 正在卖第" + ticketCount-- + "张票。");
			}
			try {
    
    
				//让当前线程暂时停止执行
				Thread.sleep(1000);
			} catch (InterruptedException e) {
    
    
				e.printStackTrace();
			}
		}
		
		/**
		 * 线程安全:synchronized 同步
		 */
		private synchronized void safeSale() {
    
    
			if (ticketCount > 0) {
    
    
				System.out.println(Thread.currentThread().getName() + " 正在卖第" + ticketCount-- + "张票。");
			}
			try {
    
    
				//让当前线程暂时停止执行
				//Thread.sleep(1000);
				Thread.sleep((int)(Math.random()*1000));
			} catch (InterruptedException e) {
    
    
				e.printStackTrace();
			}
		}
	
		@Override
		public void run() {
    
    
			while (ticketCount > 0) {
    
    
				safeSale();
			}
		}
	}

1.2、静态同步方法


静态同步方法的锁是当前类的class对象 ,进入同步方法前要获得当前类的class对象的锁。如果两个线程实例化两个不同的对象,但是访问的是静态的方法,那么这两个线程就会发生互斥(即一个线程访问,另一个线程只能等着)。因为静态方法是依附于类而不是对象的,所以当synchronized修饰静态方法时,锁是类的class对象。

	/**
	 * 模拟卖票
	 * @author Administrator
	 *
	 */
	public class TestTicket2 {
    
    
		public static void main(String[] args) {
    
    
			TicketThread2 t1 = new TicketThread2();
			TicketThread2 t2 = new TicketThread2();
			new Thread(t1).start();
			new Thread(t2).start();
		}
	}
	
	class TicketThread2 implements Runnable {
    
    
		private static int ticketCount = 100;
	
		private static synchronized void safeSale() {
    
    
			if (ticketCount > 0) {
    
    
				System.out.println(Thread.currentThread().getName() + " 正在卖第" + ticketCount-- + "张票。");
			}
			try {
    
    
				//让当前线程暂时停止执行
				Thread.sleep((int)(Math.random()*1000));
			} catch (InterruptedException e) {
    
    
				e.printStackTrace();
			}
		}
	
		@Override
		public void run() {
    
    
			while (ticketCount > 0) {
    
    
				safeSale();
			}
		}
	}

1.3、同步代码块


有时只是希望防止多个线程同时访问的是方法内部的部分代码,而不是整个方法。通过这种方式分离出来的代码被称为临界区,它使用synchronized关键字建立。这里,synchronized被用来指定某个对象,此对象的锁被用来对花括号内的代码进行同步。同步代码块可以适当降低同步整个方法带来的性能消耗。

将synchronized作用于一个给定的实例对象,即给定实例对象就是锁。每次当线程进入synchronized包括的代码块时,就会要求当前线程持有实例对象的锁,如果当前有其他线程正持有该对象锁,那么该线程就必须等待,直到获取对象的锁。这样也就保证了每次只有一个线程执行操作。当然除了实例化的对象作为锁外,还可以使用this对象或者类的class对象作为锁。

	/**
	  * 通过多线程给Person对象赋值,并输出
	  */
	public class TestPerson {
    
    
		public static void main(String[] args) {
    
    
			Person p =new Person();
			ModifyThread mt = new ModifyThread(p);
			ShowThread st =new ShowThread(p);
			
			new Thread(mt).start();
			new Thread(st).start();
		}
	}
	
	class Person {
    
    
		private String name;
		private String sex;
	
		Object obj = new Object();
	
		public void showPerson() {
    
    
			synchronized (obj) {
    
    
				try {
    
    
					obj.wait();//使当前线程处于等待状态,会释放锁
					System.out.println("Person:" + name + "," + sex);
				} catch (InterruptedException e) {
    
    
					e.printStackTrace();
				}
			}
			
		}
	
		public void setPerson(String name, String sex) {
    
    
			synchronized (obj) {
    
    	
				try {
    
    
					obj.notify();//唤醒处于等待状态的线程,不会释放锁
					this.name = name;
					Thread.sleep((int) (Math.random() * 1000));
					this.sex = sex;				
				} catch (InterruptedException e) {
    
    
					e.printStackTrace();
				}	
			}
		}
	}
	
	class ModifyThread implements Runnable {
    
    
		private Person p;
	
		public ModifyThread(Person p) {
    
    
			super();
			this.p = p;
		}
	
		public void modify() {
    
    
			int random = (int) (Math.random() * 100);
			if (random % 2 == 0) {
    
    
				p.setPerson("小明", "男");
			} else {
    
    
				p.setPerson("小红", "女");
			}
		}
	
		@Override
		public void run() {
    
    
			while (true) {
    
    
				modify();
			}
		}
	}
	
	class ShowThread implements Runnable {
    
    
		private Person p;
	
		public ShowThread(Person p) {
    
    
			super();
			this.p = p;
		}
		
		public void show() {
    
    
			p.showPerson();
		}
		
		@Override
		public void run() {
    
    
			while(true) {
    
    
				show();
			}
			
		}
	}

2、死锁


死锁 是指两个或者两个以上的线程在执行过程中,因争夺资源而造成的一种相互等待的现象。

	public class TestMain {
    
    
		public static Object obj1 = new Object();
		public static Object obj2 = new Object();
	
		public static void main(String[] args) {
    
    
			Thread01 t1 = new Thread01();
			t1.start();
			Thread02 t2 = new Thread02();
			t2.start();
		}
	}
	
	class Thread01 extends Thread {
    
    
		@Override
		public void run() {
    
    
			synchronized (TestMain.obj1) {
    
    
				System.out.println("Thread01 lock obj1");
	
				try {
    
    
					Thread.sleep(3000);
				} catch (InterruptedException e) {
    
    
					e.printStackTrace();
				}
	
				synchronized (TestMain.obj2) {
    
    
					System.out.println("Thread01 lock obj2");
				}
			}
		}
	}
	
	class Thread02 extends Thread {
    
    
		@Override
		public void run() {
    
    
			synchronized (TestMain.obj2) {
    
    
				System.out.println("Thread02 lock obj2");
	
				try {
    
    
					Thread.sleep(3000);
				} catch (InterruptedException e) {
    
    
					e.printStackTrace();
				}
				synchronized (TestMain.obj1) {
    
    
					System.out.println("Thread02 lock obj1");
				}
			}
		}
	}

3、ReentrantLock类


虽然我们现在已经可以理解同步代码块和同步方法的锁对象问题,但是我们并不能直接看到在哪里加了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5中提供了一个新的锁对象Lock(接口),提供了更为清晰的语义。

JDK5中有一个Lock的默认实现:ReentrantLock 是可重入的独占锁。该对象与synchronized关键字有着相同的表现和更清晰的语义,而且还具有一些扩展的功能。

可重入:同一个线程可以反复在同一个ReentrantLock对象上调用lock()方法,当然对应的是必须调用相同次unlock()方法。

ReentrantLock 类有一个重要特性体现在构造器上,构造器接受一个可选参数,是否是公平锁,默认是非公平锁。

公平锁:先来一定先排队,一定先获取锁。

非公平锁:不保证上述条件,非公平锁的吞吐量更高。

可重入锁可被最近的一个成功lock的线程占有(unlock后释放)。

(1)加锁:lock.lock();

(2)解锁:lock.unlock();

八、线程池

1、线程池的简介


在 Java 中,如果每个请求到达就创建一个新线程,开销是相当大的。在实际使用中,服务器在创建和销毁线程上花费的时间和消耗的系统资源都相当大,甚至可能要比在处理实际的用户请求的时间和资源还要多。除了创建和销毁线程的开销之外,活动的线程也需要消耗系统资源。

如果在一个JVM里创建太多的线程,可能会使系统由于过度消耗内存或切换过度而导致系统资源不足。为了防止资源不足,服务器应用程序需要采取一些办法来限制任何给定时刻处理的请求数目,尽可能减少创建和销毁线程的次数,特别是一些资源耗费比较大的线程的创建和销毁,尽量利用已有对象来进行服务,这就是 池化资源 技术产生的原因。

线程池主要用来解决线程生命周期开销问题和资源不足问题。

通过对多个任务重复使用线程,线程创建的开销就被分摊到了多个任务上了,而且由于在请求到达时,线程已经存在,所以消除了线程创建所带来的延迟。这样就可以立即为请求服务,使用应用程序响应更快。通过适当的调整线程中的线程数目,可以防止出现资源不足的情况。

当一个服务器接受到大量短小线程的请求时,使用线程池技术是非常合适的,它可以大大减少线程的创建和销毁次数,提高服务器的工作效率。

2、线程池的基本结构


一个比较简单的线程池至少应包含:

(1)线程池管理器:创建、销毁并管理线程池,将工作线程放入线程池中。

(2)工作线程:一个可以循环执行任务的线程,没有任务的时候则进行等待。

(3)任务列队:提供一种缓冲机制,将没有处理的任务放在任务列队中。

(4)任务接口:每个任务必须实现的接口,主要用来规定任务的入口、任务执行完后的收尾工作、任务的执行状态等,工作线程通过该接口调度任务的执行。

3、ExecutorService接口


ExecutorService 是Java中对线程池定义的一个接口,在这个接口中定义了和后台任务执行相关的方法。

ExecutorService接口 位于 java.util.concurrent 包中。

  • 部分方法

void shutdown():启动有序关闭,其中先前提交的任务将被执行,但不会接受任何新任务。 如果已经关闭,调用没有额外的作用。

Future<?> submit(Runnable task):提交一个可运行的任务执行,并返回一个表示该任务的Future。

4、Executors类


Executors类 位于 java.util.concurrent 包中。

Java通过Executors提供四种线程池(通过静态方法返回一个线程池),分别为:

(1)public static ExecutorService newCachedThreadPool():创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

(2)public static ExecutorService newFixedThreadPool(int nThreads):创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

(3)public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize):创建一个定长线程池,支持定时及周期性任务执行。

(4)public static ExecutorService newSingleThreadExecutor():创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

  • 线程池的创建和任务的执行

	public class TestThreadPool {
    
    
		public static void main(String[] args) {
    
    
			ExecutorService es = Executors.newFixedThreadPool(10);
			
			TicketThread tt = new TicketThread();
			
			for (int i = 0; i < 10; i++) {
    
    
				es.submit(tt);
			}
			es.shutdown();
		}
	}
	
	
	class TicketThread implements Runnable {
    
    
		private int ticketCount = 100;
		/**
		 * 线程安全:synchronized 同步
		 */
		private synchronized void safeSale() {
    
    
			if (ticketCount > 0) {
    
    
				System.out.println(Thread.currentThread().getName() + " 正在卖第" + ticketCount-- + "张票。");
			}
			try {
    
    
				//让当前线程暂时停止执行
				Thread.sleep((int)(Math.random()*1000));
			} catch (InterruptedException e) {
    
    
				e.printStackTrace();
			}
		}
	
		@Override
		public void run() {
    
    
			while (ticketCount > 0) {
    
    
				safeSale();
			}
		}
	}

九、生产者-消费者模式


Java实现生产者消费者模型问题是研究多线程程序时绕不开的经典问题之一:它描述一块缓冲区作为仓库,生产者可以将产品放入仓库,消费者则可以从仓库中取走产品。

实际上,生产者消费者模式准确说应该是 生产者-消费者-仓储 模式,离开了仓储,生产者消费者模型就显得没有说服力了。

对于此模型,应该明确一下几点:

① 生产者仅仅在仓储未满时候生产,仓满则停止生产。

② 消费者仅仅在仓储有产品时候才能消费,仓空则等待。

③ 当消费者发现仓储没产品可消费时候会通知生产者生产。

④ 生产者在生产出可消费产品时候,应该通知等待的消费者去消费。


	public class TestPerson {
    
    
		public static void main(String[] args) {
    
    
			Person p =new Person();
			ModifyThread mt = new ModifyThread(p);
			ShowThread st =new ShowThread(p);
			
			new Thread(mt).start();
			new Thread(st).start();
		}
	}
	
	class Person {
    
    
		private String name;
		private String sex;
		
		// 设置一次数据,读取一次数据
		
		// 标记位flag 是否设置了name和sex的信息:
		// true 表示 name和sex的数据设置完成;
		// false 表示 name和sex的数据读取完成
		boolean flag = false;
	
		Object obj = new Object();
	
		/**
		 * 获取并输出name和sex的值
		 */
		public synchronized void showPerson() {
    
    
			
			if(!flag) {
    
    
				try {
    
    
					wait();//使当前线程处于等待状态,会释放锁,在wait之后的代码都不执行
				} catch (InterruptedException e) {
    
    
					e.printStackTrace();
				}
			}
			System.out.println("Person:" + name + "," + sex);
			
			// 数据读取完成
			flag = false;
			// 唤醒ModifyThread的线程
			notify();//唤醒处于等待状态的线程,不会释放锁
		}
	
		/**
		 * 设置name和sex的值
		 * @param name 名字
		 * @param sex 性别
		 */
		public synchronized void setPerson(String name, String sex) {
    
    
			if(flag) {
    
    
				try {
    
    
					wait();//使当前线程处于等待状态,会释放锁,在wait之后的代码都不执行
				} catch (InterruptedException e) {
    
    
					e.printStackTrace();
				}
			}
			this.name = name;
			try {
    
    
				Thread.sleep((int) (Math.random() * 1000));
			} catch (InterruptedException e) {
    
    
				e.printStackTrace();
			}
			this.sex = sex;
			
			// 数据设置完成
			flag = true;
			// 唤醒 ShowThread的线程
			notify();//唤醒处于等待状态的线程,不会释放锁
	
		}
	}
	
	class ModifyThread implements Runnable {
    
    
		private Person p;
	
		public ModifyThread(Person p) {
    
    
			super();
			this.p = p;
		}
	
		public void modify() {
    
    
			int random = (int) (Math.random() * 100);
			if (random % 2 == 0) {
    
    
				p.setPerson("小明", "男");
			} else {
    
    
				p.setPerson("小红", "女");
			}
		}
	
		@Override
		public void run() {
    
    
			while (true) {
    
    
				modify();
			}
		}
	}
	
	class ShowThread implements Runnable {
    
    
		private Person p;
	
		public ShowThread(Person p) {
    
    
			super();
			this.p = p;
		}
		
		public void show() {
    
    
			p.showPerson();
		}
		
		@Override
		public void run() {
    
    
			while(true) {
    
    
				show();
			}
			
		}
	}

猜你喜欢

转载自blog.csdn.net/qq_42141141/article/details/110098928
今日推荐