zoukankan      html  css  js  c++  java
  • 第十三章 线程

    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()方法关闭线程池。
  • 相关阅读:
    使用Supervisor守护Python进程
    Python 程序员经常犯的 10 个错误
    DNS的view加速
    转-Gitorious搭建步骤
    linux下curl的地址使用双引号引用的原因
    java poi ppt操作示例
    CentOS 6.3下Samba服务器的安装与配置
    Linux NFS服务器的安装与配置
    Linux下安装JDK和tomcat
    Oracle数据库合并行记录,WMSYS.WM_CONCAT 函數的用法
  • 原文地址:https://www.cnblogs.com/ttzzyy/p/9742798.html
Copyright © 2011-2022 走看看