• JAVASE(十七) 多线程:程序、进程、线程与线程的生命周期、死锁、单例、同步锁


    个人博客网:https://wushaopei.github.io/    (你想要这里多有)

    1、程序、进程、线程的理解

    1.1 概念

    • 程序(program)是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
    • 进程(process)是程序的一次执行过程,或是正在运行的一个程序。动态过程:有它自身的产生、存在和消亡的过程。

               如:运行中的QQ,运行中的MP3播放器
               程序是静态的,进程是动态的

    • 线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。

                 若一个程序可同一时间执行多个线程,就是支持多线程的

    1.2 进程与多线程

            

    每个Java程序都有一个隐含的主线程: main 方法

    • 程序需要同时执行两个或多个任务。
    • 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
    • 需要一些后台运行的程序时。

    2、多线程的创建和启动

    2.1 Thread 线程概念:

               

    2.2 源码流程解析:

    (1)Thread 类 与 Runnable接口

    并且在Thread中有一个构造器的参数是Runnable的实现子类,所以当一个自定义类实现了Runnable接口后,可以通过newThread()作为构造参数传入来实现Thread多线程的创建。

    如图:

    (2)Thread 与 Runnable 、Callable 及Future Task 的关系

    首先分析:

    Future Task的底层实现了RunnableFuture 接口:

    RunnableFuture 的底层实现了两个接口: Runnable , Future

    而根据FutureTask 的构造方法和Callable 的类型可知:

        

    由以上可以得知,通过自定义实现了Callable 接口的实例对象做为FutureTask 的构造器形参,然后因为实现了Runnable 接口的缘故,该对象可以做参数传入Thread对象的构造器中,用来创建新的多线程 thread ,并使用 start ()启动线程。


    2.3 案例:mt子线程的创建和启动过程

    3、线程中常用的API

     

      

       

        

    interrupt() : 中断线程(也会将当前线程中的任务执行完毕会进行线程的一个标记,标记该线程是要被停止。
    isInterrupted() :测试此线程是否已被中断。(如果线程的标记被标识成中断则返回true)

    3、创建多线程的四中种方式

    3.1 继承Thread

    1. 自定义一个类并继承Thread
    2. 重写run方法
    3. run方法中可以写入需要在分线程中执行的代码
    4. 创建Thread子类的对象
    5. 通过对象调用start方法

    3.2 实现Runnable接口

    1. 自定义一个类并实现Runnable接口
    2. 重写run方法
    3. 在run方法中写入需要在分线程中执行的代码
    4. 创建Runnable实现类的对象
    5. 创建Thread对象并将Runnable实现类的对象作为实参传入到Thread的构造器中
    6. 通过Thread对象调用start方法

    3.3 实现Callable接口

    1. 自定义一个类,并实现Callable接口
    2. 重写call方法,并返回结果
    3. 在call方法中实现需要在分线程中执行的代码
    4. 创建Callable接口的实现类的对象
    5. 创建FutureTask对象,并将Callable接口的实现类的对象作为实参传到FutureTask的构造器中
    6. 创建Thread对象,并将FutureTask对象作为实参传入到Thread构造器中
    7. 调用start方法

    代码:

    public class ThreadTest {
    
    	public static void main(String[] args) throws Exception {
    		//4.创建Callable接口的实现类的对象
    		MyCallble myCallble = new MyCallble();
    		//5.创建FutureTask对象,并将Callable接口的实现类的对象作为实参传到FutureTask的构造器中
    		FutureTask<Integer> futureTask = new FutureTask<>(myCallble);
    		//6.创建Thread对象,并将FutureTask对象作为实参传入到Thread构造器中
    		Thread thread = new Thread(futureTask);
    		//7.调用start方法
    		thread.start();
    		
    		//阻塞主线程,等待分线程返回数据结果,然后再向下执行。
    		Integer intNumber = futureTask.get();
    		
    		System.out.println("===" + intNumber);
    		System.out.println("aaaaaaaaaaaaaaaaaaa");
    	}
    }
    //1.自定义一个类,并实现Callable接口
    class MyCallble implements Callable<Integer>{
    
    	//2.重写call方法,并返回结果
    	@Override
    	public Integer call() throws Exception {
    		/*
    		 * 3.需要在分线程中执行的代码
    		 */
    		for (int i = 0; i < 100; i++) {
    			System.out.println(Thread.currentThread().getName() + " == " + i);
    		}
    		return 100;
    	}
    	
    }

    3.4 线程池

                    //创建一个线程池,根据需要创建新的线程,但在可用时将重用先前构建的线程。
    		ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
    		//创建一个线程池,使用固定数量的线程操作了共享无界队列。
    		ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(3);
    		//创建一个执行器,使用一个单一的工作线程操作关闭一个无限的队列。 
    		ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
    newCachedThreadPool.execute(new Runnable() {
    
    			@Override
    			public void run() {
    				for (int i = 0; i < 100; i++) {
    					System.out.println(Thread.currentThread().getName() + "====" + i);
    				}
    			}
    		});
    		//关闭线程池
    		newCachedThreadPool.shutdown();

    4、Thread的生命周期

         

    要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态:

    • 新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
    • 就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件
    • 运行:当就绪的线程被调度并获得处理器资源时,便进入运行状态, run()方法定义了线程的操作和功能
    • 阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态
    • 死亡:线程完成了它的全部工作或线程被提前强制性地中止   

    5、线程的同步机制

    5.1 为什么要使用线程的同步机制?

    多个线程在同时操作共享数据的时候可能会发生线程安全问题。

    比如卖票:会发生重票,0票,负票的问题

    5.2 解决线程安全问题的方式?
    解决方法一 :同步代码块 :
    格式 :    

             synchronized (同步监视器) {
                    //操作共 享数据的代码
             }

    1.同步监视器 : 可以是任何对象
        注意 : 多个线程必须使用的是同一把锁(必须保证多个线程使用到的同步监视器是同一个对象)。
    2.同步代码块内 : 操作共享数据的代码
    (实现Runnable使用同步代码块案例 : 详见RunnableTest.java)
            
    解决方法二:  同步方法
              格式 :

            public synchronized void say(){}  -- 修饰普通方法

                       默认的同步监视器是:this

               public static synchronized void say(){} -- 修饰静态方法

                     默认的同步监视是运行时类的对象。 例:Person.class

    6、线程的单例模式

    class Bank {
    	private Bank() {
    	}
    	private static Bank bank = null;
    	public static Bank getInstance() {
    
    		if (bank == null) {
    			synchronized (Bank.class) {
    				if (bank == null) {
    					bank = new Bank();
    				}
    			}
    		}
    
    		return bank;
    	}
    }

    7、死锁的问题

    死锁的原因:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
    代码:

                    //创建两把锁
                    StringBuffer s1 = new StringBuffer();
    		StringBuffer s2 = new StringBuffer();
    		
    		new Thread() {
    			public void run() {
    				synchronized (s1) {
    					s2.append("A");
    					try {
    						Thread.currentThread().sleep(10);
    					} catch (InterruptedException e) {
    						// TODO Auto-generated catch block
    						e.printStackTrace();
    					}
    					synchronized (s2) {
    						s2.append("B");
    						System.out.print(s1);
    						System.out.print(s2);
    					}
    				}
    			}
    		}.start();
    
    		new Thread() {
    			public void run() {
    				synchronized (s2) {
    					s2.append("C");
    					try {
    						Thread.currentThread().sleep(10);
    					} catch (InterruptedException e) {
    						// TODO Auto-generated catch block
    						e.printStackTrace();
    					}
    					synchronized (s1) {
    						s1.append("D");
    						System.out.print(s2);
    						System.out.print(s1);
    					}
    				}
    			}
    		}.start();

    8、线程通信

    8.1 线程通信涉及到三个方法:

           

    注意 :  

    1. 上面个方法只能用在同步代码块和同步方法中
    2. 调用个方法实际上是调用监视对象中的方法

    8.2 [面试题] wait和sleep的区别?

    1. sleep睡觉的时候会抱着锁。wait睡觉的时候会释放锁。
    2. sleep时间一到就自动唤醒,wait需要被其它线程调用notify/notifyAll才能唤醒。
    3. sleep是Thread中的方法, wait是Object中的方法。

    8.3 [面试题] 继承Thread和实现Runnable的区别?

    继承Thread :

              同步监视器 : 不可以使用this
              共享资源 : 需要使用static关键字修饰。
              单继承
              public static synchronized void say() - 默认锁是运行时类的对象。 比如 : Person.class

    实现Runnable   

            同步监视器 : 可以使用this
            共享资源 : 不需要使用static关键字修饰。
            多实现
            public synchronized void say(){} - 默认锁是this

    9、同步的优点和缺点

    • 同步的优点 : 解决了线程安全问题
    • 同步的缺点 : 在同步代码块和同步方法的方法体执行时,只能有一个线程在执行。效率低。

    10、ReentrantLock

    代码:

    class MyRunnable2 implements Runnable {
    	// 同一个类的多个对象共同拥有一份类变量
    	private  int ticket = 100;
    	private ReentrantLock lock = new ReentrantLock();
    	@Override
    	public void run() {
    
    		while (true) {
    			lock.lock(); //锁住当前的线程
    			try {
    				if (ticket > 0) {
    					try {
    						Thread.sleep(10);
    					} catch (InterruptedException e) {
    						e.printStackTrace();
    					}
    					System.out.println(Thread.currentThread().getName() + " ==== " + ticket);
    					ticket--;
    
    				} else {
    					return;
    				} 
    			} finally {
    				//最后一定要手动解锁
    				lock.unlock();
    			}
    		}
    	}
    }

    注意 :ReentrantLock使用到的线程通信不是使用的wait()和notify()

  • 相关阅读:
    AS400一些有用的命令
    Publish的时候某些需要用到的文件没deploy上去
    DB2一些SQL的用法
    根据PostgreSQL 系统表查出字段描述
    linux memcached 安装
    CentOS下XEN虚拟服务器安装配置
    Apache the requested operation has failed
    PHP配置兼容ZendDebugger和Optimizer
    虚拟机比较
    memcache 运行情况,内存使用
  • 原文地址:https://www.cnblogs.com/wushaopei/p/12259611.html
Copyright © 2020-2023  润新知