返回顶部
扩大
缩小

Heaton

第十三章 线程

13、多线程
13.1 程序、进程、线程的概念 1课时
13.2 Java中多线程的创建和使用 1课时
13.3 线程的生命周期 1课时
13.4 线程的同步 1课时
13.5 线程的通信 1课时
13.6 线程池 1课时
##13-1 程序、进程、线程的概念 ![](https://i.imgur.com/6vDzgAU.png)

案例

//如下程序不是多线程的。一个线程可以理解为一条执行路径。
public class MainTest {
	
	
	public static void method1(String info){
		System.out.println(info);
	}
	
	public static void method2(String info){
		System.out.println(info);
		method1(info);
	}
	
	public static void main(String[] args) {
		method2("今天天气不错!");
	}
}

案例2

/*
 * 创建一个分线程,遍历100以内的偶数
 * 
 * 一、继承Thread的方式
 * 1.提供一个继承于Thread类的子类
 * 2.重写Thread类的run():将创建的线程要执行的操作,声明在run()中。
 * 3.实例化Thread子类
 * 4.调用子类对象的start()
 * 
 */
//1.提供一个继承于Thread类的子类
class NumThread extends Thread{
	//2.重写Thread类的run():将创建的线程要执行的操作,声明在run()中。
	 public void run() {
		 for(int i = 1;i <= 100;i++){
			 if(i % 2 == 0){
				 System.out.println(Thread.currentThread().getName() + ":" + i);
			 }
		 }
	 }
}


public class ThreadTest {
	public static void main(String[] args) {
		//3.实例化Thread子类
		NumThread thread1 = new NumThread();
		//4.调用子类对象的start():①启动线程 ②调用当前线程的run()
		thread1.start();
		//问题一:能不能直接调用run(),去启动分线程?不能!
//		thread1.run();
		//问题二:再启动一个分线程,仍然遍历100以内的偶数.如下的写法是错误的
//		thread1.start();
		//正确的写法:新建NumThread的对象
		NumThread thread2 = new NumThread();
		thread2.start();
		
		for(int i = 1;i <= 100;i++){
			 if(i % 2 == 0){
				 System.out.println(Thread.currentThread().getName() + ":" + i + "****************");
			 }
		}
		
	}
}

案例3

/**
 * 创建多线程的方式二:实现Runnable接口
 * 1.创建一个实现Runnable接口的类
 * 2.实现Runnable中的run()
 * 3.创建当前实现类的对象
 * 4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
 * 5.通过Thread类的对象调用其start()
 * 
 * 对比继承Thread类和实现Runnable接口两种方式?
 * 1.联系:public class Thread implements Runnable 
 * 2.相同点:启动线程,使用的是同一个start()方法
 * 
 * 3.对比:实现Runnable接口更好一些。
 *     原因:1)不影响类的继承。因为类是单继承的。
 *          2)针对于有共享数据的操作,更适合使用Runnable的方式。
 *             换句话说,实现Runnable接口的方式,实现了代码和数据的分离。
 * 
 * 4.面试题:创建多线程有几种方法?4种!
 *    继承Thread类;实现Runnable接口;实现Callable接口;使用线程池;
 */
// 1.创建一个实现Runnable接口的类
class Num implements Runnable{

	//2.实现Runnable中的run()
	@Override
	public void run() {
		for(int i = 1;i <= 100;i++){
			if(i % 2 == 0){
				System.out.println(Thread.currentThread().getName() + ":" + i);
			}
		}
	}
	
}


public class ThreadTest1 {
	public static void main(String[] args) {
		//3.创建当前实现类的对象
		Num num = new Num();
		//4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
		Thread t1 = new Thread(num);
		//5.通过Thread类的对象调用其start():①启动线程 ②调用此线程的run()
		t1.start();//start()--->Thread的run()--->Num的run()
		
		//再创建一个线程,遍历100以内的偶数
		Thread t2 = new Thread(num);
		t2.start();
		
	}
}

案例4

/**
 * Thread类的常用方法的测试
 * 1.run():Thread的子类一定要重写的方法。将此分线程要执行的操作,声明在run()中
 * 2.start():要想启动一个分线程,就需要调用start():①启动线程②调用线程的run()
 * 3.currentThread():静态方法,获取当前的线程
 * 4.getName():获取当前线程的名字
 * 5.setName(String name):设置当前线程的名字
 * 6.yield():当前线程调用此方法,释放CPU的执行权
 * 7.join():在线程a中调用线程b的join()方法:只用当线程b执行结束以后,线程a结束阻塞状态,继续执行。
 * 8.sleep(long millitimes):让当前的线程睡眠millitimes毫秒
 * 9.isAlive():判断当前线程是否存活
 * 
 * 10.线程的优先级:
 * 		MAX_PRIORITY:10
 * 		NORM_PRIORITY:5 ---默认优先级
 * 		MIN_PRIORITY:1
 * 
 *   设置优先级:setPriority(int priority); 
 *   获取优先级:getPriority();
 *   
 *   设置优先级以后,对高优先级,使用优先调度的抢占式策略,抢占低优先级的执行。但是并不意味着高优先级的线程一定先于低
 *   优先级的线程执行,而是从概率上来讲,概率更大而已。
 * 
 * 
 * 线程通信:wait() / notify() / notifyAll()  ---->java.lang.Object类中定义的方法
 * 
 *
 */
class NumberThread extends Thread{
	 public void run() {
		 for(int i = 1;i <= 100;i++){
			 if(i % 2 == 0){
				 
//				 try {
//					Thread.sleep(10);
//				} catch (InterruptedException e) {
//					e.printStackTrace();
//				}
				
				System.out.println(Thread.currentThread().getName()+ ":" + Thread.currentThread().getPriority() + ":" + i);
			 }
//			 if(i % 20 == 0){
//				 yield();
//			 }
		 }
	 }
	 
	 public NumberThread(){}
	 public NumberThread(String name){
		 super(name);
	 }
}



public class ThreadMethodTest {
	public static void main(String[] args) {
		NumberThread t1 = new NumberThread();
		
		t1.setName("分线程1");
		
		//设置t1的优先级
		t1.setPriority(Thread.MAX_PRIORITY);
		
		t1.start();
		
//		NumberThread t2 = new NumberThread("分线程2");
//		t2.start();
		
		Thread.currentThread().setName("主线程");
		
		for(int i = 1;i <= 100;i++){
			 if(i % 2 == 0){
				 System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().getPriority() + ":" + i);
			 }
			 
//			 if(i == 20){
//				 try {
//					t1.join();
//				} catch (InterruptedException e) {
//					e.printStackTrace();
//				}
//			 }
		 }
//		System.out.println(t1.isAlive());
		
	}
}

案例5

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

/*创建多线程的方式:
1.继承Thread
2.实现Runnable
3.实现Callable
4.使用线程池

*/
class MyThread01 extends Thread {
	@Override
	public void run() {
		System.out.println("-----MyThread01");
	}
}

class MyThread02 implements Runnable {
	public void run() {
		System.out.println("-----MyThread02");
	}
}

class MyThread03 implements Callable<Integer> {
	@Override
	public Integer call() throws Exception {
		System.out.println("-----MyThread03");
		return 200;
	}
}

public class ThreadNew {
	public static void main(String[] args) {
		new MyThread01().start();
		new Thread(new MyThread02()).start();

		FutureTask futureTask = new FutureTask(new MyThread03());
		new Thread(futureTask).start();
	}
}

案例6

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//创建并使用多线程的第四种方法:使用线程池
class MyThread implements Runnable {

	@Override
	public void run() {
		for (int i = 1; i <= 100; i++) {
			System.out.println(Thread.currentThread().getName() + ":" + i);
		}
	}

}

public class ThreadPool {
	public static void main(String[] args) {
		//1.调用Executors的newFixedThreadPool(),返回指定线程数量的ExecutorService
		ExecutorService pool = Executors.newFixedThreadPool(10);
		//2.将Runnable实现类的对象作为形参传递给ExecutorService的submit()方法中,开启线程
		//并执行相关的run()
		pool.submit(new MyThread());//线程.start()
		pool.submit(new MyThread());
		pool.submit(new MyThread());
		//3.结束线程的使用
		pool.shutdown();
		
		
	}
}

13-2 Java中多线程的创建和使用

练 习

创建两个子线程,让其中一个输出1-100之间的偶数,另一个输出1-100之间的奇数。
练 习

/**
 * 创建两个分线程,线程一:遍历100以内的偶数,线程二:遍历100以内的奇数
 *
 */
public class ThreadExer {
	public static void main(String[] args) {
		//方式一:
//		MyThread1 t1 = new MyThread1();
//		MyThread2 t2 = new MyThread2();
//		
//		t1.start();
//		t2.start();
		
		//方式二:创建Thread类的匿名子类的匿名对象
		new Thread(){
			public void run() {
				for(int i = 1;i <= 100;i++){
					 if(i % 2 == 0){
						 System.out.println(Thread.currentThread().getName() + ":" + i);
					 }
				 }
			}
		}.start();
		
		
		new Thread(){
			public void run() {
				for(int i = 1;i <= 100;i++){
					 if(i % 2 != 0){
						 System.out.println(Thread.currentThread().getName() + ":" + i);
					 }
				 }
			}
		}.start();
		
		
	}
}

class MyThread1 extends Thread{
	@Override
	public void run() {
		for(int i = 1;i <= 100;i++){
			 if(i % 2 == 0){
				 System.out.println(Thread.currentThread().getName() + ":" + i);
			 }
		 }
	}
}

class MyThread2 extends Thread{
	@Override
	public void run() {
		for(int i = 1;i <= 100;i++){
			 if(i % 2 != 0){
				 System.out.println(Thread.currentThread().getName() + ":" + i);
			 }
		 }
	}
}


守护线程

/**
* @author Heaton
* @email tzy70416450@163.com
* @date 2018/10/16 0016 16:28
* @describe
 * 当我们在Java中创建一个线程,缺省状态下它是一个User线程,如果该线程运行,JVM不会终结该程序。
 * 当一个线被标记为守护线程,JVM不会等待其结束,只要所有用户(User)线程都结束,JVM将终结程序及相关联的守护线程。
 * Java中可以用 Thread.setDaemon(true) 来创建一个守护线程。咱们看一个Java中有关守护线程的例子。
 *
 *
*/
public class JavaDaemonThread {
 
    public static void main(String[] args) throws InterruptedException {
        Thread dt = new Thread(new DaemonThread(), "dt");
//        dt.setDaemon(true);//此次将User线程变为Daemon线程
        dt.start();
        //程序继续
        Thread.sleep(30000);
        System.out.println("程序结束");
    }
 
}
 
class DaemonThread implements Runnable{//此类虽类名是为Daemon线程,其实为User线程
 
    @Override
    public void run() {
        while(true){
            processSomething();
        }
    }
 
    private void processSomething() {
        try {
            System.out.println("守护线程");
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
     
}

当你运行该程序,JVM 在main()方法中先创建一个用户线程,再创建一个守护线程。当main()方法结束后,程序终结,同时JVM也关闭守护线程。

下面就是上述程序执行的结果:

如果我们不将一个线程以守护线程方式来运行,即使主线程已经执行完毕,程序也永远不会结束,可以尝试把上述将线程设为守护线程的那句注释掉,重新运行看看结果:

通常我们创建一个守护线程,对于一个系统来说在功能上不是主要的。例如抓取系统资源明细和运行状态的日志线程或者监控线程。

13-3 线程的生命周期

13-4 线程的同步

例 题

模拟火车站售票程序,开启三个窗口售票。
例 题解法一

/**
 * 
 * 模仿车站买票程序。开启3个窗口卖票。总票数为100张。-----使用继承的方式
 * 
 * 此题目中存在线程的安全问题,待解决。
 */
public class WindowTest {
	
	public static void main(String[] args) {
		Window w1 = new Window();
		Window w2 = new Window();
		Window w3 = new Window();
		
		
		w1.setName("窗口1");
		w2.setName("窗口2");
		w3.setName("窗口3");
		
		w1.start();
		w2.start();
		w3.start();
	}
	
}

class Window extends Thread{
	
	static int ticket = 100;
	
	@Override
	public void run() {
		
		while(true){
			
			if(ticket > 0){
				System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);
				
				ticket--;
			}else{
				break;
			}
			
		}
		
	}
	
}

例 题解法二

/**
 * 模仿车站买票程序。开启3个窗口卖票。总票数为100张。-----使用实现的方式
 * 
 * 仍然存在线程的安全问题,待解决
 */
public class WindowTest {
	public static void main(String[] args) {
		Window w = new Window();
		
		Thread t1 = new Thread(w);
		Thread t2 = new Thread(w);
		Thread t3 = new Thread(w);
		
		t1.setName("窗口1");
		t2.setName("窗口2");
		t3.setName("窗口3");
		
		t1.start();
		t2.start();
		t3.start();
		
		
	}
}

class Window implements Runnable{
	int ticket = 100;

	@Override
	public void run() {
		while(true){
			if(ticket > 0){
				System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);
				ticket--;
			}else{
				break;
			}
		}

	}
	
	
}

线程安全1

/**
 * 
 * 模仿车站买票程序。开启3个窗口卖票。总票数为100张。-----使用实现的方式
 * 
 * 1.问题:出现了重票和错票
 * 2.问题出现的原因:一个窗口在没有售完票的情况下,其他的窗口参与进来,操作ticket,导致ticket输出的错误。
 * 3.解决的方案:当某一个窗口完全操作完ticket以后,其他窗口应该才被允许进来,继续操作ticket。
 * 4.java如何实现的?同步机制:①同步代码块    ②同步方法 
 * 
 * 4.1 同步代码块:
 * 		synchronized(同步监视器){
 * 			//需要被同步的代码
 * 		}
 * 	  说明:需要被同步的代码:即为操作共享数据的代码
 *       共享数据:多个线程共同操作的数据。比如:ticket
 *       同步监视器:俗称:锁。 可以由任何一个类的对象充当。
 *         要求:保证多个线程共用同一把锁!
 * 4.2 同步方法:将操作共享数据的方法,声明为同步的。此方法即为同步方法。
 * 
 * 5. 好处:线程的同步机制,解决了线程的安全问题。
 * 6. 劣势:在操作共享数据过程中,是单线程的。

 */
public class WindowTest {
	public static void main(String[] args) {
		Window w = new Window();
		
		Thread t1 = new Thread(w);
		Thread t2 = new Thread(w);
		Thread t3 = new Thread(w);
		
		t1.setName("窗口1");
		t2.setName("窗口2");
		t3.setName("窗口3");
		
		t1.start();
		t2.start();
		t3.start();
		
		
	}
}

class Window implements Runnable{
	int ticket = 100;
//	Object obj = new Object();
	Dog dog = new Dog();
	@Override
	public void run() {
		while(true){
//			synchronized(dog){
//			synchronized(new Dog()){
			synchronized(this){//this:当前的对象:w
				if(ticket > 0){
					try {
						
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					
					System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);
					ticket--;
				}else{
					break;
				}
			}
		}

	}
	
	
}

class Dog{
	
}

线程安全2

/**
 * 
 * 模仿车站买票程序。开启3个窗口卖票。总票数为100张。-----使用继承的方式
 * 
 * 使用同步代码块的方式解决继承中的线程安全问题。
 */
public class WindowTest1 {

	public static void main(String[] args) {
		Window1 w1 = new Window1();
		Window1 w2 = new Window1();
		Window1 w3 = new Window1();

		w1.setName("窗口1");
		w2.setName("窗口2");
		w3.setName("窗口3");

		w1.start();
		w2.start();
		w3.start();
	}

}

class Window1 extends Thread {

	static int ticket = 100;
	static Object obj = new Object();
	@Override
	public void run() {

		while (true) {

//			synchronized (obj) {//正确的。此时的obj是唯一的
//			synchronized(this){//this:此时表示为:w1,w2,w3。不唯一。
			synchronized(Window1.class){//Class clazz = Window1.class;//此时的Window1.class也是唯一的。
				if (ticket > 0) {

					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}

					System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);

					ticket--;
				} else {
					break;
				}
			}

		}

	}

}

线程安全3

/**
 * 使用同步方法解决实现方式的线程安全问题。
 * 
 * 1.默认的同步方法(非静态的)的锁是:当前对象,也就是this.
 * 2.默认的同步方法(静态的)的锁是:当前类本身.
 * 
 */
public class WindowTest2 {
	public static void main(String[] args) {
		Window2 w = new Window2();
		
		Thread t1 = new Thread(w);
		Thread t2 = new Thread(w);
		Thread t3 = new Thread(w);
		
		t1.setName("窗口1");
		t2.setName("窗口2");
		t3.setName("窗口3");
		
		t1.start();
		t2.start();
		t3.start();
		
		
	}
}

class Window2 implements Runnable{
	int ticket = 100;

	@Override
	public void run() {
		while(true){
			show();
		}

	}
	//同步方法:保证同一个时间段内,只能有一个线程来访问。
	private synchronized void show() {
		if(ticket > 0){
			
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			
			System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);
			ticket--;
		}
	}
	
	
}

线程安全4

/**
 * 使用同步方法解决继承方式的线程安全问题
 * 
 * 注意:继承的方式中,要慎用同步方法。
 * 
 */
public class WindowTest3 {

	public static void main(String[] args) {
		Window3 w1 = new Window3();
		Window3 w2 = new Window3();
		Window3 w3 = new Window3();

		w1.setName("窗口1");
		w2.setName("窗口2");
		w3.setName("窗口3");

		w1.start();
		w2.start();
		w3.start();
	}

}

class Window3 extends Thread {

	static int ticket = 100;

	@Override
	public void run() {

		while (true) {

			show();

		}

	}

//	private synchronized void show() {//默认的锁:this:w1,w2,w3
	private static synchronized void show() {//默认的锁:当前类本身:Window3.class,是唯一的。
		if (ticket > 0) {

			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}

			System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);

			ticket--;
		}
	}

}


练习

public class Customer extends Thread{

	private Account account;

	
	public Customer(Account account) {
		this.account = account;
	}

	public Account getAccount() {
		return account;
	}

	public void setAccount(Account account) {
		this.account = account;
	}

	@Override
	public void run() {
		for(int i=0;i<3;i++){
			account.deposit(1000);
		}
		
	}
}

public class Account {
		private double balance;//账户余额
		

		
		public double getBalance() {
			return balance;
		}



		public void setBalance(double balance) {
			this.balance = balance;
		}



		public Account() {
			super();
		}



		public Account(double balance) {
			super();
			this.balance = balance;
		}



		/**
		 * 存钱的操作
		 * @param number
		 */
		public synchronized void deposit(double number) {
			notify();
			
			balance+=number;
			
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			System.out.println(Thread.currentThread().getName()+"存款金额:"+ number +"余额为:"+balance);
			
			
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

/** * 银行有一个账户。 有两个储户分别向同一个账户存3000元,每次存1000,存3次。每次存完打印账户余额。
问题:该程序是否有安全问题,如果有,如何解决?


分析:1.确定是多线程的问题。
	2.此时的多个线程:两个储户
	3.此时的账户,是共享数据。保证账户的唯一性
	4.是否存在线程的安全问题? 有共享数据,有安全问题。
	5.考虑如何处理:使用同步机制。
	
 * 拓展:如何实现两个储户交替存钱:线程的通信:wait() / notify() / notifyAll()
 */
public class ThreadTest {

	
	public static void main(String[] args) {
		
		Account account=new Account();
		
		Customer customer1 = new Customer(account);
		Customer customer2 = new Customer(account);
		
		customer1.setName("用户1");
		customer2.setName("用户2");
		
		customer1.start();
		customer2.start();
		
	}
	
}

线程安全

/**
 * 实现线程安全的懒汉式(单例模式)
 */
public class BankTest {

}

class Bank{
	
	private Bank(){}
	
	private static Bank bank = null;
	
	//方式一:
//	public static synchronized Bank getInstance(){//当前的同步监视器:Bank.class
//		
//		if(bank == null){
//			
//			bank = new Bank();
//		}
//		
//		return bank;
//	}
	
	//方式二:相较于方式一,效率高一些
	public static Bank getInstance(){
		
		if(bank == null){
			
			synchronized (Bank.class) {
				if (bank == null) {
					
					bank = new Bank();
				}
			}
		}
		
		return bank;
	}
	
}

使用ReentrantLock线程安全

import java.util.concurrent.locks.ReentrantLock;

/**
 * 
 * 模仿车站买票程序。开启3个窗口卖票。总票数为100张。-----使用实现的方式
 * 
 * 解决线程安全问题的方式三:
 * 存在线程的安全问题,使用Lock的方式解决线程安全问题。
 * 
 * 
 * 面试题:同步的方式和Lock的方式,解决线程安全方面的异同?
 *    同:解决了线程安全问题
 *    异:同步的方式:同步监视器在线程执行完操作共享数据的代码以后,自动释放
 *       Lock的方式:需要显式的调用unlock()方法之后,才能保证其他线程操作共享数据。
 * 
 *
 */
public class WindowTest {
	public static void main(String[] args) {
		Window w = new Window();
		
		Thread t1 = new Thread(w);
		Thread t2 = new Thread(w);
		Thread t3 = new Thread(w);
		
		t1.setName("窗口1");
		t2.setName("窗口2");
		t3.setName("窗口3");
		
		t1.start();
		t2.start();
		t3.start();
		
		
	}
}

class Window implements Runnable{
	int ticket = 100;
	//1.ReentrantLock的实例化
	private ReentrantLock lock = new ReentrantLock();//保证此对象的唯一性。
	
	@Override
	public void run() {
		while(true){
			
			try{
				//2.调用lock()
				lock.lock();
				
				if(ticket > 0){
					
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					
					System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);
					ticket--;
				}else{
					break;
				}
				
			}finally{
				//3.调用unlock()
				lock.unlock();
			}
			
		}

	}
	
	
}

	/**
 * 死锁:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
 * 
 * 
 * 死锁,是我们开发中需要规避的!
 */
public class DeadLockTest {

	public static void main(String[] args) {
		StringBuffer s1 = new StringBuffer();
		StringBuffer s2 = new StringBuffer();

		new Thread(new Runnable() {
			public void run() {
				synchronized (s1) {
					s1.append("a");// 类似s += "a"
					s2.append("1");

					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}

					synchronized (s2) {

						s1.append("b");
						s2.append("2");

						System.out.println(s1);
						System.out.println(s2);
					}

				}

			}
		}).start();

		new Thread(new Runnable() {
			public void run() {
				synchronized (s2) {
					s1.append("c");// 类似s += "a"
					s2.append("3");

					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}

					synchronized (s1) {
						System.out.println("#########2###########");
						s1.append("d");
						s2.append("4");

						System.out.println(s1);
						System.out.println(s2);
					}

				}

			}
		}).start();
	}

}

//死锁的问题
class A {
	public synchronized void foo(B b) {
		System.out.println("当前线程名: " + Thread.currentThread().getName()
				+ " 进入了A实例的foo方法"); // ①
		try {
			Thread.sleep(200);
		} catch (InterruptedException ex) {
			ex.printStackTrace();
		}
		System.out.println("当前线程名: " + Thread.currentThread().getName()
				+ " 企图调用B实例的last方法"); // ③
		b.last();
	}

	public synchronized void last() {
		System.out.println("进入了A类的last方法内部");
	}
}

class B {
	public synchronized void bar(A a) {
		System.out.println("当前线程名: " + Thread.currentThread().getName()
				+ " 进入了B实例的bar方法"); // ②
		try {
			Thread.sleep(200);
		} catch (InterruptedException ex) {
			ex.printStackTrace();
		}
		System.out.println("当前线程名: " + Thread.currentThread().getName()
				+ " 企图调用A实例的last方法"); // ④
		a.last();
	}

	public synchronized void last() {
		System.out.println("进入了B类的last方法内部");
	}
}

public class DeadLock implements Runnable {
	A a = new A();
	B b = new B();

	public void init() {
		Thread.currentThread().setName("主线程");
		// 调用a对象的foo方法
		a.foo(b);
		System.out.println("进入了主线程之后");
	}

	public void run() {
		Thread.currentThread().setName("副线程");
		// 调用b对象的bar方法
		b.bar(a);
		System.out.println("进入了副线程之后");
	}

	public static void main(String[] args) {
		DeadLock dl = new DeadLock();
		dl.init();
		new Thread(dl).start();
	}
}

13-5 线程的通信

例 题

使用两个线程打印 1-100. 线程1, 线程2 交替打印

/**
 * 线程的通信:
 * wait():一个线程在执行过程中,一旦调用此方法,则此线程进入阻塞状态,等待其他线程来唤醒自己。
 * notify():一个线程在执行过程中,一旦调用此方法,则会唤醒被wait()的一个线程。高优先级的要优先被唤醒。
 * notifyAll():一个线程在执行过程中,一旦调用此方法,则会唤醒所有被wait()的线程。
 * 
 * 
 * 
 * 例题:使用两个线程打印 1-100. 线程1, 线程2 交替打印
 * 
 * 注意点:1.此三个方法必须使用在同步中。
 *       2.此三个方法的调用者是同步监视器!否则,如果三个方法的调用者不是同步监视器,报异常。
 *       3.此三个方法定义在Object类
 * 
 * 面试题:sleep() 和  wait() 的异同?
 * 1.方法声明在哪? Thread:sleep()   Object:wait()
 * 2.共同点:使得当前线程进入阻塞状态
 * 3.使用的范围要求:sleep()使用没有情境的要求;wait()必须使用在同步代码块或同步方法中
 * 4.都使用在同步当中的话:wait()需要唤醒:notify()/notifyAll();  sleep():不会释放锁;wait()会释放锁
 * 
 */
class Num implements Runnable{
	
	int number = 1;
	Object obj = new Object();

	@Override
	public void run() {
		
		while(true){
			synchronized (obj) {
				
				obj.notify();
				
				if (number <= 100) {

					try {
						Thread.sleep(10);////一旦执行此方法,当前线程进入阻塞状态,不释放同步监视器。
					} catch (InterruptedException e) {
						e.printStackTrace();
					}

					System.out.println(Thread.currentThread().getName() + ":" + number);
					number++;
					
					//一旦执行此方法,当前线程进入阻塞状态,释放同步监视器。
					try {
						obj.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					
					
				} else {
					break;
				}
			}
		}
			
	}
	
}


public class CommunicationTest {
	public static void main(String[] args) {
		Num n = new Num();
		Thread t1 = new Thread(n);
		Thread t2 = new Thread(n);
		
		t1.setName("线程1");
		t2.setName("线程2");
		
		t1.start();
		t2.start();
	}
}

/**
 * 多线程的应用:生产者、消费者问题
 * 
 * 生产者(Producer)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,
 * 店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员会叫生产者停一下,
 * 如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,
 * 如果店中有产品了再通知消费者来取走产品。
 * 
 * 
 * 分析:
 * 1.是不是多线程问题?是。此时的生产者、消费者就是线程
 * 2.是否存在线程安全问题? 是,因为有共享数据
 * 3.共享数据是? 产品(或产品的数量)
 * 4.使用同步机制处理线程的安全问题:操作共享数据的代码 ; 使用唯一的同步监视器
 * 5.涉及线程的通信问题。

 */

class Producer extends Thread{//生产者
	
	private Clerk clerk;
	public Producer(Clerk clerk) {
		this.clerk = clerk;
	}
	
	@Override
	public void run() {
		System.out.println("生产者开始生产产品....");
		//不停的生产产品
		while(true){
			
			try {
				Thread.sleep(50);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			clerk.addProduct();
			
		}
	}
	
}

class Consumer extends Thread{//消费者
	private Clerk clerk;
	
	public Consumer(Clerk clerk) {
		this.clerk = clerk;
	}
	
	@Override
	public void run() {
		System.out.println("消费者开始消费产品....");
		//不停的消费产品
		while(true){
			
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			clerk.reduceProduct();
			
		}
	}
}


class Clerk{//店员
	
	private int product = 0;//产品的数量

	public synchronized void reduceProduct() {//消费产品
		
		if(product > 0){
			System.out.println(Thread.currentThread().getName() + "消费了第" + product + "个产品");
			product--;
			
			notifyAll();//将生产者唤醒
			
		}else{
			//没有产品了,应该等待。
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
		
	}

	public synchronized void addProduct() {//生产产品
		if(product < 20){
			product++;
			System.out.println(Thread.currentThread().getName() + "生产了第" + product + "个产品");
			
			notifyAll();//将消费者唤醒
		}else{
			//已经达到上限20,等待
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
}



public class ProducerAndConsumer {
	public static void main(String[] args) {
		
		Clerk clerk = new Clerk();
		
		Consumer consumer1 = new Consumer(clerk);
		Consumer consumer2 = new Consumer(clerk);
		Producer producer1 = new Producer(clerk);
		
		consumer1.setName("消费者1");
		consumer2.setName("消费者2");
		producer1.setName("生产者1");
		
		
		consumer1.start();
		consumer2.start();
		producer1.start();
		
	}
}
13-6 线程池

系统启动一个新线程的成本是比较高的,因为它涉及与os交互。这种情况下,系统启动时即创建大量空闲的线程,就可以很好地提高性能,尤其是当程序需要创建大量生存期很短暂的线程时。

除此之外,使用线程池可以有效地控制系统中并发线程的数量。避免因并发创建的线程过多,导致系统性能下降,JVM崩溃。

Java 5以前,需要手动创建自己的线程池;Java 5开始,新增了Executors工厂类产生线程池。

使用线程池执行线程任务的步骤如下:

  1. 调用Executors 类的静态方法newFixedThreadPool(int nThreads),创建一个可重用的、具有固定线程数的线程池ExecutorService对象
  2. 创建Runnable实例,作为线程执行任务
  3. 调用ExecutorService对象的submit()提交Runnable实例
  4. 调用ExecutorService对象的shutDown()方法关闭线程池。

posted on 2018-10-04 18:35  咘雷扎克  阅读(189)  评论(0编辑  收藏  举报

导航