学习课程连接:Java异常处理+集合+IO流+多线程+网络编程

第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对多线程的支持十分的完美。

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@Overridepublic void run() {// TODO Auto-generated method stubfor (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@Overridepublic void run() {// TODO Auto-generated method stubfor (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@Overridepublic void run() {// TODO Auto-generated method stubfor (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构造器中,再让线程就绪

/***创建线程的方式有三种*1、实现Callable接口,并重写call方法***/
public class MyThread_03  implements Callable<Integer>{/** 重写Callable接口中的call方法*/@Overridepublic Integer call() throws Exception {// TODO Auto-generated method stubint 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 exceptione.printStackTrace();}}}

4-7线程的生命周期

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()*/@Overridepublic void run() {// TODO Auto-generated method stubfor (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方法@Overridepublic void run() {// TODO Auto-generated method stubfor (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 exceptione.printStackTrace();}}}

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 exceptione.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 stubthis.account = account;this.drawMoney = drawMoney;}//重写Thread类的run方法,@Overridepublic 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 通过同步锁保证取款线程安全


通过synchronized实现线程安全:
1、方法使用synchronized修饰
2、需要同步的代码块使用synchronized同步,提供同步监视器防止多个线程同时操作统一资源;

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

通过synchronized实现线程安全:

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 exceptione.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 exceptione.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();

不加锁可能出现的现象:

加锁:
例子:

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

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 exceptione.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 exceptione.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 stubthis.account = account;this.drawMoney = drawMoney;}//重写Thread类的run方法,@Overridepublic 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讲解


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

public class ThreadTest extends Thread{private SequenceNum sequenceNum;public ThreadTest(SequenceNum sequenceNum) {super();// TODO Auto-generated constructor stubthis.sequenceNum = sequenceNum;}@SneakyThrows@Overridepublic void run() {// TODO Auto-generated method stubfor (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();}
}

  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();//因为从没存过,第一次取不到为nullif(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();}
}



ThreadLocal使用场景

线程间通信 wait以,notify,notifyAll

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

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

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

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

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

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 exceptione.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 exceptione.printStackTrace();}}}

模拟取钱线程

/* 模拟取钱线程*/
public class DrawThread extends Thread {//存款账户private Account account;//存款金额private double quKuanMoney;public DrawThread(Account account,double quKuanMoney) {this.account = account;this.quKuanMoney = quKuanMoney;}@Overridepublic void run() {// TODO Auto-generated method stubwhile(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;}@Overridepublic 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工厂类来产生线程池,该工厂类里包含如下几个静态工厂方法来创建线程池:


  - 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(); 程序不会运行结束

/* 线程池*/
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@Overridepublic void run() {// TODO Auto-generated method stubfor (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@Overridepublic void run() {// TODO Auto-generated method stubfor (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允许程序 直接对线程组进行控制。

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

/*** 线程组:可以对线程进行批量管理,包括可以用线程组处理该组中所有的线程出现的异常*/
public class ThreadGroupTest {/*** @param args*/public static void main(String[] args) {// TODO Auto-generated method stub//创建线程组ThreadGroup  threadGroup = new ThreadGroup("线程组一") {@Overridepublic 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{@Overridepublic void run() {// TODO Auto-generated method stubfor (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后台线程|守护线程


/* 线程中常用方法讲解*/
public class ThreadTest extends Thread{/* (non-Javadoc)* @see java.lang.Thread#run()*/@SneakyThrows@Overridepublic void run() {// TODO Auto-generated method stubfor (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);}}}

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

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>());}

Java多线程+线程池相关推荐

  1. Java多线程 线程池Executor框架

    目录 一.说明 二.理解 Executor ExecutorService Executors 三.实现 1. newSingleThreadExecutor 2. newFixedThreadPoo ...

  2. Java多线程——线程池的饥饿现象

    概述 定长线程池的使用过程中会存在饥饿现象,也就是当多线程情况下,当池中所有线程都被占用后,被占用的线程又需要空闲线程去进行下一步的操作,此时又获取不到池中空闲的线程,此时就出现了饥饿现象. 示例 p ...

  3. Java多线程- 线程池的基本使用和执行流程分析 - ThreadPoolExecutor

    线程池的实现原理 池化技术 一说到线程池自然就会想到池化技术. 其实所谓池化技术,就是把一些能够复用的东西放到池中,避免重复创建.销毁的开销,从而极大提高性能. 常见池化技术的例如: 线程池 内存池 ...

  4. java多线程线程池_Java多线程——线程池(ThreadPool)

    我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁 ...

  5. Java多线程——线程池使用示例

    示例代码: import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;public clas ...

  6. 多线程线程池的实现java_如何在Java中实现线程池

    多线程线程池的实现java 线程是独立程序的执行路径. 在java中,每个线程都扩展java.lang.Thread类或实现java.lang.Runnable. 多线程是指在一个任务中同时执行两个或 ...

  7. java定时线程池_java 定时器线程池(ScheduledThreadPoolExecutor)的实现

    前言 定时器线程池提供了定时执行任务的能力,即可以延迟执行,可以周期性执行.但定时器线程池也还是线程池,最底层实现还是ThreadPoolExecutor,可以参考我的另外一篇文章多线程–精通Thre ...

  8. Java中线程池,你真的会用吗

    转载自   Java中线程池,你真的会用吗 在<深入源码分析Java线程池的实现原理>这篇文章中,我们介绍过了Java中线程池的常见用法以及基本原理. 在文中有这样一段描述: 可以通过Ex ...

  9. java中线程池的几种实现方式

    1.线程池简介:     多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力.         假设一个服务器完成一项任务所需时间为:T1 ...

  10. 多线程线程池的基本创建,使用方法

    import java.util.concurrent.*;/*** 多线程线程池的基本创建,使用方法** @author silence*/ public class Silence {public ...

最新文章

  1. AI一分钟 | Google因跟踪用户数据接受调查;iPhone XS真机图曝光
  2. python利器怎么用-bluepy 一款python封装的BLE利器简单介绍
  3. 选择海外数据中心是否等级越高越好
  4. python根据月份获取月初月末_用python获取月末数据
  5. VUE 解决:Property or method “deleteFun“ is not defined on the instance but referenced during render.
  6. Docker 环境下如何 安装 Zookeeper
  7. java的常用引用类、数组、String类
  8. sourcetree的安装及使用
  9. protobuf java 自动反射_protobuf在java应用中通过反射动态创建对象
  10. 通过配置XML,使用TpiSyntaxAnalyzer语法分析,快速生成网页
  11. JS事件流与DOM事件处理程序
  12. Perl语言程序设计_输入与输出
  13. 软件需求的薛定谔之猫
  14. Unity 初识:创建游戏场景
  15. C# vb .net实现玻璃桌子效果滤镜
  16. 什么是迭代式项目开发
  17. html、css、js粒子特效——前端
  18. **浅谈STM32系列单片机的零基础学习方法**
  19. c#定义一个接口IShape,其中包括方法Area()用来计算面积,
  20. 去中心化交易所与中心化交易所的优劣势对比

热门文章

  1. matlab为数据加表头,matlab xlswrite 表头
  2. 玩转人工智能(11)使用Pyspark上手机器学习
  3. java工具类_16 个超级实用的 Java 工具类
  4. 【渝粤教育】电大中专电商运营实操 (24)作业 题库
  5. 第二章 工具变量法(IV)与两阶段最小二乘法
  6. python爬取全球历年GDP数据
  7. 移动硬盘提示文件或目录损坏且无法读取怎么办
  8. vss服务器状态失败_关于vss事件日志报如下错误
  9. [转载]MIS专业排名
  10. find -regex