Java 多进程、多线程、多线程中的共享变量的安全问题、同步锁、线程同步、ThreadLocal

日萌社

人工智能AI:Keras PyTorch MXNet TensorFlow PaddlePaddle 深度学习实战(不定时更新)


  •  多进程、多线程、多线程中的共享变量的安全问题、同步锁、线程同步、ThreadLocal

  • Java 线程池 newCachedThreadPool缓存线程池、newFixedThreadPool固定线程池


1.多进程和多线程:
	1.进程和线程:
		1.进程:当前正在运行的程序,一个应用程序在内存中的执行区域
		2.线程:一个线程 为进程中的一个执行控制单元,真正执行代码的主体
 
	2.单线程和多线程:
		1.单线程:安全性高、效率低
		2.多线程:安全性低、效率高

	3.主线程和子线程:
		1.主线程:负责执行main方法,并负责创建子线程
		2.子线程:负责执行多线程的线程代码


2.实现多线程:
	1.Thread 线程类:
		1.Thread 线程类为 Runnable接口的实现类
		2.实现多线程第一种方法:(四个步骤)
			1.定义子类继承Thread 线程类,那么该子类也为线程类  
			2.自定义的子类中 重写run()方法
			3.run()方法体 中实现 子线程要执行的 功能代码,run()方法中的this为子线程类,即为 继承了Thread 线程类的自定义子类
			4.在main方法中 使用自定义的子类的实例对象 调用 start()方法,启动子线程自动执行run()方法
    				1.创建 Thread线程类的 子类线程类对象:自定义子类类名   obj  =  new   自定义子类类名() ;
    				2.调用线程对象的start方法:obj.start()  启动一个线程,start()方法底层还会调用run()方法;

		3.自定义子线程类 继承Thread 线程类的特点:
			每个子线程都不共享自定义的子类线程类的实例对象,每个线程都有自己一个自定义的子类线程类的实例对象

	2.Runnable接口:
		1.Runnable接口的实现类 Thread 线程类
		2.实现多线程第二种方法:(四个步骤)
			1.创建自定义实现类 实现Runnable接口,此时自定义实现类 不是线程类,只有继承Thread 线程类的子类 才为 线程类;
			2.在自定义实现类中重写 Runnable接口中的 run方法
			3.run()方法体 中实现 子线程要执行的 功能代码,run()方法中的this代表线程类,即为调用start()方法的 Thread线程类
			4.在main方法中  Thread线程类调用start()方法,并在Thread线程类的构造器中 传入 自定义实现类的实例对象
     					1.创建 Thread线程类对象:Thread   t  =  new  Thread( new   自定义实现类名() );    
     					2.调用线程对象的start方法:t.start()   启动一个线程,start()方法底层还会调用run()方法;

		3.自定义实现类 实现Runnable接口的特点:
			1.创建的 每个 Thread线程类对象的 构造器中 传入的都是 不同的 自定义实现类的实例对象,
			    那么每个线程都有自己的 自定义实现类的实例对象,即每个线程之间不共享 自定义实现类的实例对象;
				例子:
					Thread   t1  =  new  Thread( new   自定义实现类名() )
					Thread   t2  =  new  Thread( new   自定义实现类名() ) 

			2.数据同步问题(线程安全问题):
			    创建的 每个 Thread线程类对象的 构造器中 传入的都是 同一个 自定义实现类的实例对象,
			    那么每个线程中 拥有的自定义实现类的实例对象 都是同一份,即每个线程之间 共享 同一个自定义实现类的实例对象,
			    因为所有每个线程都是访问操作同一个自定义实现类的实例对象中的数据,所以任意一个线程修改了自定义实现类的实例对象
			    中的数据之后,别的线程访问的 自定义实现类的实例对象中的数据也是修改后的数据;
			    此时要注意 会出现 数据同步问题(线程安全问题),解决方式比如 锁机制(同步代码块、同步方法)等;
				例子:
					自定义实现类名  obj  =  new   自定义实现类名() 
					Thread   t1  =  new  Thread(obj)
					Thread   t2  =  new  Thread(obj) 

	3.线程池:
		1.在执行 子线程要执行的代码之前,就已经创建出 线程池,并在线程池中创建初始化了多个线程,那么用户访问之前,
		   就已经创建初始化了多个线程,这样用户就无需等待创建子线程了,节省时间;
		2.线程池中的线程执行完run()方法后 不会被销毁,而是重新回到线程池中,等待其他用户来访问再从线程池中取出一条线程复用
		3.创建和使用线程池:
			1.创建一个线程池对象pool:
				默认线程池只创建有一个线程:ExecutorService  pool  =  Executors.newSingleThreadExecutor();
				可以创建 指定线程数量的线程池:ExecutorService  pool  =  Executors.newFixedThreadPool(len容量);
		
			2.使用线程池其中的一个线程去执行任务函数:
				线程池对象pool 调用 execute(...实例对象) / submit(...实例对象),底层会调用 实例对象中的 run()方法:
				1.第一种方法:pool.execute(继承了Thread线程类 的子类实例对象):调用 “继承了Thread线程类 的”子类中的 run()方法
				2.第二种方法:pool.submit(实现了Runnable接口的实现类的 实例对象):
					              调用 “实现了Runnable接口的” 实现类中的 run()方法;

			3.数据同步问题(线程安全问题):
				如果 pool.execute(...实例对象) / pool.submit(...实例对象) 中传入的是 同一个实例对象的话,
				那么 线程池中 所有每个线程 都共享同一份实例对象,那么每个线程 操作的都是 同一个实例对象中的数据,
				此时就会容易出现数据同步问题(线程安全问题); 
	 
 				
3.线程安全问题(数据同步问题)和 解决方案(锁机制:同步代码块、同步方法、同步锁):
	1.只要是在 多线程 执行的 run()方法中 操作到了 共享数据(同一份数据)的地方  都要加上 synchronized;
	    也即必须在 synchronized同步代码块、synchronized同步方法 中操作 共享数据(同一份数据),即可以达到同一时间只有一个线程在操作共享数据,
	    保证共享数据的同步;

	2.共享数据(同一份数据)的定义:
		1.定义 非静态变量/静态变量(类变量) 来作为 共享数据使用:
			1.如果是 多个Thread线程类 只操作 一个实例对象中的 共享数据的话,那么该实例对象中 可以定义 非静态变量/静态变量(类变量) 
		   	    来作为 共享数据使用;
		   	2.在 “class  自定义实现类  implements Runnable”的方式中:
				因为只需要创建一个实例对象,所以使用 非静态变量/静态变量(类变量) 作为共享数据 都只有一份;
				而每个Thread线程都只需操作这唯一一个实例对象中的共享数据;

		2.定义 静态变量(类变量) 来作为 共享数据使用:
			1.如果是 多个 “继承了Thread线程类的” 子线程类的实例对象 来操作共享数据的话,那么该实例对象中 只能定义静态变量(类变量)
			   来作为 共享数据使用;
			2.在 “class  自定义子线程类  extends  Thread”的方式中:
				因为创建一个子线程类的实例对象就是创建一个新的子线程,所以创建多线程的话,
				就需要创建多个子线程类的实例对象;因为只有定义 静态变量(类变量) 来作为 共享数据使用,
				才能保证多个子线程类的实例对象操作的都是同一份数据;
 
	3.问题的产生的根源:(多个线程操作同一份数据)
		创建的 每个 Thread线程类对象的 构造器中 传入的都是 同一个 自定义实现类的实例对象,
		那么每个线程中 拥有的自定义实现类的实例对象 都是同一份,即每个线程之间 共享 同一个自定义实现类的实例对象,
		因为所有每个线程都是访问操作同一个自定义实现类的实例对象中的数据,所以任意一个线程修改了自定义实现类的实例对象
		中的数据之后,别的线程访问的 自定义实现类的实例对象中的数据也是修改后的数据;
		此时要注意 会出现 数据同步问题(线程安全问题),解决方式比如 线程锁等;
			例子:
				自定义实现类名  obj  =  new   自定义实现类名() 
				Thread   t1  =  new  Thread(obj)
				Thread   t2  =  new  Thread(obj) 

	4.解决方式比如 锁机制(同步代码块、同步方法)等:
		1.同步代码块:
			synchronized(同步锁)
			{
     				需要同步操作的代码
			}

		2.同步代码块 和 同步锁:
			同步代码块 中的 同步监听对象/同步锁/同步监听器/互斥锁:
				为了保证每个线程都能正常执行原子操作,Java引入了线程同步机制;
				对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁;
				Java程序运行使用任何对象作为同步监听对象,但是一般的,我们把当前并发访问的共同资源作为同步监听对象;
				注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着;

			1.同步锁:字符串值、类名.class(字节码对象)、this、Object对象(Object  obj  =  new  Object())
				1.synchronized (字符串值 / 类名.class)
				    只要 该对象/变量 在整个程序中只存在一份,那么就能作为同步锁;
				    比如:字符串值、类名.class(字节码对象)在一个程序中只有一份,所以可以作为同步锁;
				2.synchronized (this / Object对象):
					1.this / Object对象 只有在 implements Runnable的实现类中的 才可以作为 同步锁使用,
					     因为在 implements Runnable的实现类中 只需要创建一份 实现类的 实例对象,
					     每个子线程 都只需要 共享同一份 实现类的 实例对象;
					2.this / Object对象 都不能在  extends Thread 的子线程类中 作为 同步锁使用,
					   因为会在子线程类中创建多个 子线程类实例对象,才等于创建了多个子线程,
					   this 就可以被代表每个子线程,就不会只有一份;

				3.在一个多线程的 程序中,多线程环境下 使用到了 多个 synchronized同步代码块 / synchronized同步方法 / 
				    底层带有synchronized的集合 的话,那么在这个程序中 就会存在 多个 不同类型的  同步锁对象,
				    那么当一个线程获取了某一个同步锁对象,而另外的线程则可以获取除这个之外的 其他的同步锁对象,
				    每个不同类型的 同步锁对象 各自只有一份,一个同步锁对象最多只可以被一个线程所持有;

			2.synchronized (字符串值):
					可以是 任意字符串值,因为字符串就是字面量常量,在方法区中的常量区只有一份,
					所以可以作为同步锁,只要有一个线程拥有字符串常量,其他线程都无法拥有;

			3.synchronized (this):
				1.synchronized (this) 不能用在 extends Thread,只能用在 implements Runnable;
				   原因:当创建多个子线程类的实例对象时,才为创建多个子线程,
					synchronized (this) 中的this 就可以代表 多个实例对象 自身(多个子线程 自身),
					哪个实例对象调用该this就代表该实例对象,那么每个线程都可以有一份this,this无法作为同步锁;
					只有使用在implements Runnable的实现类中的 synchronized (this)  可以作为 同步锁,
					因为实现类的 实例对象只需要创建一份,那么多个Thread线程类 都可以共享同一个实例对象,
					this仅代表这一个实例对象;

				2.对于非静态方法中的同步代码块,锁对象就是this,在使用上 只创建一份实例对象,
				    多个线程都是共享同一份实例对象;

				3.例子:(使用在implements Runnable的实现类中的 synchronized (this)  可以作为 同步锁)
					自定义实现类名  obj  =  new   自定义实现类名()  //只创建一份实例对象,多个线程都是共享同一份实例对象
					Thread   t1  =  new  Thread(obj)
					Thread   t2  =  new  Thread(obj) 

			4.synchronized (类名.class):
					对于 非静态方法/静态方法中的同步代码块, 锁对象 就是 字节码对象(Class  c  =  类名.class)  
					因为字节码对象	只有一份,所以可以作为 同步锁;

		3.同步方法:
			1.synchronized  public  void  doWork(){ ...... }:
				使用在implements Runnable的实现类中的 非静态 同步方法的 锁对象 是该类的实例对象(this),
				在使用上 只创建一份实例对象,多个线程都是共享同一份实例对象;
				例子:
					自定义实现类名  obj  =  new   自定义实现类名()  //只创建一份实例对象,多个线程都是共享同一份实例对象
					Thread   t1  =  new  Thread(obj)
					Thread   t2  =  new  Thread(obj) 

			2.synchronized  public  static  void  doWork(){ ...... }:
				对于静态方法的锁对象 就是 字节码对象(Class  c  =  类名.class) ;
 				因为字节码对象	只有一份,所以可以作为 同步锁;

	5.例子:
			//第一种方式:class  子线程类  extends  Thread
			class  类名  extends  Thread

			//第二种方式:class  实现类   implements Runnable
			class  类名  implements Runnable
			{
				--------------------------------------------------------------------------------
				1.class  子线程类  extends  Thread:
					1.需要创建多个子线程类的实例对象才等于创建多个子线程;
					2.如果需要多个子线程操作共享数据(同一个数据)的话,子线程类中只能定义静态变量作为共享数据来使用,
					    因为即使是有多个实例对象,静态变量(类变量)也是有一份;

				2.class  实现类   implements  Runnable:
					1.只需要创建一个实现类的实例对象,并使用多个new Thread(实例对象).start() 即可;
					2.如果需要多个子线程操作共享数据(同一个数据)的话,因为如果只创建一个实现类的实例对象,
					    多个Thread线程类 都只操作该单个实现类的实例对象的话,可以使用 非静态变量 / 静态变量(类变量);
				--------------------------------------------------------------------------------
				//extends  Thread:
				//	创建的 每个子线程类的实例对象 都会有 属于自己一份的 非静态变量(成员变量) ;
				//	即使创建多个子线程类的实例对象,静态变量(类变量)都只有一份,多个子线程类的实例对象可以共享
				//	同一份静态变量;
				int num = 50;  
 
				//extends  Thread:多个子线程类的实例对象 共享使用同一个变量的值,只能使用 静态变量(类变量)
				//implements  Runnable:多个Thread线程类 都只操作该单个实现类的实例对象的话,可以使用 非静态变量 / 静态变量   
				static  int num = 50; 

 				--------------------------------------------------------------------------------
				同步方法中:
					synchronized 不能修饰 run()方法上,如不能定义:synchronized public void run() 
		 			synchronized 修饰 run()方法的后果是:某一个线程就会单独执行完run(),其他线程并没有机会执行run()方法
				--------------------------------------------------------------------------------
				@Override
	 			public void run() 
				{
					---------------------------------------------第一种同步写法:synchronized同步代码块--------------------------------------------
					--------------------------------------------------------------------------------
					synchronized同步代码块中:
						应根据需求 判断是否可以把 循环放在 同步代码块中;
						一旦把循环放在同步代码块中,意味着某一个线程就会单独执行完该循环;
						如果run()方法体中 全部功能代码都是放在 循环中的话,就不应把 循环放在同步代码块中,
						否则,某一个线程就会单独执行完该循环中的全部功能代码,
						和synchronized 修饰 run()方法的后果是一样的。
					--------------------------------------------------------------------------------
					
					for( int  i  =  0;  i  <  100  ;i++)
					{
						//synchronized (this):
						//	this 代表调用run()方法的实例对象,只有在“implements Runnable”的方式下只创建一个实例对象
						//synchronized (this) 
 
						//synchronized (类名.class):字节码对象(Class c = 类名.class) 在内存中只有一份 
						synchronized (类名.class) 
						{
							//操作共享数据
						}
					}

					-------------------------------- 调用 同步方法 或 调用 “带有同步代码块的” 静态方法 / 非静态方法------------------------------ 
					for( int  i  =  0;  i  <  100  ;i++)
					{
						function();
					}
				}

				-------------------------------------------------第二种同步写法:synchronized同步方法---------------------------------------------------
				synchronized  public  void  function()
				{
					//操作共享数据
				}

				-------------------------------------------------第三种同步写法:“带有同步代码块的” 静态方法 ---------------------------------------- 
				public  static  void  function()
				{
					//synchronized (this):
					//	1.静态方法中 无法使用 synchronized(this),因为静态方法中不存在this
					//	2.this 代表调用run()方法的实例对象,只有在“implements Runnable”的方式下只创建一个实例对象
					//synchronized (this) 
 
					//synchronized (类名.class):
					//	1.静态方法中 可以使用 synchronized (类名.class)
					//	2.字节码对象(Class c = 类名.class) 在内存中只有一份 
					synchronized (类名.class) 
					{
						//操作共享数据
					}
				}	

				-------------------------------------------------第四种同步写法:“带有同步代码块的” 非静态方法 ---------------------------------------- 
				public  void  function()
				{
					//synchronized (this):
					//	1.非静态方法中 可以使用 synchronized(this) 
					//	2.this 代表调用run()方法的实例对象,只有在“implements Runnable”的方式下只创建一个实例对象
					//synchronized (this) 
 
					//synchronized (类名.class):
					//	1.非静态方法中 可以使用 synchronized (类名.class)
					//	2.字节码对象(Class c = 类名.class) 在内存中只有一份 
					synchronized (类名.class) 
					{
						//操作共享数据
					}
				}
			}


4.多线程操作单例对象在Spring框架下是安全的,而不在Spring框架下使用多线程操作单例对象时会出现数据安全同步问题:
	1.不在spring框架下 单纯使用 多线程 操作单例对象的话,会出现数据安全同步问题,因此单例对象和static静态变量(类变量)一样,
	  都只有一份,都必须使用同步操作/synchronized,或把静态变量存到ThreadLocal后再从ThreadLocal获取出来使用,
	  这样才能保证多个线程可以安全地操作静态变量的数据。
	2.在spring框架下 使用多线程时,多个线程在操作 单例对象 仍然是是安全的,因为 spring框架会把单例对象封装进ThreadLocal中,
	   那么每个线程都从ThreadLocal中取单例对象的话,实际取的是单例对象的副本,那么每个线程中使用的都是单例对象的副本。
	3.spring框架下把QueryRunner runner对象设置为多例是安全的,即使是把QueryRunner runner对象设置为单例也是安全的,
 	   因为 spring框架会把单例的QueryRunner runner对象封装进ThreadLocal中,那么每个线程都从ThreadLocal中取单例的QueryRunner runner对象的时候,
	   实际取的是单例的QueryRunner runner对象的副本。
	   例子:   //scope="prototype"把QueryRunner对象设置为多例的
		          
			
    		

5.多线程 的例子:
	1.例子1:
		class Door extends Thread 
		{
			//===================第一种方法===================================
			//static静态变量(类变量) 在整个程序中 始终只有一份
			private static Hashtable map = new Hashtable<>();  //Hashtable中的方法都是synchronized修饰 
			static int num = 0;

			public void run() 
			{
				String name = this.getName();
				while (true) 
				{
					// getUnusedId方法的作用:随机返回一个还没有进门的学生的id,如果返回-1,就表明所有学生都进来了
					int id = Demo03.getUnusedId();
					if (id == -1)
					 {
						num++;
						if (num == 2) 
						{
							 // 表明最后一个线程执行完了
							System.out.println(map);
						}
						break;
					}
					System.out.println(name + "进来了id为 " + id + " 的学生");

					// 统计个数
					if (map.containsKey(name)) 
					{
						map.put(name, map.get(name) + 1);
					} 
					else
					 {
						map.put(name, 1);
					}

				}
			}
	
			//===================第二种方法===================================
			//static静态变量(类变量) 在整个程序中 始终只有一份
			private static HashMap map = new HashMap<>(); //HashMap中的方法并没有synchronized修饰 
			static int num = 0;

			public void run() 
			{
				String name = this.getName();
				while (true) 
				{
					synchronized(Door.class)
					{
						int id = Demo03.getUnusedId();// getUnusedId方法的作用:随机返回一个还没有进门的学生的id,如果返回-1,就表明所有学生都进来了
						if (id == -1) 
						{
							num++;
							if (num == 2) 
							{ // 表明最后一个线程执行完了
								System.out.println(map);
							}
							break;
						}
						System.out.println(name + "进来了id为 " + id + " 的学生");

						// 统计个数
						if (map.containsKey(name)) 
						{
							map.put(name, map.get(name) + 1);
						} 
						else 
						{
							map.put(name, 1);
						}
					}
				}
			}
		}

		public class Demo03 
		{
			//static静态变量(类变量) 在整个程序中 始终只有一份
			private static Random r = new Random();
			private static ArrayList usedList = new ArrayList<>();

			//使用了第一种方法的话,此处就使用同步方法
			public synchronized static int getUnusedId() 

			//使用了第二种方法的话,此处就不使用同步方法
  			//static静态变量(类变量) 在整个程序中 始终只有一份
			//public   static int getUnusedId()			
			{
				// getUnusedId方法的作用:返回一个还没有进门的学生的id,如果返回-1,就表明所有学生都进来了
				if (usedList.size() >= 64) 
				{
					return -1;
				}

				int id = r.nextInt(64) + 1;
				while (usedList.contains(id)) 
				{
					id = r.nextInt(64) + 1;
				}

				usedList.add(id);
				return id;
			}

			public static void main(String[] args) 
			{
				Door t1 = new Door();
				Door t2 = new Door();
				t1.setName("前门");
				t2.setName("后门");
				t1.start();
				t2.start();
			}
		}
		--------------------例子1中使用到的知识点解释------------------------ 
		1.HashTable 和 HashMap
			1.两者区别:
				如果HashTable中的方法都带有synchronized关键字,而HashMap中的方法是不带有synchronized关键字;
				因此在多线程的环境中,使用HashTable是安全的,但性能比HashMap低,使用HashMap是不安全的,
				但性能比HashTable高;

			2.多线程下的 HashTable 和 HashMap 的选择使用:
				多线程的程序中 HashTable / HashMap 作为共享数据的话,那么有以下两种使用方法:
				1.使用方法一:多线程中 使用HashTable(共享数据),HashTable中的方法都带有synchronized修饰,是安全的;
				2.使用方法二:多线程中 使用HashMap,但必须在 synchronized同步代码块、synchronized同步方法中 
						操作 HashMap(共享数据)

		2.在一个多线程的 程序中,多线程环境下 使用到了 多个 synchronized同步代码块 / synchronized同步方法 / 底层带有synchronized的集合 
		    的话,那么在这个程序中 就会存在 多个 不同类型的  同步锁对象,那么当一个线程获取了某一个同步锁对象,
		    而另外的线程则可以获取除这个之外的 其他的同步锁对象,每个不同类型的 同步锁对象 各自只有一份,
		    一个同步锁对象最多只可以被一个线程所持有;
		------------------------------------------------------------------------------------ 


	2.例子2:
		public class Demo01 
		{
			public static void main(String[] args)
			 {
				//final 修饰的变量在常量区,静态区/常量区的变量的生命周期 都比 局部变量 的生命周期要长	
				final int[] arr = new int[6];

				new Thread("线程1") 
				{
					public void run()
					 {
						set(arr); // set方法的作用:为arr数组的随机3个索引赋值
					}
				}.start();

				new Thread("线程2") 
				{
					public void run()
					 {
						set(arr); // set方法的作用:为arr数组的随机3个索引赋值
					};
				}.start();

			}
			// set方法的作用:为arr数组的随机3个索引赋值
			protected static void set(int[] arr) 
			{
				for (int i = 0; i < 3; i++) 
				{
					int index = getUnusedIndex(); // getUnusedIndex方法的作用:返回一个没有使用过的索引
					int num = getRanNum(); // getRanNum:返回一个随机数
					arr[index] = num;
					System.out.println(Thread.currentThread().getName() + " 刚给索引:" + index + " 赋值 " + num);
					try {
						Thread.sleep(20);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
			private static ArrayList usedList = new ArrayList<>();// usedList集合专门用来存放已经被用过的索引
			private static  synchronized  int getUnusedIndex()
			 {
				int index = r.nextInt(6);
				while (usedList.contains(index)) 
				{
					index = r.nextInt(6);
				}
				usedList.add(index);
				return index;
			}
			private static Random r = new Random();
			private static int getRanNum()
		 	{
				return r.nextInt(101);
			}
		}


1.并行和并发和串行:
	1.并发和并行是即相似又有区别(微观概念):
		1.并行:指两个或多个事件在 同一时刻点 瞬间同时 发生;
		2.并发:指两个或多个事件在 同一时间段内 交替发生;

 	2.在操作系统中,在多道程序环境下,并发性是指在一段时间内宏观上有多个程序在同时运行,但在单CPU系统中,
	    每一时刻却仅能有一道程序执行(时间片),故微观上这些程序只能是分时地交替执行。
	   倘若计算机系统中有多个CPU,则这些可以并发执行的程序便可被分配到多个处理器上,实现多任务并行执行,
	   即利用每个处理器来处理一个可并发执行的程序,这样,多个程序便可以同时执行,因为是微观的,
	   所以大家在使用电脑的时候感觉就是多个程序是同时执行的。
	   所以,大家买电脑的时候喜欢买“核”多的,其原因就是“多核处理器”电脑可以同时并行地处理多个程序,从而提高了电脑的运行效率。
	   单核处理器的计算机肯定是不能并行的处理多个任务的,只能是多个任务在单个CPU上并发运行。
	   同理,线程也是一样的,从宏观角度上理解线程是并行运行的,但是从微观角度上分析却是串行运行的,
	   即一个线程一个线程的去运行,当系统只有一个CPU时,线程会以某种顺序执行多个线程,我们把这种情况称之为线程调度。
 	   时间片即CPU分配给各个程序的运行时间(很小的概念);

	3.多线程串行:即为多个线程排成队列,排队执行,每个线程逐一执行,一个线程执行结束了,才轮到下一个线程开始执行

Java 多进程、多线程、多线程中的共享变量的安全问题、同步锁、线程同步、ThreadLocal_第1张图片

2.进程和线程:
	1.进程是指一个内存中运行中的应用程序。每个进程都有自己独立的一块内存空间,一个应用程序可以同时启动多个进程。
	    比如在Windows系统中,一个运行的abc.exe就是一个进程。
	    那么我们此时就可以处理同时玩游戏和听音乐的问题了,我们可以设计成两个程序,一个专门负责玩游戏,一个专门负责听音乐。
	    但是问题来了,要是要设计一个植物大战僵尸游戏,我得开N个进程才能完成多个功能,这样的设计显然是不合理的。
	    更何况大多数操作系统都不需要一个进程访问其他进程的内存空间,也就是说进程之间的通信很不方便。
	    此时我们得引入“线程”这门技术,来解决这个问题。
	    线程是指进程中的一个执行任务(控制单元),一个进程可以同时并发运行多个线程,如:多线程下载软件。
	    多任务系统,该系统可以运行多个进程,一个进程也可以执行多个任务,一个进程可以包含多个线程;
	    一个进程至少有一个线程,为了提高效率,可以在一个进程中开启多个执行任务,即多线程。

	2.多进程:操作系统中同时运行的多个程序。
	   多线程:在同一个进程中同时运行的多个任务。
 
	3.进程与线程的区别:
		进程:有独立的内存空间,进程中的数据存放空间(堆空间和栈空间)是独立的,至少有一个线程。
		线程:堆空间是共享的,栈空间是独立的,线程消耗的资源也比进程小,相互之间可以影响的,又称为轻型进程或进程元。

	4.在操作系统中允许多个任务,每一个任务就是一个进程,每一个进程也可以同时执行多个任务,每一个任务就是线程。
	   因为一个进程中的多个线程是并发运行的,那么从微观角度上考虑也是有先后顺序的,那么哪个线程执行完全取决于CPU调度器(JVM来调度),
	   程序员是控制不了的。我们可以把多线程并发性看作是多个线程在瞬间抢CPU资源,谁抢到资源谁就运行,这也造就了多线程的随机性。

	5.Java程序的进程(Java的一个程序运行在系统中)里至少包含主线程和垃圾回收线程(后台线程)。

	6.线程调度:
		计算机通常只有一个CPU时,在任意时刻只能执行一条计算机指令,每一个进程只有获得CPU的使用权才能执行指令;
		所谓多进程并发运行,从宏观上看,其实是各个进程轮流获得CPU的使用权,分别执行各自的任务;
		那么,在可运行池中,会有多个线程处于就绪状态等到CPU,JVM就负责了线程的调度;
		JVM采用的是抢占式调度,没有采用分时调度,因此可以能造成多线程执行结果的的随机性。


	7.多线程优势:
		1.多线程优势:多线程作为一种多任务、并发的工作方式,当然有其存在优势;
			1.进程之前不能共享内存,而线程之间共享内存(堆内存)则很简单。
			2.系统创建进程时需要为该进程重新分配系统资源,创建线程则代价小很多,因此实现多任务并发时,多线程效率更高;
			3.Java语言本身内置多线程功能的支持,而不是单纯第作为底层系统的调度方式,从而简化了多线程编程; 
	
		2.多线程下载:
			可以理解为一个线程就是一个文件的下载通道,多线程也就是同时开起好几个下载通道。
			当服务器提供下载服务时,使用下载者是共享带宽的,在优先级相同的情况下,总服务器会对总下载线程进行平均分配。
			不难理解,如果你线程多的话,那下载的越快。现流行的下载软件都支持多线程。
			多线程是为了同步完成多项任务,不是为了提供程序运行效率,而是通过提高资源使用效率来提高系统的效率。
			所以现在大家买电脑的时候,也应该看看CPU的线程数。

			宽带带宽是以位(bit)计算,而下载速度是以字节(Byte)计算,1字节(Byte)等于8位(bit),
			所以1024kb/s是代表上网带宽为1024千位(1M),而下载速度需要1024千位/秒(1024kb/s)带宽除以8,
			得出128千字节/秒(128KB/s)。

Java 多进程、多线程、多线程中的共享变量的安全问题、同步锁、线程同步、ThreadLocal_第2张图片

3.开启多进程:
	1.Java操作进程:
		1.Runtime类的exec方法 
		2.ProcessBuilder类的start方法 

 Java 多进程、多线程、多线程中的共享变量的安全问题、同步锁、线程同步、ThreadLocal_第3张图片

4.开启多线程:
	1.创建和启动线程,传统有两种方式:
		1.方式1:继承Thread类 ;
			   线程类(java.lang.Thread):Thread类和Thread的子类 称之为线程类;
			   类 Thread 实现了接口Runnable ;
		2.方式2:实现Runnable接口 ;
		3.方式3:使用匿名内部类 来创建线程,只适用于某一个类只使用一次的情况;

 	2.方式1:继承Thread类 
		1.创建步骤:
			1.定义一个类A 继承于 java.lang.Thread类 ,Thread类和Thread的子类类A 称之为线程类;
			2.在A类中 覆盖Thread类中的 run方法 ;
			3.我们在run方法中 编写需要执行的操作:run方法里的代码 为线程执行体 ;
			4.在main方法(线程)中,创建线程对象,并启动线程:
    				1.创建线程类对象:A类   a  =  new   A类() ;
    				2.调用线程对象的start方法:a.start() 启动一个线程;
				3.注意:千万不要调用run方法,如果调用run方法好比是对象调用方法,依然还是只有一个线程,
					 并没有开启新的线程;

	3.方式2:实现Runnable接口 
		1.创建步骤:
			1.定义一个类A 实现 java.lang.Runnable接口,注意A类不是线程类;
			    只有 Thread类和Thread的子类 才称之为线程类;
			2.在A类中 覆盖/重写 Runnable接口中的 run方法;
			3.我们在run方法中 编写需要执行的操作:run方法中的代码为 线程执行体;
			4.在main方法(线程)中,创建线程对象,并启动线程:
     				1.创建线程类对象:
						Thread   t  =  new  Thread(new  A());  // 启动线程 t.start()
						Runnable  a  =  new  A()   //多态
						Thread   t  =  new  Thread( a );    // 启动线程 t.start()
						 new  Thread(new  A(),  “线程名字”).start()  // 链式编程:此处启动线程 
     				2.调用线程对象的start方法:t.start()  启动一个线程

	4.Runnable接口:(Thread类实现了Runnable接口)
 			1.方法: void run() 
          				 使用实现接口 Runnable 的对象创建一个线程时,启动该线程将导致在独立执行的线程中调用对象的 run 方法。 
			2.public interface RunnableRunnable 接口:
				应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为 run 的无参数方法。 
				设计该接口的目的是为希望在活动时执行代码的对象提供一个公共协议。
				例如,Thread 类实现了 Runnable。激活的意思是说某个线程已启动并且尚未停止。 
				此外,Runnable 为非 Thread 子类的类提供了一种激活方式。通过实例化某个 Thread 实例并将自身作为运行目标,
				就可以运行 Runnable 的实现类而无需创建 Thread 的子类。大多数情况下,如果只想重写 run() 方法,
				而不重写其他 Thread 方法,那么应使用 Runnable 接口。
				这很重要,因为除非程序员打算修改或增强类的基本行为,否则不应为该类创建子类。 
			3.在线程的 run 方法上不能使用 throws 来声明抛出异常,只能在方法中使用  try-catch来处理异常。
	   		   原因是:子类覆盖父类方法的原则,在Runnable接口中的run方法,都没有声明抛出异常,其子类不能抛出新的异常,
		      		     因此其子类重写的 run 方法更不能抛出异常

	5.Thread类:(Runnable 接口的实现类Thread类)
			1.构造器:
				1.Thread():分配新的 Thread 对象。 
				2.Thread(Runnable target):传入Runnable的实现类的实例对象,分配新的 Thread 对象。 
				3.Thread(Runnable target, String name):传入Runnable的实现类的实例对象 和 新线程的名称,分配新的 Thread 对象。 
				4.Thread(String name):传入 新线程的名称,分配新的 Thread 对象。 
 
			2.方法:
				static Thread currentThread() 返回对当前正在执行的线程对象的引用。 
				long getId() 返回该线程的标识符。 
				String getName() 返回该线程的名称。
				void setName(String name) 改变线程名称,使之与参数 name 相同。  
				int getPriority() 返回线程的优先级。 
				Thread.State getState() 返回该线程的状态。 
				ThreadGroup getThreadGroup() 返回该线程所属的线程组。 
				void interrupt() 中断线程。 
				boolean isInterrupted() 测试线程是否已经中断。 
				static boolean interrupted() 测试当前线程是否已经中断。 
				boolean isAlive() 测试线程是否处于活动状态。 
				boolean isDaemon() 测试该线程是否为守护线程。 
				void setDaemon(boolean on) 将该线程标记为守护线程或用户线程。 
				void setContextClassLoader(ClassLoader cl) 设置该线程的上下文 ClassLoader。 
				void join() 等待该线程终止。 
				void join(long millis) 等待该线程终止的时间最长为 millis 毫秒。 
				void join(long millis, int nanos) 等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。 
				String toString() 返回该线程的字符串表示形式,包括线程名称、优先级和线程组。 
				static void yield() 暂停当前正在执行的线程对象,并执行其他线程。 
				static void sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),
							   此操作受到系统计时器和调度程序精度和准确性的影响。 
				static void sleep(long millis, int nanos) 在指定的毫秒数加指定的纳秒数内让当前正在执行的线程休眠(暂停执行),
								      此操作受到系统计时器和调度程序精度和准确性的影响。 
				void start() 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。 
				void run() 如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;
					   否则,该方法不执行任何操作并返回。 Thread 的子类应该重写该方法。 

Java 多进程、多线程、多线程中的共享变量的安全问题、同步锁、线程同步、ThreadLocal_第4张图片

	6.方式3:使用匿名内部类 来创建线程,只适用于某一个类只使用一次的情况;
		1.匿名内部类 创建线程的 第一种写法:
		         例子:在一个自定义类中 使用"new  父类Thread(){ ...... }" 即进行创建出来的 匿名子类对象中(也即 父类Thread线程类 的子线程类中) 
			       重写run()方法,即该自定义类会继承父类Thread线程类,该自定义类便成为子线程类,
			       那么最终实际为 该子线程的实例对象 负责来 该重写好的run()方法;
				public   class   TestDemo1  
				{
					public static void main(String[] args) 
					{
						-------------------------第一种写法---------------------- 
						new Thread()  
						{
							@Override
							public void run()
							{
								//创建出来的子线程执行的代码
							}
						}.start();

						-------------------------第二种写法---------------------- 
						Thread t = new Thread()  
						{
							@Override
							public void run()
							{
								//创建出来的子线程执行的代码
							}
						};
						t.start();
					}
				}

				上面的代码 底层编译为:
					class TestDemo1  extends  Thread
					{
						public void run() //重写了 父类Thread线程类中的 run()方法
						{
							//创建出来的子线程执行的代码
						}
					}

		2.匿名内部类 创建线程的 第二种写法:
			例子:在一个自定义类中 使用"new  父类Thread(......)" 即进行 创建出来的Thread类的匿名对象中(也即 Thread类的构造器中) 
			              传入 “new  接口Runnable(){......}”,底层实际为该自定义类实现接口Runnable,该自定义类也即为接口Runnable的实现类
			              那么“new  接口Runnable(){......}” 也即为创建匿名实现类对象,也即为创建接口Runnable的实现类的匿名对象,
			              而接口的方法体中重写run()方法 也即 实现类中进行 重写 run()方法,
			              那么最终的 Thread线程类的构造器中 实际传入的是 该接口Runnable的实现类的匿名对象,
			              然后Thread线程类实例对象调用start()方法,实际为 调用执行 实现类的匿名对象中的 重写好了的 run()方法

				public   class   TestDemo1  
				{
					public static void main(String[] args) 
					{
						-------------------------第一种写法----------------------
						new Thread(new Runnable() 
						{
							@Override
							public void run()
							{
								//创建出来的子线程执行的代码	 						
							}					
						}).start();

						-------------------------第二种写法---------------------- 
						Thread t = new Thread(new Runnable() 
						{
							@Override
							public void run()
							{		
						 	 	//创建出来的子线程执行的代码					
							}					
						});
						t.start();
					}
				}

				上面的代码 底层编译为:
					class  TestDemo1  implements Runnable
					{
						public void run() //重写了Runnable接口中的 空方法体run() 
						{
							//子线程执行的代码
						}
					}
		3.匿名内部类 的写法:
			1.第一种用法:匿名子类对象(匿名内部类)
				---------------------------------------- 写法一:匿名子类对象(匿名内部类) ------------------------------------------------------------- 
				class  A
				{
					//创建 匿名子类对象:class  A类的 匿名对象
					new  父类类名([实参列表])  // 调用的 可以是 空参构造器 或 有参构造器
					{ 
	  					public void  XX(){ ...... } //实际为 子类class  A 重写父类中的 public void  XX()方法
					} ; //必须分号结束
				}

			     ------> 底层编译为:
				//创建 匿名子类对象:class  A类的 匿名对象
				class  A  extends  父类类名  //子类class  A  继承 父类
				{
					public void  XX(){ ...... } //子类class  A中 重写了 父类 中的 XX()方法
				}	

				--------------------------------------------- 写法二:匿名子类对象(匿名内部类)  ------------------------------------------------------- 
				class  A
				{
					//创建 匿名子类对象:class  A类的 匿名对象
					new  父类类名([实参列表])  // 调用的 可以是 空参构造器 或 有参构造器
					{ 
	  					public void  XX(){ ...... } //实际为 子类class  A 重写父类中的 public void  XX()方法
					} .XX()方法;   // 匿名子类对象(class  A类的 匿名对象) 调用重写好了的 XX()方法
				}

			 ------> 底层编译为:
				//创建 匿名子类对象:class  A类的 匿名对象
				class  A  extends  父类类名   //子类class  A  继承 父类
				{
					public void  XX(){ ...... } //子类class  A中 重写了 父类 中的 XX()方法
				}	
				new  A().XX()方法 // 匿名子类对象(class  A类的 匿名对象) 调用重写好了的 XX()方法

				--------------------------------------------- 写法三:匿名子类对象(匿名内部类)  ------------------------------------------------------- 
				class  A
				{
					父类类名  obj  =  new  父类类名([实参列表])  // 调用的 可以是 空参构造器 或 有参构造器
					{ 
	  					public void  XX(){ ...... }  //实际为 子类class  A 重写父类中的 public void  XX()方法
					} ; //必须分号结束
					obj.XX()方法;    // 匿名子类对象(class  A类的 匿名对象) 调用重写好了的 XX()方法
				}

			 ------> 底层编译为:
				class  A  extends  父类类名     //子类class  A  继承 父类	
				{
					public void  XX(){ ...... } //子类class  A中 重写了 父类 中的 XX()方法
				}	
				new  A().XX()方法  // 匿名子类对象(class  A类的 匿名对象) 调用重写好了的 XX()方法


 			2.第二种用法:匿名实现类对象(匿名内部类)
				---------------------------------------- 写法一:匿名实现类对象(匿名内部类) ------------------------------------------------------------- 
				class  A
				{
					new 接口()
					{ 
	   					public void  XX(); //实际为 实现类class  A 重写接口中的 public void  XX()方法(XX()方法为 空方法体)
					} ; //必须分号结束
				}

			   ------> 底层编译为::
				class  A  implements   接口名
				{
					public void  XX(){ ...... } //实现类 class  A中 重写了 接口中的 空方法体的 XX()方法
				}

				--------------------------------------------- 写法二:匿名实现类对象(匿名内部类)  ------------------------------------------------------- 

				class  A
				{
					new 接口()
					{ 
	   					public void  XX(); //实际为 实现类class  A 重写接口中的 public void  XX()方法(XX()方法为 空方法体)
					} .XX()方法;   //匿名实现类对象(class  A实现类的匿名对象) 调用实现类中 重写好了的 XX()方法
				}
			
			  ------> 底层编译为:
				class  A  implements   接口名
				{
					public void  XX(){ ...... } //实现类 class  A中 重写了 接口中的 空方法体的 XX()方法
				}
				new  A().XX()方法  //匿名实现类对象(class  A实现类的匿名对象) 调用实现类中 重写好了的 XX()方法 

				--------------------------------------------- 写法三:匿名实现类对象(匿名内部类)  ------------------------------------------------------- 

				class  A
				{
					接口名  obj  =  new 接口名()
					{ 
	   					public void  XX(); //实际为 实现类class  A 重写接口中的 public void  XX()方法(XX()方法为 空方法体)
					} ; //必须分号结束
					obj.XX()方法;    //匿名实现类对象(class  A实现类的匿名对象) 调用实现类中 重写好了的 XX()方法
				}

			    ------> 底层编译为:
				class  A  implements   接口名
				{
					public void  XX(){ ...... } //实现类 class  A中 重写了 接口中的 空方法体的 XX()方法
				}
				new  A().XX()方法  //匿名实现类对象(class  A实现类的匿名对象) 调用实现类中 重写好了的 XX()方法 


			3.第三种用法:
				--------------------------------------------- 写法一:匿名实现类对象(匿名内部类)  -------------------------------------------------------
				class  A
				{
					// 1.首先创建的是 自定义类的匿名对象,
					//    然后在自定义类的构造器中 传入 匿名实现类对象(接口的实现类的匿名对象)  。 
					// 2.例子有:new  Thread( new Runnable接口名(){ //重写run()方法} ).start()(start()实际调用的是重写好的run())
					new  自定义类名( new 接口()
					{ 
	   					public void  XX(); //实际为 实现类class  A 重写接口中的 public void  XX()方法(XX()方法为 空方法体)
					} ); //必须分号结束
				}

			    ------> 底层编译为:
				class  A   implements   接口名
				{
					public void  XX(){ ...... } //实现类 class  A中 重写了 接口中的 空方法体的 XX()方法
				}
				new  自定义类名(new  A())   //例子有:new  Thread(new  实现类A())

				--------------------------------------------- 写法二:匿名实现类对象(匿名内部类)  -------------------------------------------------------
				class  A
				{
					// 1.首先创建的是 自定义类的匿名对象,
					//    然后在自定义类的构造器中 传入 匿名实现类对象(接口的实现类的匿名对象)  。 
					// 2.例子有:new  Thread( new Runnable接口名(){ //重写run()方法} ).start()(start()实际调用的是重写好的run())
					new  自定义类名( new 接口()
					{ 
	   					public void  XX(); //实际为 实现类class  A 重写接口中的 public void  XX()方法(XX()方法为 空方法体)
					} ) .XX(); //必须分号结束
				}

			    ------> 底层编译为:
				class  A   implements   接口名
				{
					public void  XX(){ ...... } //实现类 class  A中 重写了 接口中的 空方法体的 XX()方法
				}
				new  自定义类名(new  A()).XX(); //例子有:new  Thread(new  实现类A()).start()(start()实际调用的是重写好的run())

				--------------------------------------------- 写法三:匿名实现类对象(匿名内部类)  -------------------------------------------------------
				class  A
				{
					// 1.首先创建的是 自定义类的匿名对象,
					//    然后在自定义类的构造器中 传入 匿名实现类对象(接口的实现类的匿名对象)  。 
					// 2.例子有:new  Thread( new Runnable接口名(){ //重写run()方法} ).start()(start()实际调用的是重写好的run())
					自定义类名  obj  =  new  自定义类名( new 接口()
					{ 
	   					public void  XX(); //实际为 实现类class  A 重写接口中的 public void  XX()方法(XX()方法为 空方法体)
					} ); //必须分号结束
					 obj.XX();
				}

			    ------> 底层编译为:
				class  A   implements   接口名
				{
					public void  XX(){ ...... } //实现类 class  A中 重写了 接口中的 空方法体的 XX()方法
				}
				new  自定义类名(new  A()).XX(); //例子有:new  Thread(new  实现类A()).start()(start()实际调用的是重写好的run())


		4.匿名对象、匿名子类对象(匿名内部类)、匿名实现类对象(匿名内部类)  结合 Thread、Runnable 一起使用 的区别:
				1.匿名对象:
					1.匿名对象的创建:
						构造器后面是不带有方法体的,例子:new  类名([实参列表]) 创建的是该类的匿名对象

					2.匿名对象调用方法:
						可以直接在匿名对象的后面接着调用本类(匿名对象所属的类)中的方法;
						例子:1.new  类名([实参列表]).方法名()、
						             2.new  Thread(Runnable target).start():
							解析:
								创建的是Thread线程类的匿名对象,然后调用匿名对象所属的类中的start()方法,
								start()方法的底层又会去调用重写好了的run()方法。
 
				2.匿名子类对象(使用匿名内部类):
					1.匿名子类对象的创建:
						父类构造器 后面是带有方法体的,例子:new 父类构造器([实参列表]){ //重写父类中方法 }。

					2.匿名子类对象 调用方法:
						可以直接在匿名子类对象的后面接着调用本类(父类)中的方法;
						例子:1.new  父类类名([实参列表]){ //重写父类中方法 }.方法名() 、
			             			              2.new Thread([String name线程名]){ //重写父类Thread中的run()方法 }.start():
							解析:
								class  A类中 创建的是 Thread线程类父类的 匿名子类对象,
								Thread线程类是父类,class  A类便是子类,而匿名子类对象指的就是
								class  A类的 匿名对象,父类Thread的方法体中重写 run()方法,
								实际就是在class  A类中重写 父类Thread中的run()方法,然后匿名子类对象调用
								start()方法,实际就是class  A类的匿名对象调用start()方法,
								start()方法的底层又会去调用重写好了的run()方法。

				3.匿名实现类对象(匿名内部类):
					1.匿名实现类对象的创建:
						接口名 后面是带有方法体的;例子:new 接口(){ //重写接口中空方法体 } 

					2.匿名实现类对象 调用方法:
						可以直接在匿名实现类对象的后面接着调用本接口中的方法;
						例子:new 接口(){ //重写接口中空方法体 }.方法名() 、
						              new Runnable(){ //重写Runnable接口 中的 run()方法 }.run():
							解析:
								class  A类中 创建的是 Runnable接口的 匿名实现类对象,
								class  A类 便是  Runnable接口的 实现类,而匿名实现类对象指的就是
								class  A实现类的 匿名对象,Runnable接口的方法体中重写run()方法,
								实际就是在 class  A实现类中重写 Runnable接口中的 run()方法,
								然后匿名实现类对象调用start()方法,实际就是 class  A实现类的 匿名对象调用
								start()方法,start()方法的底层又会去调用重写好了的run()方法。

				4.匿名对象 的构造器中 传入 匿名实现类对象:
					1.“构造器中 传入 匿名实现类对象的” 匿名对象的 创建:
						new  类名([实参列表]):刚好该实参列表中的实参可以是传入 匿名实现类对象
						例子:new  类名(new 接口(){ //重写接口中空方法体 })

					2.“构造器中 传入 匿名实现类对象的” 匿名对象 调用方法:
						可以直接在该匿名对象的后面接着调用本类(匿名对象所属的类)中的方法;
						例子:new  类名(new 接口(){ //重写接口中空方法体 }).方法名()、
						             new  Thread(new Runnable(){ //重写Runnable接口 中的 run()方法 }).start()
							解析:
								class  A类中 先是执行new  Thread(Runnable target) 创建 Thread线程类的匿名对象,
								然后在其Thread类的构造器中传入 Runnable接口的 匿名实现类对象,
								那么底层实际编译为 class  A类 implements Runnable,
								那么创建的 Runnable接口的 匿名实现类对象 实际为创建 class  A类的 匿名的实例对象,
								在该Runnable接口的 匿名实现类对象的方法体 中重写 run()方法 实际就为 
								class  A类中 重写 Runnable接口中的 run()方法,
								最终变成 new  Thread(class  A类的 匿名的实例对象).start(),
								也即 Thread类的构造器中传入 class  A类的 匿名的实例对象,然后调用 start()方法,
								 然后start()方法 底层调用的是 class  A类中 重写了的 run()方法;    							

		5.使用 匿名内部类的方式 创建一个线程 的底层原理:
			1.class  自定义类(即子类)  extends  父类:
				1.在一个自定义类中 使用 new 父类构造器([实参列表]){//重写方法},实际为 创建该父类的 匿名子类对象,
				   也即该自定义类 会在底层继承这个父类,然后创建出 这个自定义类(子类)的 匿名对象,
				   所以叫做 创建该父类的 匿名子类对象;
				   {//重写方法}  表示的是在 自定义类(子类)中 重写 父类中的 方法;

				2.例子:class  自定义子线程类  extends  Thread 的方式
						new Thread()  // new 父类构造器([实参列表]){//重写方法}   	
						{
							@Override
							public void run()
							{
								//创建出来的子线程执行的代码
							}
						}.start();

					1.在一个自定义类中 使用new Thread([实参列表]){//重写run()方法},
					    那么底层该自定义类实际会编译成子线程类, 
					    然后子线程类继承父类Thread线程类:class  自定义子线程类  extends  Thread  ;

					2.第一步:底层 自定义类 继承线程类Thread:class  自定义子线程类  extends  Thread;
					3.第二步:创建该父类 线程类Thread的 匿名子类对象,实际即为创建 父类Thread线程类的 
						     自定义子线程类的 一个匿名对象;			
					4.第三步:{//重写run()方法} 表示的是会在 自定义子线程类中 重写 “父类Thread线程类中的” run()方法;
					5.父类Thread线程类的匿名子类对象 调用 start() 方法,实际底层为 自定义子线程类的 匿名对象 去调用 
					   start() 方法,然后start()方法底层会去调用 “自定义子线程类中的重写了父类Thread线程类中的” run()方法
 
			2.class  自定义类(即实现类)  implements   接口:
				1.在一个自定义类中 使用 new 接口(){//重写方法}的话,那么底层实际把该自定义类 编译成 该接口的实现类,
				    也即变成 “class  自定义实现类  implements  接口”,而在接口的方法体中重写了的 run()方法 实际为 
				    该自定义实现类中 重写 该接口中的 run()方法;
  
				2.例子:class  自定义实现类  implements  Runnable的方式

					1.注意:匿名对象 和 匿名子类对象(匿名内部类) 和 匿名实现类对象(匿名内部类) 的区别:
							1.匿名对象:构造器后面是不带有方法体的;
							2.匿名子类对象(使用匿名内部类):父类构造器 后面是带有方法体的;  
							3.匿名实现类对象(匿名内部类):接口名 后面是带有方法体的;

						 // 1.此处的 “Thread构造器中 传入 new Runnable接口名(){//重写方法}”:实际为 Thread线程类的
						 //     匿名对象中 传入 Runnable接口的 匿名实现类对象; 
						// 2.Runnable接口的 匿名实现类对象 实际为  Runnable接口的 实现类的 匿名的 实例对象; 
						new Thread(new Runnable()  
						{
							@Override
							public void run()
							{
								//创建出来的子线程执行的代码	 						
							}					
						} ).start();

					2.在一个自定义类中 使用 new 接口Runnable(){//重写方法},那么底层该自定义类 编译成该Runnable接口
					    的实现类,而在接口的方法体中重写了的 run()方法 实际为 实现类中 重写 该接口中run()方法;

					3.创建 Thread线程类的 匿名对象,调用Thread线程类的构造器:new Thread(Runnable target);
					    此处为调用Thread的构造器创建Thread线程类的 匿名对象,并不是创建Thread线程类的 匿名子类对象;
					    并且 Thread线程类的构造器需要传入的是Runnable接口 对象或Runnable 接口的实现类的 实例对象,
					    因此此处定义的 Thread线程类的构造器中传入的是 Runnable 接口的 匿名实现类对象,
					    而  Runnable 接口的 匿名实现类对象 实际也即为 Runnable 接口的 实现类的 匿名实例对象,
					    所以最终结果是  Thread线程类的构造器中传入 Runnable 接口的 实现类的 匿名实例对象,
					    然后Thread线程类执行该实现类中重写好了的 run()方法;
 
					4.调用Thread线程类的 构造器Thread(Runnable target) ,创建 Thread线程类的 匿名对象,
					    并不是创建 Thread线程类的 匿名子类对象,因此 构造器Thread(Runnable target) 后面不带有方法体; 
					5.构造器Thread(Runnable target) 中 传入的是 Runnable 接口对象 或 Runnable 的实现类对象,
					    而此处 构造器Thread(Runnable target) 中 传入的是 带有方法体的 “new  接口Runnable(){//重写方法}”,
					    也即传入 Runnable 接口的 匿名实现类对象,实际也即为传入 Runnable 接口的 实现类的 匿名实例对象;
 
					6.那么在当前的自定义类中使用的是 带有方法体的 “new  接口Runnable(){//重写方法}”,
					    那么当前的自定义类 实际会被编译为 接口Runnable的 实现类,
					    也即当前的自定义类 为 接口Runnable的 实现类;
							 
					7.那么在 “new  接口Runnable(){//重写方法}” 的方法体中 重写 run()方法,
					   实际是在 自定义实现类中 重写 接口Runnable中的 run()方法;
					8.构造器Thread( “重写了run()方法的 Runnable 接口的 实现类的” 匿名实例对象 ).start():
					    Thread线程类调用start()方法,实际就是Thread线程类 执行 Runnable 接口的 实现类中的 重写好了的
					     run()方法;

Java 多进程、多线程、多线程中的共享变量的安全问题、同步锁、线程同步、ThreadLocal_第5张图片

5.继承方式 创建线程 和 实现方式 创建线程 的区别:
	1.继承方式:
		1.Java中类是单继承的,如果继承了Thread了,该类就不能再有其他的直接父类了 
                	2.从操作上分析,继承方式更简单,获取线程名字也简单 (操作上,更简单)
                	3.从多线程共享同一个资源上分析,继承方式不能做到 共享同一个资源  

	2.实现方式:               
		1.Java中类可以多实现接口,此时该类还可以继承其他类,并且还可以实现其他接口(设计上,更优雅) 
 		2.从操作上分析,实现方式稍微复杂点,获取线程名字也比较复杂,得使用Thread.currentThread()来获取当前线程的引用 
		3.从多线程共享同一个资源上分析,实现方式可以做到 共享同一个资源 

Java 多进程、多线程、多线程中的共享变量的安全问题、同步锁、线程同步、ThreadLocal_第6张图片

Java 多进程、多线程、多线程中的共享变量的安全问题、同步锁、线程同步、ThreadLocal_第7张图片

6.线程同步:
	1.线程不安全的问题分析:
		当多线程并发访问同一个资源对象的时候,可能出现线程不安全的问题;
		Thread.sleep(10):当前线程睡10毫秒,当前线程休息着,让其他线程去抢资源,经常用来模拟网络延迟;             
		在程序中并不是使用Thread.sleep(10)之后,程序才出现问题,而是使用之后,问题更明显;

	2.在线程的 run 方法上不能使用 throws 来声明抛出异常,只能在方法中使用  try-catch来处理异常。
	   原因是:子类覆盖父类方法的原则,在Runnable接口中的run方法,都没有声明抛出异常,其子类不能抛出新的异常,
		     因此其子类重写的 run 方法更不能抛出异常

	3.要解决上述多线程并发访问多一个资源的安全性问题:
	    解决方案:A线程进入操作的时候,B、C线程只能在外等着,A线程操作结束,A、B、C线程才有机会进入代码去执行 
 		1.方式1:同步代码块 (使用synchronized修饰)
		2.方式2:同步方法 (使用synchronized修饰)
		3.方式3:锁机制(Lock)

	4.同步代码块(synchronized修饰)、同步方法(synchronized修饰)、锁机制(Lock)、同步监听对象/同步锁/同步监听器/互斥锁
		1.同步代码块(synchronized修饰):
			synchronized(同步锁)
			{
     				需要同步操作的代码
			}

		2.同步监听对象/同步锁/同步监听器/互斥锁:
				1.为了保证每个线程都能正常执行原子操作,Java引入了线程同步机制;
				    对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁;
				    Java程序运行使用任何对象作为同步监听对象,但是一般的,我们把当前并发访问的共同资源作为同步监听对象;
				    注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着;
				2.同步锁必须选择多个线程共同的资源对象;
			   	    当前生产者在生产数据的时候(先拥有同步锁),其他线程就在锁池中等待获取锁;
      			   	    当线程执行完同步代码块的时候,就会释放同步锁,其他线程开始抢锁的使用权;    
				3.多个线程只有使用相同的一个对象的时候,多线程之间才有互斥效果。
			   	    我们把这个用来做互斥的对象称之为,同步监听对象/同步锁;
 			   	    同步锁对象可以选择任意类型的对象即可,只需要保证多个线程使用的是相同锁对象即可。
			   	    因为,只有同步监听锁对象才能调用wait和notify方法,所以 wait和notify方法应该存在于Object类中,而不是Thread类中。

		3.同步方法(synchronized修饰):
			synchronized public    void  doWork()
			{
     				///TODO
			}

			使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着;
			不要使用synchronized修饰 run方法,如果synchronized修饰了 run方法的话,那么某一个线程就执行完了所有的功能,
			好比是多个线程出现串行,多线程串行即为多个线程排成队列,排队执行,每个线程逐一执行,一个线程执行结束了,
			才轮到下一个线程开始执行;
			解决方案:把需要同步操作的代码定义在一个新的方法中,并且该方法使用synchronized修饰,再在run方法中调用该新的方法即可;

 Java 多进程、多线程、多线程中的共享变量的安全问题、同步锁、线程同步、ThreadLocal_第8张图片

		4.锁机制(Lock):
			锁机制中的 同步锁(Lock):
				Lock锁机制提供了比 synchronized代码块 和 synchronized方法 更广泛的锁定操作,
				同步代码块/同步方法 具有的功能 Lock锁机制都有,除此之外更强大,更体现面向对象。

Java 多进程、多线程、多线程中的共享变量的安全问题、同步锁、线程同步、ThreadLocal_第9张图片

7.synchronized、单例模式(饿汉模式、懒汉模式)、双重检查加锁:
	1.synchronized:
		好处:保证了多线程并发访问时的同步操作,避免线程的安全性问题 
		缺点:使用synchronized的方法/代码块的性能比不用要低一些 
		建议:尽量减小synchronized的作用域 (锁的粒度要尽量小)

 	2.集合中的 synchronized:
		1.HashMap 和 TreeMap 以及 LinkedHashMap 都是线程不安全的,但是性能较高。
		    Hashtable类实现线程安全的,但是性能较低。 

		2.Vector类底层其实就是一个Object数组,Vector类中的方法是支持同步(方法使用synchronized修饰)的。
		    Vector中的所有的方法都使用了synchronized修饰符,线程安全但是性能较低,适用于多线程环境。
		    Vector类:底层才有数组结构算法,方法都使用了synchronized修饰,线程安全,但是性能相对于ArrayList较低。

		3.ArrayList中的 所有的方法都没有使用synchronized修饰符,线程不安全但是性能较高。
		    ArrayList类:底层才有数组结构算法,方法没有使用synchronized修饰,线程不安全,性能相对于Vector较高。
		    ArrayList现在机会已经取代了Vector的江湖地位。
		   即使以后在多线程环境下,为了保证ArrayList的线程安全,我们也不使用Vector类而是使用ArrayList的安全线程类:
			ArrayList list = Collections.synchronizedList(new ArrayList(...)); 

		4.LinkedList类:底层才有双向链表结构算法,方法没有使用synchronized修饰,线程不安全。
		    LinkedList类是线程不安全的类,在多线程环境下所有保证线程安全,在多线程环境下,使用LinkedList的安全线程类:
		    LinkedList list = Collections.synchronizedList(new LinkedList(...));

	3.StringBuffer类中的方法 带有synchronized。
	   StringBuilder类中的方法 不带有synchronized。

Java 多进程、多线程、多线程中的共享变量的安全问题、同步锁、线程同步、ThreadLocal_第10张图片

	4.单例模式(饿汉模式)

Java 多进程、多线程、多线程中的共享变量的安全问题、同步锁、线程同步、ThreadLocal_第11张图片

	5.单例模式(懒汉模式)

Java 多进程、多线程、多线程中的共享变量的安全问题、同步锁、线程同步、ThreadLocal_第12张图片

	6.单例模式(懒汉模式)+ 双重检查加锁:

Java 多进程、多线程、多线程中的共享变量的安全问题、同步锁、线程同步、ThreadLocal_第13张图片

		1.单纯的 单例模式(懒汉模式) 使用了同步方法,已经解决了懒汉式的线程安全问题,但是synchronized的作用域太大了,损耗性能;
		    所以尽量减小synchronized的作用域,解决方案:使用 单例模式(懒汉模式)+ 双重检查锁机制。

		2.双重检查加锁:
  			1.可以使用“双重检查加锁”的方式来实现,就可以既实现线程安全,又能够使性能不受很大的影响。
  			2.所谓“双重检查加锁”机制:
				1.第一重检查:并不是每次进入getInstance方法都需要同步,而是先不同步,进入方法后,先检查实例是否存在,
				   如果不存在才进行下面的同步块;
				2.第二重检查:进入同步块过后,再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例。
				3.结论:这样一来,就只需要同步一次了,从而减少了多次在同步情况下进行判断所浪费的时间。
  			3.“双重检查加锁”机制的实现会使用关键字volatile,它的意思是 被volatile修饰的变量的值,将不会被本地线程缓存,
			      所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。
			4.注意:
				在java1.4及以前版本中,很多JVM对于volatile关键字的实现的问题,会导致“双重检查加锁”的失败,
			   	因此“双重检查加锁”机制只只能用在java5及以上的版本。
				由于volatile关键字可能会屏蔽掉虚拟机中一些必要的代码优化,所以运行效率并不是很高。
				因此一般建议,没有特别的需要,不要使用。也就是说,虽然可以使用“双重检查加锁”机制来实现线程安全的单例,
				但并不建议大量采用,可以根据情况来选用。



8.生产者和消费者:
	1.线程通信:不同的线程执行不同的任务,如果这些任务有某种关系,线程之间必须能够通信,协调完成工作。
     	2.经典的生产者和消费者案例(Producer/Consumer):
		分析案例:
              			1.生产者和消费者应该操作共享的资源(实现方式来做) 
              			2.使用一个或多个线程来表示生产者(Producer) 
              			3.使用一个或多个线程来表示消费者(Consumer) 
	3.生产者消费者的示意图:

Java 多进程、多线程、多线程中的共享变量的安全问题、同步锁、线程同步、ThreadLocal_第14张图片

	4.为什么生产者不直接把数据给消费者,而是先把数据存储到共享资源中,然后 消费者再从共享资源中取出数据,再消费。
	    在这里体现了面向对象的设计理念:低耦合。
       		高(紧)耦合:直接使用生产者把肉包子给消费者,那么生产者中得存在消费者的引用,同理,消费者要消费生产者生产的肉包子,
			         消费者中也得存在生产者对象的引用。 
       		低(松)耦合:使用一个中间对象。屏蔽了生产者和消费者直接的数据交互。

Java 多进程、多线程、多线程中的共享变量的安全问题、同步锁、线程同步、ThreadLocal_第15张图片

	5.实现 生产者和消费者 的案例:
		1.简单例子 演示 生产者和消费者 的过程:(该例子无法保证同步,会出同步问题)

Java 多进程、多线程、多线程中的共享变量的安全问题、同步锁、线程同步、ThreadLocal_第16张图片

			分析生产者和消费者案例存在的问题:
  				建议在生产姓名和性别之间以及在打印之前使用Thread.sleep(10) 使效果更明显。
  				此时出现下面的情况:

				出现上图现象的原因:生产者先生产出春哥哥-男,此时消费者没有消费,生产者继续生产出姓名为凤姐,
						             此时消费者开始消费了,重复出现凤姐-男和凤姐-女。

				 问题1:出现姓别紊乱的情况。
                 				解决方案:只要保证在生产姓名和性别的过程保持同步,中间不能被消费者线程进来取走数据。
                 					       可以使用同步代码块/同步方法/Lock机制来保持同步性。
    				问题2:应该出现生产一个数据,消费一个数据。
					解决方案:得使用 等待和唤醒机制。

		2.同步监听对象/同步锁/同步监听器/互斥锁:
				1.为了保证每个线程都能正常执行原子操作,Java引入了线程同步机制;
				    对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁;
				    Java程序运行使用任何对象作为同步监听对象,但是一般的,我们把当前并发访问的共同资源作为同步监听对象;
				    注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着;
				2.同步锁必须选择多个线程共同的资源对象;
			   	    当前生产者在生产数据的时候(先拥有同步锁),其他线程就在锁池中等待获取锁;
      			   	    当线程执行完同步代码块的时候,就会释放同步锁,其他线程开始抢锁的使用权;    
				3.多个线程只有使用相同的一个对象的时候,多线程之间才有互斥效果。
			   	    我们把这个用来做互斥的对象称之为,同步监听对象/同步锁;
 			   	    同步锁对象可以选择任意类型的对象即可,只需要保证多个线程使用的是相同锁对象即可。
			   	    因为,只有同步监听锁对象才能调用wait和notify方法,所以 wait和notify方法应该存在于Object类中,而不是Thread类中。

		3.生产者和消费者的 第一种实现方式:(线程通信:使用 Object类中的 wait和notify方法)
			1.多个线程中 使用 同一个 Object类的自定义子类的实例对象 作为 同步监听对象/同步锁/同步监听器/互斥锁;
			    Object类存在wait和notify方法,而不是Thread类中。
			    同步锁对象可以选择任意类型的对象即可,只需要保证多个线程使用的是相同锁对象即可。
			2.注意:此处的多个线程中 使用的都为同一个  Object类的自定义子类的实例对象 作为 同步锁对象,
				 永远使用该 同步锁对象(同一个 Object类的自定义子类的实例对象) 调用 wait和notify方法。

			3.java.lang.Object类提供类两类用于操作线程通信的方法:
				wait():执行该方法的线程对象释放同步锁,JVM把该线程存放到等待池中,等待其他的线程唤醒该线程。
				notify():执行该方法的线程唤醒在等待池中等待的任意一个线程,把线程转到锁池中等待。
				notifyAll():执行该方法的线程唤醒在等待池中等待的所有的线程,把线程转到锁池中等待。
				注意:上述方法只能被同步监听锁对象(Object类的自定义子类的实例对象)来调用,否则报错IllegalMonitorStateException。
			
			4.假设A线程和B线程共同操作一个同步锁对象(Object类的自定义子类的实例对象),
			     A、B线程可以通过同步锁对象(Object类的自定义子类的实例对象)的wait和notify方法来进行通信,流程如下:
				1.当A线程执行 同步锁对象中的 同步方法时,A线程持有同步锁对象的锁,B线程没有执行机会,
				    B线程在同步锁对象的锁池中等待;
				2.A线程在同步方法中执行 同步锁对象.wait()方法时,A线程释放同步锁对象的锁,A线程进入同步锁对象的等待池中;
				3.在同步锁对象的锁池中等待锁的B线程获取同步锁对象的锁,执行同步锁对象中的另一个同步方法;
				4.B线程在同步方法中执行同步锁对象.notify()方法时,JVM把A线程从同步锁对象的等待池中移动到同步锁对象的锁池中,
				   等待获取锁;
				5.B线程执行完同步方法,释放锁,A线程获得锁,继续执行同步方法。

Java 多进程、多线程、多线程中的共享变量的安全问题、同步锁、线程同步、ThreadLocal_第17张图片

Java 多进程、多线程、多线程中的共享变量的安全问题、同步锁、线程同步、ThreadLocal_第18张图片Java 多进程、多线程、多线程中的共享变量的安全问题、同步锁、线程同步、ThreadLocal_第19张图片Java 多进程、多线程、多线程中的共享变量的安全问题、同步锁、线程同步、ThreadLocal_第20张图片

Java 多进程、多线程、多线程中的共享变量的安全问题、同步锁、线程同步、ThreadLocal_第21张图片

Java 多进程、多线程、多线程中的共享变量的安全问题、同步锁、线程同步、ThreadLocal_第22张图片

		3.生产者和消费者的 第二种实现方式:(线程通信:使用  Lock 和  Condition 接口)
 			1.第一种实现方式:
				wait和notify方法 只能被同步锁对象(Object类的自定义子类的实例对象)来调用,否则报错IllegalMonitorStateException。
			2.第二种实现方式:
				从Java5开始,可以使用  Lock 和  Condition 接口;
					1.使用 Lock机制 取代 synchronized 代码块 和 synchronized 方法;
						Lock机制根本就没有同步锁了,也就没有自动获取锁和自动释放锁的概念;
						因为没有同步锁,所以Lock机制不能调用wait和notify方法;
						解决方案:Java5中提供了Lock机制的同时,提供了处理Lock机制的通信控制的Condition接口。
					2.使用 Condition接口 对象中的  await/signal/signalAll 方法 取代Object类中的  wait/notify/notifyAll 方法;

Java 多进程、多线程、多线程中的共享变量的安全问题、同步锁、线程同步、ThreadLocal_第23张图片

Java 多进程、多线程、多线程中的共享变量的安全问题、同步锁、线程同步、ThreadLocal_第24张图片

Java 多进程、多线程、多线程中的共享变量的安全问题、同步锁、线程同步、ThreadLocal_第25张图片

9.死锁:
	1.多线程通信的时候很容易造成死锁,死锁无法解决,只能避免;
	    当A线程等待由B线程持有的锁,而B线程正在等待A线程持有的锁时,发生死锁现象,JVM不检测也不试图避免这种情况,
	    所以程序员必须保证不导致死锁;
	2.避免死锁法则:当多个线程都要访问共享的资源A、B、C 时,保证每一个线程都按照相同的顺序去访问他们,比如都先访问A、接着B、最后C;
	3.Thread类中过时的方法:
		suspend()  使正在运行的线程放弃CPU,暂停运行 
		resume()   暂停的线程恢复运行 
		注意:因为容易导致死锁,所以已经被废弃了。
		              死锁情况:A线程获得对象锁,正在执行一个同步方法,如果B线程调用A线程的suspend方法,此时A线程暂停运行,
				   此时A线程放弃CPU,但是不会放弃占用的锁。

Java 多进程、多线程、多线程中的共享变量的安全问题、同步锁、线程同步、ThreadLocal_第26张图片

10.线程的生命周期:
	1.生命周期:一个事物从出生的那一刻开始到最终死亡中间的整个过程;
		          在事物的漫长的生命周期过程中,总会经历不同的状态(婴儿状态/青少年状态/中年状态/老年状态...);
		          线程也是有生命周期的,也是存在不同的状态的,状态相互之间的转换;

	2.有人又把阻塞状态、等待状态、计时等待状态合称为阻塞状态;
 	   线程对象的状态存放在Thread类的内部类(State)中:
	   注意:Thread.State类其实是一个枚举类,因为线程对象的状态是固定的,只有6种,此时使用枚举来表示是最恰当的。

Java 多进程、多线程、多线程中的共享变量的安全问题、同步锁、线程同步、ThreadLocal_第27张图片

	3.新建状态(new):
		使用new创建一个线程对象,仅仅在堆中分配内存空间,在调用start方法之前;
   		新建状态下,线程压根就没有启动,仅仅只是存在一个线程对象而已;
   		Thread t = new Thread();//此时t就属于新建状态
		当新建状态下的线程对象调用了start方法,此时从新建状态进入可运行状态;
    		线程对象的start方法只能调用一次,否则报错 IllegalThreadStateException;

	4.可运行状态(runnable):
		分成两种状态,ready和running。分别表示就绪状态和运行状态。
			就绪状态:线程对象调用start方法之后,等待JVM的调度(此时该线程并没有运行)。
			运行状态:线程对象获得JVM调度,如果存在多个CPU,那么允许多个线程并行运行。

Java 多进程、多线程、多线程中的共享变量的安全问题、同步锁、线程同步、ThreadLocal_第28张图片

	5.阻塞状态(blocked):
		1.正在运行的线程因为某些原因放弃CPU,暂时停止运行,就会进入阻塞状态;
         		    此时JVM不会给线程分配CPU,直到线程重新进入就绪状态,才有机会转到运行状态;
         		    阻塞状态只能先进入就绪状态,不能直接进入运行状态;
  		2.阻塞状态的两种情况:
           			1.当A线程处于运行过程时,试图获取同步锁时,却被B线程获取,此时JVM把当前A线程存到对象的锁池中,A线程进入阻塞状态;
            			2.当线程处于运行过程时,发出了IO请求时,此时进入阻塞状态;	
		
	6.等待状态(waiting):
		等待状态只能被其他线程唤醒,此时使用的无参数的wait方法;
       		当线程处于运行过程时,调用了wait()方法,此时JVM把当前线程存在对象等待池中;

	7.计时等待状态(timed waiting):
		使用了带参数的wait方法或者sleep方法:
       			1.当线程处于运行过程时,调用了wait(long time)方法,此时JVM把当前线程存在对象等待池中;
       			2.当前线程执行了sleep(long time)方法 

	8.终止状态(terminated):
		1.通常称为死亡状态,表示线程终止:
			1.正常执行完run方法而退出(正常死亡)。
        			2.遇到异常而退出(出现异常之后,程序就会中断)(意外死亡)。

		2.线程一旦终止,就不能再重启启动,否则报错(IllegalThreadStateException)。
		3.在Thread类中过时的方法(因为存在线程安全问题,所以弃用了):
 			void suspend()  暂停当前线程
			void resume()  恢复当前线程
 			void stop()  结束当前线程


11.线程控制操作:
	1.线程休眠:
		让执行的线程暂停一段时间,进入计时等待状态。
		方法:static void sleep(long millis) 
			调用sleep后,当前线程放弃CPU,在指定时间段之内,sleep所在线程不会获得执行的机会。
			此状态下的线程不会释放同步锁/同步监听器;
			该方法更多的用于模拟网络延迟,让多线程并发访问同一个资源的错误效果更明显;
			在开发中也会故意使用该方法,如:

Java 多进程、多线程、多线程中的共享变量的安全问题、同步锁、线程同步、ThreadLocal_第29张图片

	2.联合线程:
		线程的join方法表示一个线程等待另一个线程完成后才执行。join方法被调用之后,线程对象处于阻塞状态。
		有人也把这种方式称为联合线程,就是说把当前线程和当前线程所在的线程联合成一个线程。

Java 多进程、多线程、多线程中的共享变量的安全问题、同步锁、线程同步、ThreadLocal_第30张图片

	3.后台线程/守护线程:
		1.在后台运行的线程,其目的是为其他线程提供服务,也称为“守护线程"。JVM的垃圾回收线程就是典型的后台线程。
		2.特点:若所有的前台线程都死亡,后台线程自动死亡,前台线程没有结束,后台线程是不会结束的。
		3.测试线程对象是否为后台线程:使用thread.isDaemon()。
		   前台线程创建的线程默认是前台线程,可以通过setDaenon(true)方法 设置为后台线程,
		   并且当且仅当后台线程创建的新线程时,新线程是后台线程。
		   设置后台线程:thread.setDaemon(true) 该方法必须在start方法调用前,否则出现IllegalThreadStateException异常。

Java 多进程、多线程、多线程中的共享变量的安全问题、同步锁、线程同步、ThreadLocal_第31张图片

 

	4.线程优先级:
		1.每个线程都有优先级,优先级的高低只和线程获得执行机会的次数多少有关,并非线程优先级越高的就一定先执行,
		    哪个线程的先运行取决于CPU的调度。
		    每个线程都有默认优先级,主线程默认优先级为5,如果A线程创建了B线程,那么B线程和A线程具有相同优先级;
		    注意:不同的操作系统支持的线程优先级不同的,建议使用上述三个优先级,不要自定义;
		2.线程优先级:
			MAX_PRIORITY=10  最高优先级
			MIN_PRIORITY=1  最低优先级
			NORM_PRIORITY=5  默认优先级
 		3.方法:
 			int getPriority()  返回线程的优先级。 
			 void setPriority(int newPriority)   更改线程的优先级。 

Java 多进程、多线程、多线程中的共享变量的安全问题、同步锁、线程同步、ThreadLocal_第32张图片

	5.线程礼让:
		1.yield方法:
			表示当前线程对象提示调度器自己愿意让出CPU资源,但是调度器可以自由的忽略该提示。
			调用该yield方法之后,线程对象进入就绪状态,所以完全有可能是 某个线程调用了yield()之后,
			线程调度器又把它调度出来重新执行。
			从Java7提供的文档上可以清楚的看出,开发中很少会使用到该方法,该方法主要用于调试或测试,
			它可能有助于因多线程竞争条件下的错误重现现象。

 		2.sleep方法和yield方法的区别:
			1.都能使当前处于运行状态的线程放弃CPU,把运行的机会给其他线程;
			2.sleep方法会给其他线程运行机会,但是不考虑其他线程的优先级,yield方法只会给相同优先级或者更高优先级的线程运行的机会;
			3.调用sleep方法后,线程进入计时等待状态,调用yield方法后,线程进入就绪状态;


12.定时器和线程组:
	1.定时器:
		在JDK的java.util包中提供了Timer类,可以定时执行特定的任务。
		TimerTask类表示定时器执行的某一项任务.
		常用方法:
			schedule(TimerTask task,long delay,long period)
			schedule(TimerTask task,long delay)

	2.线程组:
		ThreadGroup类表示线程组,可以对一组线程进行集中管理;
		用户在创建线程对象时,可以通过构造器指定其所属的线程组:Thread(ThreadGroup group,String name);
		如果A线程创建了B线程,如果没有设置B线程的分组,那么B线程加入到A线程的线程组;
		一旦线程加入某个线程组,该线程就一直存在于该线程组中直到线程死亡,不能在中途修改线程的分组;
		当Java程序运行时,JVM会创建名为main的线程组,在默认情况下,所有的线程都该改线程组下;
  

 

 13.ThreadLocal:
	ThreadLocal data = new ThreadLocal();
	ThreadLocal对象.remove():移除ThreadLocal中存储的数据
	ThreadLocal对象.get():从ThreadLocal中获取所存储的数据的拷贝副本
	ThreadLocal对象.set(数据):把数据存储到ThreadLocal中

	1.ThreadLocal的作用:实现多线程安全地使用同一份对象数据,每个线程使用的都是ThreadLocal中存储的的对象数据的拷贝副本。
	    1.ThreadLocal存储数据的限制:
                  只能存储一个数据,因此需要往ThreadLocal中存储多个数据时,应先把多个数据封装到一个容器对象中,
                  再把容器对象存到ThreadLocal中。
            2.功能一:ThreadLocal把存进来的对象/数据进行拷贝副本。
                      多线程下每个线程从 ThreadLocal取出的对象/数据 都是 “ThreadLocal中存储的对象/数据的”拷贝副本。
                      所以当多线程中每个线程都要操作同一个对象/数据时,会出现数据同步安全问题,因此可以通过把对象数据存到ThreadLocal中,
                      然后每个线程再从ThreadLocal取出该对象/数据,每个线程从 ThreadLocal取出的对象/数据实际都是
                      ”存进ThreadLocal中的对象/数据的”拷贝副本。
            3.功能二:Spring框架中操作单例对象是安全的。
                       Spring框架中操作单例对象,其底层会使用ThreadLocal把存进来的单例对象进行拷贝副本,变成多例对象。
                      那么Spring框架中操作单例对象相当于操作多例对象。

	2.多线程操作单例对象在Spring框架下是安全的,而不在Spring框架下使用多线程操作单例对象时会出现数据安全同步问题:
		1.不在spring框架下 单纯使用 多线程 操作单例对象的话,会出现数据安全同步问题,因此单例对象和static静态变量(类变量)一样,
	  	  都只有一份,都必须使用同步操作/synchronized,或把静态变量存到ThreadLocal后再从ThreadLocal获取出来使用,
	  	  这样才能保证多个线程可以安全地操作静态变量的数据。
		2.在spring框架下 使用多线程时,多个线程在操作 单例对象 仍然是是安全的,因为 spring框架会把单例对象封装进ThreadLocal中,
	   	   那么每个线程都从ThreadLocal中取单例对象的话,实际取的是单例对象的副本,那么每个线程中使用的都是单例对象的副本。
		3.spring框架下把QueryRunner runner对象设置为多例是安全的,即使是把QueryRunner runner对象设置为单例也是安全的,
 	   	   因为 spring框架会把单例的QueryRunner runner对象封装进ThreadLocal中,那么每个线程都从ThreadLocal中取单例的QueryRunner runner对象的时候,
	   	   实际取的是单例的QueryRunner runner对象的副本。
	   	   例子:   //scope="prototype"把QueryRunner对象设置为多例的
			             
				   
    			   

Java 多进程、多线程、多线程中的共享变量的安全问题、同步锁、线程同步、ThreadLocal_第33张图片

14.在多线程环境下的静态方法中使用static静态变量(类变量)的话,会有线程安全问题:
	1.静态方法、静态代码块中 无法访问 非静态的 成员变量(实例变量) 和 非静态的 成员方法(实例方法),只能访问调用 静态变量、静态方法。
	   静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制。
	2.static静态变量(类变量) 在整个程序中 始终只有一份,而在多线程环境下的静态方法中使用static静态变量(类变量)的话,
	  必须使用同步操作/synchronized,或把静态变量存到ThreadLocal后再从ThreadLocal获取出来使用,
	  这样才能保证多个线程可以安全地操作静态变量的数据。

15.静态方法、静态成员、实例成员:
	1.类的成员分为两类,静态成员(static member)和实例成员(instance member)。静态成员属于类;实例成员则属于对象,即类的实例。
	2.静态字段(static field)和静态方法(static method)的调用是通过类来调用。静态方法中不对实例成员进行操作,静态方法中只能访问静态成员。
	  实例方法才可对实例成员进行操作,并且既能访问静态成员,也能访问实例成员。
	3.实例成员和静态成员在多线程中的区别:每个线程使用各自的实例字段(instance field)的副本,而共享一个静态字段(static field)。
	4.在多线程环境下的静态方法中使用static静态变量(类变量)的话,会有线程安全问题;静态方法如果没有使用静态变量,则没有线程安全问题。
	  static静态变量(类变量) 在整个程序中 始终只有一份,而在多线程环境下的静态方法中使用static静态变量(类变量)的话,
	  必须使用同步操作/synchronized,或把静态变量存到ThreadLocal后再从ThreadLocal获取出来使用,
	  这样才能保证多个线程可以安全地操作静态变量的数据。
	5.因为实例方法内声明的实例变量,每个线程调用时,都会新创建一份,而不会共用一个存储单元。
	  静态变量,由于是在类加载时占用一个存储区,每个线程都是共用这个存储区的,所以如果在静态方法里使用了静态变量,这就会有线程安全问题!


 

 

 

 

 

 

 

 

你可能感兴趣的:(java)