第4章Java多线程+网银取款案例精讲

4-2进程概述及特性

1、进程:
一个正在运行中的程序就是一个进程;
进程的特性:独立性、动态性、并发性:

  • 1.独立性
    每个进程都拥有自己独立的内存空间和其他相关资源,一般来说进程之间是很难通信的,即使可以通信也是比较麻烦
    需要通过一些进程通信的手段才可以实现进程通信。
  • 2.动态性
    程序是静止的,运行中的程序才是动态的,进程就是运行中的程序
  • 3.并发性
    CPU是分时执行的。CPU时间片与内存空间按一定的时间间隔,轮流地切换给各进程使用,
    因为CPU执行的速度太快了,用户根本看不出来,我们会看到一个假象,我们会认为同一时刻
    有多个进行在同时执行,前期是单核CPU;
    并发:同一时刻CPU实际上只在处理一个进程,当CPU处理时间片到了,CPU就会被另一个进程占用
    因为CPU这种轮流执行进程的速度很快,所以我们相当于看到程序在同时运行。 这就是并发。

如果想要同一时刻运行多个进程(称之为并行),操作系统就必须有对应的多个CPU。

4-3线程概述以及进程的关系

2、线程:
一个进程中可以包含多个线程,线程不能独立存在,必须依赖与进程,线程之间通信比较简单。因为他们可以共享同一块内存区域
特点: 基本不占用内存空间和资源,动态性,并发性。

★多线程的好处:
a.多线程相对于进程占用内存和资源非常少,节约内存。
b.也可以并发执行。
c.线程之间很容易进行通信,所以效率高。
d.java对多线程的支持十分的完美。

java线程池对同一个list操作 demo_System

4-4线程的创建以及执行

创建线程的三种方式
-1、第一种方式继承Thread类,重写Thread类中的run方法,还需要调用start方法,start方法相当于通知CPU,线程已经就绪,CPU在合适的时间点调用该线程的run方法;我们程序中的main方法,我们称之为主线程
-2、创建线程的第二种方式,实现Runnable接口,并重写run方法,
创建实例之后,将该实例包装成Thread实例,继续调用start方法让线程就绪,因为Runnable是一个函数式接口,因此可以通过Lambda表达式,进行Runnable实例的创建;
-3、创建线程的第三种方式,实现Callable,重写该接口的call方法,call方法不同于run方法,run方法没有返回值,而call方法有返回值;
第一步,创建Callable实例,重写call方法
第二步,将Callable实例传入FutureTask构造器中,得到FutureTask实例
第三步,创建Thread实例,将FutureTask实例传入Thread构造器中,再让线程就绪

总结:通过继承Thread创建线程与实现Runnable接口与实现Callable接口创建线程的区别;

1、相对而言 继承Thread 创建线程代码是最简单的;
2、如果我们的类继承了Thread那就不能再去继承其他类,因为java是单继承,
如果我们通过实现接口Runnable或者Callable来创建线程,我们的类还有权利去继承其他类;
3、实现Runnable或者Callable接口,可以让多个线程共享同一份资源
4、当我们需要线程执行完毕之后有返回值|信息返回,那么需要 实现 Callable接口

线程创建的第一种方式

/**
 *创建线程的方式有三种
 *1、继承 Thread,并重写Thread类中的run方法,通过调用start方法,让线程就绪
 *
 */
public class MyThread_01 extends Thread{

	/* 
	 * 重写线程父类Thread的run方法
	 */

	@lombok.SneakyThrows
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for (int i = 0; i < 50; i++) {
			//System.out.println("线程名字为:"+Thread.currentThread().getName()+"=====i===="+i);	
			System.out.println("线程名字为:"+this.getName()+"=====i===="+i);
			Thread.sleep(20);
		}

	}

	/**
	 * main方法是程序的入口方法
	 * main方法被称为主线程
	 */
	public static void main(String[] args) throws InterruptedException {
		// TODO Auto-generated method stub
		
		    //手动创建线程实例
			MyThread_01 thread01 = new MyThread_01();
			//设置线程的名字
			thread01.setName("线程一");
			//start方法让线程就绪    相当于告诉CPU,线程已经就绪,这个时候CPU会在合适的时间点调用该线程的run方法
			thread01.start();

		   for (int j = 0; j < 50; j++) {
			  System.out.println("线程名字为:"+Thread.currentThread().getName()+"=====j===="+j);
			   Thread.sleep(20);
		   }

		   System.out.println("------------程序执行完毕---------");

	}

}

线程创建的第二种方式

创建线程的第二种方式,实现Runnable接口,并重写run方法,
创建实例之后,将该实例包装成Thread实例,继续调用start方法让线程就绪,因为Runnable是一个函数式接口,因此可以通过Lambda表达式,进行Runnable实例的创建;

Runnable接口中没有start方法,所以需要装成Thread实例,来调用Thread start 方法来使线程进入就绪状态

/**
 *创建线程的方式有三种
 *2、实现Runnable接口,并重写run方法
 *
 */
public class MyThread_02  implements Runnable{

	/* 
	 * 重写Runnable接口中的run方法
	 */
	@SneakyThrows
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for (int i = 0; i < 50; i++) {
			//Thread.currentThread():获取当前线程         getName:获取当前线程的名字
			System.out.println("线程名字为:"+Thread.currentThread().getName()+"=====i===="+i);
			Thread.sleep(10);
		}
		
	}


	public static void main(String[] args) throws InterruptedException {
//=============1====================//
		//1.创建MyThread_02实例
		MyThread_02 mythread = new MyThread_02();

		//创建线程实例,对Runnable进行包装
		Thread thread = new Thread(mythread);
		//设置线程的名字
		thread.setName("打开小明聊天窗口");
		//让线程就绪
		thread.start();
		//==========2=========================//
//==================1=======================/
		//2. 使用匿名内部类的方式实例一个Runnable接口类
		Runnable runnable = new Runnable() {
			/*
			 * 重写Runnable的run方法
			 */
			@SneakyThrows
			@Override
			public void run() {
				// TODO Auto-generated method stub
				for (int k = 0; k < 50; k++) {
					//Thread.currentThread():获取当前线程         getName:获取当前线程的名字
					System.out.println("线程名字为:"+Thread.currentThread().getName()+"=====k===="+k);
					Thread.sleep(10);
				}
			}
		};

		//将Runnable实例包装成 Thread的实例
		Thread thread02 = new Thread(runnable);
		thread02.setName("打开阿毛聊天窗口");
		//让线程就绪
		thread02.start();
//======================2========================//
		//=================1=========================//
		//3. 因为Runnable是一个函数式接口,用Lambda表达式进行简化
		Runnable runnable02 = () ->{
			for (int k = 0; k < 50; k++) {
				//Thread.currentThread():获取当前线程         getName:获取当前线程的名字
				System.out.println("线程名字为:"+Thread.currentThread().getName()+"=====k===="+k);
				try {
					Thread.sleep(10);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}

		};

		//将Runnable实例包装成 Thread的实例
		Thread thread03 = new Thread(runnable02);
		thread03.setName("打开无忌聊天窗口");
		//让线程就绪
		thread03.start();
//=====================2================//


		for (int i = 0; i < 50; i++) {
			//Thread.currentThread():获取当前线程         getName:获取当前线程的名字
			System.out.println("线程名字为:"+Thread.currentThread().getName()+"=====i===="+i);
			Thread.sleep(10);
		}

	}

}

线程创建的第三种方式

创建线程的第三种方式,实现Callable,重写该接口的call方法,call方法不同于run方法,run方法没有返回值,而call方法有返回值;

第一步,创建Callable实例,重写call方法

第二步,将Callable实例传入FutureTask构造器中,得到FutureTask实例

第三步,创建Thread实例,将FutureTask实例传入Thread构造器中,再让线程就绪

java线程池对同一个list操作 demo_优先级_02

/**
 *创建线程的方式有三种
 *1、实现Callable接口,并重写call方法
 *
 *
 */
public class MyThread_03  implements Callable<Integer>{

	/*
	 * 重写Callable接口中的call方法
	 */
	@Override
	public Integer call() throws Exception {
		// TODO Auto-generated method stub
		int count = 0;
		for (int k = 0; k < 50; k++) {
			//Thread.currentThread():获取当前线程         getName:获取当前线程的名字
			System.out.println("线程名字为:"+Thread.currentThread().getName()+"=====k===="+k);
			count++;
			Thread.sleep(20);
		}
		return count;
	}

	
     public static void main(String[] args) throws InterruptedException {
    	 
    	 //1、创建 MyThread_03实例
    	 MyThread_03 callable = new MyThread_03();
    	 
    	 //2、创建FutureTask实例    FutureTask就是Runnable实现类
    	 FutureTask<Integer> future = new FutureTask<>(callable);

    	 //3.创建Thread实例,对Runnable进行包装
    	 Thread thread = new Thread(future);
    	 
    	 thread.setName("线程一");
    	 thread.start();


    	 for (int i = 0; i < 50; i++) {
	 			//Thread.currentThread():获取当前线程         getName:获取当前线程的名字
	 			System.out.println("线程名字为:"+Thread.currentThread().getName()+"=====i===="+i);
	 			Thread.sleep(50);
	 	}
		 try {
			 //用FutureTask对象在,当线程执行完毕之后获取线程执行完毕后的返回值
			 int count = future.get();
			 System.out.println("count:"+count);
		 } catch (Exception e) {
			 // TODO: handle exception
			 e.printStackTrace();
		 }
	 }
	 
}

4-7线程的生命周期

java线程池对同一个list操作 demo_优先级_03

4-8线程的常用方法

1、currentThread : 静态方法,获取当前线程
2、getName:获取线程的名字,实例方法
3、getId:获取线程id,线程id是唯一
4、setPriority : 设置线程优先级,如果不设置,默认为5 ,
总共有 三个 1 5 10 值越大说明,被CPU调用的机会越大;
6、setName:设置线程名字,注意没有setId方法,因为线程的id是自动分配的,并且是唯一,不需要人为设置
7、getPriority :获取线程优先级
8、start方法让线程就绪
9、stop方法用于结束当前线程
10、run:线程的主方法,线程在执行的时候,就是在执行run方法
11、sleep方法:让线程处于睡眠状态

/**
 * 线程中常用方法讲解
 */
public class ThreadTest extends Thread{

	/* (non-Javadoc)
	 * @see java.lang.Thread#run()
	 */
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for (int i = 0; i < 50; i++) {
			//获取当前线程实例
			Thread thread = Thread.currentThread();
			//获取线程的名字,可以重复
			String name = thread.getName();
			//获取线程id,线程id是不会重复的,好比人的省份证一样不会重写
			long id = thread.getId();

			//获取线程的优先级     如果线程的优先级越大,说明获取CPU的执行机会越大
			int priority = thread.getPriority();
			System.out.println("name:"+name+"   id:"+id +" 优先级:"+priority);
		}
	}


	/**
	 * @param args
	 */
	public static void main(String[] args) {

		  ThreadTest thread = new ThreadTest();
		  thread.setName("线程一");
		  //设置线程的优先级
		  thread.setPriority(Thread.MIN_PRIORITY);
		  //设置为后台线程     最后死亡,最后死亡不代表最后执行完毕
		  thread.setDaemon(true);
		  //让线程就绪
		  thread.start();
		  
		  for (int i = 0; i < 50; i++) {
			 //获取当前线程实例
			Thread thread2 = Thread.currentThread();
			//获取线程的名字,可以重复
			String name = thread2.getName();
			//获取线程id,线程id是不会重复的,好比人的省份证一样不会重写
			long id = thread2.getId();

			//获取线程的优先级     如果线程的优先级越大,说明获取CPU的执行机会越大
			int priority = thread2.getPriority();
			System.out.println("name:"+name+"   id:"+id +" 优先级:"+priority);
		}
  
	}
	
}

总结:

  • 通过继承Thread创建线程与实现Runnable接口与实现Callable接口创建线程的区别;
    1、相对而言 继承Thread 创建线程代码是最简单的;
    2、如果我们的类继承了Thread那就不能再去继承其他类,因为java是单继承,
    如果我们通过实现接口Runnable或者Callable来创建线程,我们的类还有权利去继承其他类;
    3、实现Runnable或者Callable接口,可以让多个线程共享同一份资源
    4、当我们需要线程执行完毕之后有返回值|信息返回,那么需要 实现 Callable接口

4-10 join线程

join:等待线程执行完毕

/**
 * join:等待线程完成
 */
public class ThreadJoin extends Thread{

	//重写Thread中的run方法
	@Override
	public void run() {
		// TODO Auto-generated method stub


		for (int i = 0; i < 50; i++) {
			System.out.println(this.getName()+"----i---"+i);
		}
	}
	
	
	/**
	 * 主线程 main方法
	 */
	public static void main(String[] args) {
		
		  ThreadJoin thread = new ThreadJoin();
		  thread.setName("线程一");
		  //让线程就绪
		  thread.start();
		  
		  try {
			  
			  for (int j = 0; j < 50; j++) {
				  if(j==20) {
					  //当 j 等于 20的时候,让 thread执行,知道thread线程执行完毕之后,才把机会交给其他线程
					 thread.join();
				  }
					System.out.println(Thread.currentThread().getName()+"----j---"+j);
			 }
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
		  
	}
	
}

java线程池对同一个list操作 demo_优先级_04

4-11模拟网上取钱

  • 该类用于封装账号相关信息
public class Account {

	 //卡号
	  private String cardId;	
	 //密码
	  private String password;
	 //余额
	  private double amount;
	  //定义取钱的方法
	  public void draw(double drawMoney) {

			  //判断余额是否大于用户取款金额
			  if(this.getAmount() >= drawMoney) {
				 
				  try {
					  //让线程睡眠
					  Thread.sleep(1000);
				} catch (Exception e) {
					// TODO: handle exception
					e.printStackTrace();
				}
				 
				  //更新余额
				  this.setAmount(this.getAmount() - drawMoney);
				 System.out.println(Thread.currentThread().getName()+"取款成功,卡中还有余额"+this.getAmount());
				  
			  }else {
				  System.out.println(Thread.currentThread().getName()+"取款失败,余额不足!");
			  }
	  }
	}
  • 模拟线程
    通过创建DrawThread 2个实例 模拟小明 和 小明老婆 两个线程
lic class DrawThread extends Thread {

	//定义账户信息
	private Account account;
	//定义取款金额
	private double drawMoney;
	
	
	
	public DrawThread(Account account,double drawMoney) {
		// TODO Auto-generated constructor stub
		this.account = account;
		this.drawMoney = drawMoney;
	}

	
	//重写Thread类的run方法,
	@Override
	public void run() {
		// TODO Auto-generated method stub
		//调用draw方法进行取钱
		account.draw(drawMoney);
	}
    
}
  • 程序入口:使用同一个账号资源
public class MainTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		//创建账户
		Account account = new Account();
		System.out.println("account:"+account);
		//这是余额
		account.setAmount(10000);

		//模拟小明以及小明老婆两个取钱的线程     第一个参数:取款账户信息       第二个参数:取多少钱
		DrawThread thread01 = new DrawThread(account,1000);
		//设置线程名字
		thread01.setName("小明");
		                            //第一个参数:取款账户信息       第二个参数:取多少钱
		DrawThread thread02 = new DrawThread(account,1000);
		//设置线程名字
		thread02.setName("小明老婆");
		
		//让两个线程就绪,准备取钱,cpu会调用thread01的run方法
		thread01.start();
		thread02.start();

	}

}
  • 程序人口 使用不同的资源,及操作不同的账户
public class MainTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		//创建账户
		Account account = new Account();
		System.out.println("account:"+account);
		//这是余额
		account.setAmount(10000);
		

		//模拟小明以及小明老婆两个取钱的线程     第一个参数:取款账户信息       第二个参数:取多少钱
		DrawThread thread01 = new DrawThread(account,10000);
		//设置线程名字
		thread01.setName("小明");
		                            //第一个参数:取款账户信息       第二个参数:取多少钱
		DrawThread thread02 = new DrawThread(account,10000);
		//设置线程名字
		thread02.setName("小明老婆");
		
		//让两个线程就绪,准备取钱,cpu会调用thread01的run方法
		thread01.start();
		thread02.start();

	}

}

线程安全问题:

线程安全主要包含线程间同步和线程间通信

线程安全问题: 当多个线程并发修改某个竞争资源,就会导致线程安全问题。

  • 线程间同步:
  1. synchronized:使用synchronized修饰方法
  2. ThreadLocal:需要同步的代码块使用ThreadLocal同步,提供同步监视器;线程局部变量,ThreadLocal会把竞争资源,针对每个线程复制一个副本, 每个线程要修改竞争资源时,其实是修改的自己有拥有的副本。
  • 线程间通信:
  1. wait以及notify,notifyAll,
  2. Lock对象

4-12 synchronized 通过同步锁保证取款线程安全

java线程池对同一个list操作 demo_优先级_05


通过synchronized实现线程安全:

1、方法使用synchronized修饰

2、需要同步的代码块使用synchronized同步,提供同步监视器防止多个线程同时操作统一资源;

synchronized(同步锁)修饰方法:
定义取钱的方法 用synchronized修饰方法,该方法就是线程安全的,同一时刻只有一个线程可以进入该方法,必须等该线程执行完毕之后,其他线程才能进来

通过synchronized实现线程安全:

java线程池对同一个list操作 demo_优先级_06

public class Account {

	 //卡号
	  private String cardId;
	
	 //密码
	  private String password;
	
	 //余额
	  private double amount;
 
	  //定义取钱的方法   用synchronized修饰方法,该方法就是线程安全的,同一时刻只有一个线程可以进入该方法,必须等该线程执行完毕之后,其他线程才能进来
	  public synchronized void draw(double drawMoney) {

			  //判断余额是否大于用户取款金额
			  if(this.getAmount() >= drawMoney) {
				 
				  try {
					  //让线程睡眠
					  Thread.sleep(1000);
				} catch (Exception e) {
					// TODO: handle exception
					e.printStackTrace();
				}
				 
				  //更新余额
				  this.setAmount(this.getAmount() - drawMoney);
				  System.out.println(Thread.currentThread().getName()+"取款成功,卡中还有余额"+this.getAmount());
				  
			  }else {
				  System.out.println(Thread.currentThread().getName()+"取款失败,余额不足!");
			  }

	  }
	  
	}
  • 程序入口 操作同一个资源
public class MainTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		//创建账户
		Account account = new Account();
		System.out.println("account:"+account);
		//这是余额
		account.setAmount(10000);
		

		//模拟小明以及小明老婆两个取钱的线程     第一个参数:取款账户信息       第二个参数:取多少钱
		DrawThread thread01 = new DrawThread(account,10000);
		//设置线程名字
		thread01.setName("小明");
		                            //第一个参数:取款账户信息       第二个参数:取多少钱
		DrawThread thread02 = new DrawThread(account,10000);
		//设置线程名字
		thread02.setName("小明老婆");

		//让两个线程就绪,准备取钱,cpu会调用thread01的run方法
		thread01.start();
		thread02.start();

	}

}
  • 同步代码块
public class Account {

	 //卡号
	  private String cardId;
	
	 //密码
	  private String password;
	
	 //余额
	  private double amount;
	  
	 
	  
	  //定义取钱的方法   
	  public  void draw(double drawMoney) {

			  //定义同步代码块
			  synchronized(this) {
				  //判断余额是否大于用户取款金额
				  if(this.getAmount() >= drawMoney) {
					 
					  try {
						  //让线程睡眠
						  Thread.sleep(1000);
					} catch (Exception e) {
						// TODO: handle exception
						e.printStackTrace();
					}
					 
					  //更新余额
					  this.setAmount(this.getAmount() - drawMoney);
					  System.out.println(Thread.currentThread().getName()+"取款成功,卡中还有余额"+this.getAmount());

				  }else {
					  System.out.println(Thread.currentThread().getName()+"取款失败,余额不足!");
				  }
			  }

	  }
	 }

线程同步 :ReentrantLock()

// 锁只能是同一把 只能同时一个线程用,一个线程用完 锁打开 交给下个线程来使用
通过ReentrantLock对象对资源进行加锁操作

synchronized与ReentrantLock()的不同
synchronized锁方法
ReentrantLock锁代码块

private final ReentrantLock lk = new ReentrantLock();

    //上锁

    lock.lock();

    //需要被锁住的资源

  	//释放锁
	lock.unlock();

不加锁可能出现的现象:

java线程池对同一个list操作 demo_优先级_07


加锁:

例子:

该类用于封装账号相关信息

public class Account {
	
	 //定义ReentrantLock,对资源进行加锁
	  private final ReentrantLock lock = new ReentrantLock();

	 //卡号
	  private String cardId;
	
	 //密码
	  private String password;
	
	 //余额
	  private double amount;

	  //定义取钱的方法   
	  public  void draw(double drawMoney) {
		  
		  try {
			  //加锁
			  lock.lock();

				  
					  //判断余额是否大于用户取款金额
					  if(this.getAmount() >= drawMoney) {
						 
						  try {
							  //让线程睡眠
							  Thread.sleep(1000);
						} catch (Exception e) {
							// TODO: handle exception
							e.printStackTrace();
						}
						 
						  //更新余额
						  this.setAmount(this.getAmount() - drawMoney);
						  System.out.println(Thread.currentThread().getName()+"取款成功,卡中还有余额"+this.getAmount());
						  
					  }else {
						  System.out.println(Thread.currentThread().getName()+"取款失败,余额不足!");
					  }
			  
			  
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
			
		}finally {
			
			//释放锁
			lock.unlock();
			
		}

	  }
	}

线程:

/ * 模拟线程
 * 通过创建DrawThread 2个实例  模拟小明  和  小明老婆  两个线程
 * 
 */
public class DrawThread extends Thread {

	//定义账户信息
	private Account account;
	//定义取款金额
	private double drawMoney;
	
	public DrawThread(Account account,double drawMoney) {
		// TODO Auto-generated constructor stub
		this.account = account;
		this.drawMoney = drawMoney;
	}

	
	//重写Thread类的run方法,
	@Override
	public void run() {
		// TODO Auto-generated method stub
		//调用draw方法进行取钱
		account.draw(drawMoney);
	}
	    
}

入口类

/* 取款,但是操作不同的账户
 */
public class MainTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		//创建账户
		Account account = new Account();
		System.out.println("account:"+account);
		//这是余额
		account.setAmount(10000);

		//模拟小明以及小明老婆两个取钱的线程     第一个参数:取款账户信息       第二个参数:取多少钱
		DrawThread thread01 = new DrawThread(account,10000);
		//设置线程名字
		thread01.setName("小明");
		                            //第一个参数:取款账户信息       第二个参数:取多少钱
		DrawThread thread02 = new DrawThread(account,10000);
		//设置线程名字
		thread02.setName("小明老婆");

		//让两个线程就绪,准备取钱,cpu会调用thread01的run方法
		thread01.start();
		thread02.start();
	}

}

4-14线程同步:ThreadLocal讲解

java线程池对同一个list操作 demo_创建线程_08


ThreadLocal也与线程安全有关,当有多个线程同时竞争同一个资源的时候,会为每一个线程克隆一个资源的副本,每个线程操作的都是资源的副本,每个线程之间互不烦扰;

java线程池对同一个list操作 demo_System_09

public class ThreadTest extends Thread{
	
	private SequenceNum sequenceNum;
	
	public ThreadTest(SequenceNum sequenceNum) {
		super();
		// TODO Auto-generated constructor stub
		this.sequenceNum = sequenceNum;
	}
	
	@SneakyThrows
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for (int i = 0; i < 3; i++) {
			Thread.sleep(2000);
			System.out.println("当前线程名:"+this.getName()+"   ---i:"+sequenceNum.getNextNum());
			
		}
	}
	
}
  1. 三个线程都在操作同一份资源(i)
public class SequenceNum {

	/*
	 *在没有使用ThreadLocal的时候,三个线程都在操作同一份资源(i) 
	 * */
	
	ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();
	
	//定义静态成员变量
	static Integer i;
	
	//定义实例方法,每次调用该实例方法  i的值都会自增
	public Integer getNextNum() {
		 if(i == null) {
			 i = 0;
		 }
		//i自增
		i++;


		return i;
	}
	
 
	public static void main(String[] args) {
		
		//创建SequenceNum实例
		SequenceNum sequenceNum = new SequenceNum();
		
		
		//模拟多个线程
		ThreadTest threadTest01 = new ThreadTest(sequenceNum);
		threadTest01.setName("线程1");
		
		ThreadTest threadTest02 = new ThreadTest(sequenceNum);
		threadTest02.setName("线程2");
		
		ThreadTest threadTest03 = new ThreadTest(sequenceNum);
		threadTest03.setName("线程3");
		
		
		//让3个线程就绪
		threadTest01.start();
		threadTest02.start();
		threadTest03.start();
	}
}

java线程池对同一个list操作 demo_System_10

java线程池对同一个list操作 demo_System_11

  1. 如果希望让三个线程操作各自的副本,类似克隆 3个i出来,可以通过java提供的类ThreadLocal来实现
    通过ThreadLocal让每个线程操作自己独立的副本,在没有使用ThreadLocal的时候,三个线程都在操作同一份资源(i)
public class SequenceNum {

	/*
	 * 通过ThreadLocal让每个线程操作自己独立的副本,在没有使用ThreadLocal的时候,三个线程都在操作同一份资源(i)
	 * 如果希望让三个线程操作各自的副本,类似克隆 3个i出来,可以通过java提供的类ThreadLocal来实现
	 * */
	
	ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();
	
	//定义静态成员变量
	static Integer i;
	
	//定义实例方法,每次调用该实例方法  i的值都会自增
	public Integer getNextNum() {

		//从 ThreadLocal 中 获取每个线程各自的副本
		 i = threadLocal.get();
		 //因为从没存过,第一次取不到为null
		 if(i == null) {
			 i = 0;
		 }
		//i自增
		i++;
		//将i的值存放在   threadLocal  中
		threadLocal.set(i);

		return i;
	}
	
 
	public static void main(String[] args) {
		
		//创建SequenceNum实例
		SequenceNum sequenceNum = new SequenceNum();
		
		//模拟多个线程
		ThreadTest threadTest01 = new ThreadTest(sequenceNum);
		threadTest01.setName("线程1");
		
		ThreadTest threadTest02 = new ThreadTest(sequenceNum);
		threadTest02.setName("线程2");
		
		ThreadTest threadTest03 = new ThreadTest(sequenceNum);
		threadTest03.setName("线程3");
		
		//让3个线程就绪
		threadTest01.start();
		threadTest02.start();
		threadTest03.start();
	}
}

java线程池对同一个list操作 demo_System_12


java线程池对同一个list操作 demo_优先级_13


ThreadLocal使用场景

java线程池对同一个list操作 demo_System_14

线程间通信 wait以,notify,notifyAll

4-16 线程间通信 :wait以及notify,notifyAll

通过Object中wait以及notify等方法实现线程通信
★- wait():导致当前线程等待,直到其他线程调用该同步监视器的notify()方法或notifyAll()方法来唤醒该线程。该wait()方法有三种形式:无时间参数的wait(一直等待,直到其他线程通知),带毫秒参数的wait和带毫秒、微秒参数的wait(这两种方法都是等待指定时间后自动苏醒)。调用wait()方法的当前线程会释放对该同步监视器的锁定。

★- notify():唤醒在此同步监视器上等待的单个线程。如果所有线程都在此同步监视器上等待,则会选择唤醒其中一个线程。选择是任意性的。只有当前线程放弃对该同步监视器的锁定后(使用wait()方法),才可以执行被唤醒的线程。

★- notifyAll():唤醒在此同步监视器上等待的所有线程。只有当前线程放弃对该同步监视器的锁定后,才可以执行被唤醒的线程。

★必须要把wait和notify放到同步代码块或者同步的方法里面。

java线程池对同一个list操作 demo_System_15


java线程池对同一个list操作 demo_System_16

public class Account {

	  //卡号
	  private String cardId;
	  //密码
	  private String password;
	  //余额
	  private double balance;
	  
	  
	  //定义标识符  ,用于记录卡里是否有钱
	 private boolean flag = true;
	  
	  
	  //取钱
	  public synchronized void drawMoney(double quKuanMoney) {
		 
		 try {
			 if(flag) {
				 
				 if(this.getBalance() >= quKuanMoney) {
					 
					 
					 //更新余额
					 this.setBalance(this.getBalance() - quKuanMoney);
					 System.out.println(Thread.currentThread().getName()+" 取款成功,余额"+this.getBalance());

					 //声明卡中没钱了
					 flag = false;
					 //通知其他线程
					  this.notifyAll();
					  //当前线程处于等待状态
					  this.wait();
					 
				 }else {
					 //余额不够
					 System.out.println(Thread.currentThread().getName()+" 取款失败,余额不够!");
					 //通知其他线程
					  this.notifyAll();
					  //当前线程处于等待状态
					  this.wait();
				 }
				   
				   
			   }else {
				   //说明没钱,通过 小头爸爸  二叔  王叔,给小明打钱
				   //通知其他线程
				   this.notifyAll();
				   //当前线程处于等待状态
				   this.wait();
			   }
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
		   
		  
		  
	  }
	  
	  
	  //存钱
	  public synchronized void saveMoney(double saveMoney) {
		
		  try {
			  
			  //睡眠一秒钟
			  Thread.sleep(1000);

			  if(flag) {
				  //卡里有钱,通知小明取钱
				  //通知其他线程
				   this.notifyAll();
				   //当前线程处于等待状态
				   this.wait();  
			  }else {
				  //卡里没钱,直接更新卡中余额
				  this.setBalance(this.getBalance() + saveMoney);
				  
				  System.out.println(Thread.currentThread().getName()+" 存款成功,余额"+this.getBalance());
				  //将标志位改成 true,相当于声明卡中有钱
				  flag = true;
				  
				  //通知其他线程,包括小明
				   this.notifyAll();
				   //当前线程处于等待状态
				   this.wait();  
				  
			  }
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
		  
	  }
	  
}

模拟取钱线程

/* 模拟取钱线程
 */
public class DrawThread extends Thread {

	
	//存款账户
	private Account account;
	//存款金额
	private double quKuanMoney;

	
	public DrawThread(Account account,double quKuanMoney) {
		
		this.account = account;
		this.quKuanMoney = quKuanMoney;
	}
	
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(true) {
			//开始取钱
			account.drawMoney(quKuanMoney);
		}
	}
}

模拟存钱线程

/* 模拟存钱线程
 */
public class SaveThread extends Thread {
    
	//存款账户
	private Account account;
	//存多少钱
	private double saveMoney;

	
	public SaveThread(Account account,double saveMoney) {
		
		this.account = account;
		this.saveMoney = saveMoney;
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub

         //不断给小明打钱
		while(true) {
			//给指定账户存钱
			account.saveMoney(saveMoney);
		}

	}

}
/* 
 *  启动线程进行存款以及取款操作:
 *  1、小明负责取钱
 *  2、小明老爸、小明二叔、隔壁王叔   负责给小明账户存钱
 *  3、当小明的取款金额大于余额就可以取钱,当余额不够就通知其他线程给小明账户存钱
 *     存完钱之后就通知小明去取钱
 *  那么需要用到
 *  wait方法 以及  notify、notifyAll进行线程通信
 */
public class ThreadTest {

	  public static void main(String[] args) {
		
		  //创建账户
		  Account account = new Account();
		  //卡号
		  account.setCardId("88888");
		  //密码
		  account.setPassword("1234");
		  //设置余额
		  account.setBalance(10000);
		  
		   
		  //模拟四个线程    一个负责取钱   三个负责存钱      第一个参数:账户     第二个参数:取款金额
		  DrawThread drawThread = new DrawThread(account,10000);
		  drawThread.setName("大头儿子");
		  //让线程就绪
		  drawThread.start();

		  /**三个负责存钱, 第一个参数:账户     第二个参数:取款金额*/

		  //             第一个参数:账户     第二个参数:存款金额
		  SaveThread saveThread01 = new SaveThread(account,10000);
		  saveThread01.setName("小头爸爸");
		  //             第一个参数:账户     第二个参数:存款金额
		  SaveThread saveThread02 = new SaveThread(account,10000);
		  saveThread02.setName("二叔");
		  //             第一个参数:账户     第二个参数:存款金额
		  SaveThread saveThread03 = new SaveThread(account,10000);
		  saveThread03.setName("隔壁老王");
		  
		  
		  //让线程就绪
		  saveThread01.start();
		  saveThread02.start();
		  saveThread03.start();
		  
		  
	}
	  
}

4-17线程池

线程池优点:
1.减少线程间切换系统运行内存的开销

★ 系统启动一个新线程的成本是比较高的,因为它涉及到与操作系统交互。在这种情形下,使用线程池可以很好地提高性能,尤其是当程序中需要创建大量生存期很短暂的线程时,更应该考虑使用线程池。

与数据库连接池类似的是,线程池在系统启动时即创建大量空闲的线程,程序将一个Runnable对象传给线程池,线程池就会启动一条线程来执行该对象的run方法,当run方法执行结束后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个Runnable对象的run方法。

★ JDK 1.5创建线程池非常容易:

JDK 1.5提供了ExecutorService对象来代表线程池。
    JDK1.5提供了一个Executors工厂类来产生线程池,该工厂类里包含如下几个静态工厂方法来创建线程池:

java线程池对同一个list操作 demo_创建线程_17


java线程池对同一个list操作 demo_优先级_18

- newCachedThreadPool():创建一个具有缓存功能的线程池,系统根据需要创建线程,这些线程将会被缓存在线程池中。

  - newFixedThreadPool(int nThreads):创建一个可重用的、具有固定线程数的线程池。

  - newSingleThreadExecutor():创建一个只有单线程的线程池,它相当于newFixedThreadPool方法时传入参数为1。

  - newScheduledThreadPool(int corePoolSize):创建具有指定线程数的线程池,它可以在指定延迟后执行线程任务。          corePoolSize指池中所保存的线程数,即使线程是空闲的也被保存在线程池内。

  - newSingleThreadScheduledExecutor():创建只有一条线程的线程池,它可以在指定延迟后执行线程任务。

★ 在JDK 1.5时,使用线程池的方法:

(1)先调用Executors工厂类的static方法,创建ExecutorService对象,可作为线程池。

 (2)将一个Runnable对象,或Callable对象的提交给ExecutorService线程池。

        submit()   - 让线程池尽早执行该Runnable对象。

        schedule() - 让线程池在暂停某个时间后执行Runnable对象。

 (3)调用线程池的方法关闭 ,不调用	executorService.shutdown(); 程序不会运行结束

java线程池对同一个list操作 demo_优先级_19

/* 线程池
 */
public class ThreadPoolTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub

		//通过Executors的静态方法获取一个线程池         newFixedThreadPool(3):创建容量为3的线程池
		ExecutorService  executorService = Executors.newFixedThreadPool(3);
		
        //创建Runnable实现类ThreadTest的实例
		ThreadTest threadTest = new ThreadTest();
		
		//将Runnable实现类ThreadTest的实例提交给线程池,线程池会将  threadTest包装成一个线程,放在池子中,并且会通知CPU调用run方法
		//创建一个线程放在线程池,并执行 run方法
		executorService.submit(threadTest);
		
		//创建一个线程放在线程池,并执行 run方法
		executorService.submit(threadTest);
		
		//创建一个线程放在线程池,并执行 run方法
		executorService.submit(threadTest);
		
		//从池子中拿出一个线程并执行
		executorService.submit(threadTest);
		
		//关闭线程池
		executorService.shutdown();

	}

}


class ThreadTest implements Runnable{

	
	@SneakyThrows
	@Override
	public void run() {
		// TODO Auto-generated method stub
		
		for (int i = 0; i < 3; i++) {
			Thread.sleep(100);
			System.out.println(Thread.currentThread().getName()+"---i:"+i);
		}
		System.out.println("------------"+Thread.currentThread().getName()+"执行结束"+"------------");
		
	}
	
}

pool-1-thread-2---i:0
pool-1-thread-3---i:0
pool-1-thread-1---i:0
pool-1-thread-1---i:1
pool-1-thread-2---i:1
pool-1-thread-3---i:1
pool-1-thread-3---i:2
pool-1-thread-2---i:2
------------pool-1-thread-2执行结束------------
------------pool-1-thread-3执行结束------------
pool-1-thread-1---i:2
------------pool-1-thread-1执行结束------------
pool-1-thread-2---i:0
pool-1-thread-2---i:1
pool-1-thread-2---i:2
------------pool-1-thread-2执行结束------------
  • 创建池子容量为1的线程池,往里面放3个线程的
    //通过Executors的静态方法获取一个线程池 newFixedThreadPool(1):创建容量为1的线程池
    ExecutorService executorService = Executors.newFixedThreadPool(1);
/* 线程池
 */
public class ThreadPoolTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub

		//通过Executors的静态方法获取一个线程池         newFixedThreadPool(3):创建容量为3的线程池
		ExecutorService  executorService = Executors.newFixedThreadPool(1);
		
        //创建Runnable实现类ThreadTest的实例
		ThreadTest threadTest = new ThreadTest();
		
		//将Runnable实现类ThreadTest的实例提交给线程池,线程池会将  threadTest包装成一个线程,放在池子中,并且会通知CPU调用run方法
		//创建一个线程放在线程池,并执行 run方法
		executorService.submit(threadTest);
		
		//创建一个线程放在线程池,并执行 run方法
		executorService.submit(threadTest);
		
		//创建一个线程放在线程池,并执行 run方法
		executorService.submit(threadTest);
		
		//从池子中拿出一个线程并执行
		executorService.submit(threadTest);
		
		//关闭线程池
		executorService.shutdown();

	}

}


class ThreadTest implements Runnable{

	
	@SneakyThrows
	@Override
	public void run() {
		// TODO Auto-generated method stub
		
		for (int i = 0; i < 3; i++) {
			Thread.sleep(100);
			System.out.println(Thread.currentThread().getName()+"---i:"+i);
		}
		System.out.println("------------"+Thread.currentThread().getName()+"执行结束"+"------------");
		
	}
	
}

结果:
就体现出线程池优点:.减少线程间切换系统运行内存的开销

pool-1-thread-1---i:0
pool-1-thread-1---i:1
pool-1-thread-1---i:2
------------pool-1-thread-1执行结束------------
pool-1-thread-1---i:0
pool-1-thread-1---i:1
pool-1-thread-1---i:2
------------pool-1-thread-1执行结束------------
pool-1-thread-1---i:0
pool-1-thread-1---i:1
pool-1-thread-1---i:2
------------pool-1-thread-1执行结束------------
pool-1-thread-1---i:0
pool-1-thread-1---i:1
pool-1-thread-1---i:2
------------pool-1-thread-1执行结束------------

4-18线程组(用的不多)

线程组:

Java使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序 直接对线程组进行控制。

一旦某个线程加入了指定线程组之后,该线程将一直属于该线程组,直到该线程死亡,线程运行中途不能改变它所属的线程组。

java线程池对同一个list操作 demo_System_20

/**
 * 线程组:可以对线程进行批量管理,包括可以用线程组处理该组中所有的线程出现的异常
 */
public class ThreadGroupTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		
		//创建线程组
		ThreadGroup  threadGroup = new ThreadGroup("线程组一") {
			@Override
			public void uncaughtException(Thread t, Throwable e) {
				// TODO Auto-generated method stub
				//e.printStackTrace();
				System.out.println(t.getName()+"  异常的原因:"+e.getMessage());
			}
			
		};

		//创建线程
		Th th1 = new Th();
		
		//创建线程  指定该线程属于哪一个线程组        第一个参数:线程组     第二个参数:Runnable实现类实例    第三个参数:线程名字
		Thread thread = new Thread(threadGroup,th1,"线程一");
		//让线程就绪
		thread.start();
		
		Thread thread2 = new Thread(threadGroup,th1,"线程二");
		thread2.start();
		
		System.out.println("====代码执行完毕===");
		
	}

}


class Th implements Runnable{

	@Override
	public void run() {
		// TODO Auto-generated method stub
		for (int i = 0; i < 20; i++) {
			if(i==10) {
				
				System.out.println(20/(i-10));

			}else {
				System.out.println("线程名:"+Thread.currentThread().getName()+"    i:"+i);
			}
			 
		}
		
		System.out.println(Thread.currentThread().getName()+"执行完毕=====");
	}
	
	  
}

4-20后台线程|守护线程

java线程池对同一个list操作 demo_优先级_21


java线程池对同一个list操作 demo_System_22

/* 线程中常用方法讲解
 */
public class ThreadTest extends Thread{

	/* (non-Javadoc)
	 * @see java.lang.Thread#run()
	 */
	@SneakyThrows
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for (int i = 0; i < 5; i++) {
			Thread.sleep(100);
			//获取当前线程实例
			Thread thread = Thread.currentThread();
			//获取线程的名字,可以重复
			String name = thread.getName();
			//获取线程id,线程id是不会重复的,好比人的省份证一样不会重写
			long id = thread.getId();


			//获取线程的优先级     如果线程的优先级越大,说明获取CPU的执行机会越大
			int priority = thread.getPriority();
			System.out.println("name:"+name+"   id:"+id +" 优先级:"+priority);
		}
	}


	/**
	 * @param args
	 */
	public static void main(String[] args) throws InterruptedException {

		  ThreadTest thread = new ThreadTest();
		  thread.setName("线程一");
		  //设置线程的优先级
		  thread.setPriority(Thread.MIN_PRIORITY);
		  //设置为后台线程     最后死亡,最后死亡不代表最后执行完毕
		  thread.setDaemon(true);
		  //让线程就绪
		  thread.start();

		  for (int i = 0; i < 5; i++) {
			  Thread.sleep(100);
			 //获取当前线程实例
			Thread thread2 = Thread.currentThread();
			//获取线程的名字,可以重复
			String name = thread2.getName();
			//获取线程id,线程id是不会重复的,好比人的省份证一样不会重写
			long id = thread2.getId();

			//获取线程的优先级     如果线程的优先级越大,说明获取CPU的执行机会越大
			int priority = thread2.getPriority();
			System.out.println("name:"+name+"   id:"+id +" 优先级:"+priority);
		}

	}

}

后台线程最后死亡,但并不最后执行

java线程池对同一个list操作 demo_System_23

4-21通过Collections保证集合线程安全

  • java中提供了一个工具类 Collections,调用Collections类中指定的方法可以获取线程安全的集合
    例如:ArrayList<>() 是线程不安全的集合,放入Collections.synchronizedList(new ArrayList())中返回的集合就是线程安全的
public static void main(String[] args) {
		// TODO Auto-generated method stub
        //java中提供了一个工具类  Collections,调用Collections类中指定的方法可以获取线程安全的集合
		List<Integer> list = Collections.synchronizedList(new ArrayList<Integer>());
		
		//获取线程安全的HashMap集合
		Map<Integer,String> maps = Collections.synchronizedMap(new HashMap<Integer,String>());
		
	
	}