本文共 30070 字,大约阅读时间需要 100 分钟。
1)动态性: 进程是运行中的程序,要动态的占用内存,CPU和网络等资源。
2)独立性 : 进程与进程之间是相关独立的,彼此有自己的独立内存区域。
3)并发和并行
并行: 某个时间段同时运行多个程序。
并发: 在某个时间点同时运行多个程序。
定义一个线程类继承Thread类,然后重写run()方法,再创建线程对象,调用start()方法启动线程。
package cn.guardwhy_01;/**方式一的步骤: a.定义一个线程类继承Thread类 b.重写Thread类的run()方法 c.创建一个子线程对象 d.调用线程对象的start()方法启动线程(其实最终就是执行线程对象的run()方法)线程的注意: 1.启动线程不能直接调用run()方法,否则是普通对象的普通方法调用了,将失去线程特征。线程的启动必须调用start() 2.一般是先创建子线程,再申明主线程的任务,否则主线程任务总是先执行完! 优缺点: 优点:编码简单。 缺点:线程类已经继承了Thread类,不能再继承其他类,功能被削弱了。不能做线程池。无法直接返回线程执行的结果。 */// a.定义一个线程类继承Thread类class MyThread extends Thread{ // 重写Thread类的run()方法 @Override public void run() { for(int i=0; i < 10; i++){ System.out.println("子线程执行:" + i); } }}public class ThreadDemo02 { public static void main(String[] args) { // 创建一个子线程对象 MyThread t = new MyThread(); // 启动线程,线程的启动必须调用start() t.start(); // 遍历操作 for(int i=0; i<10; i++){ System.out.println("main线程执行:" + i); } }}
定义一个线程任务类实现Runnable接口,然后重写run()方法,创建线程任务对象,把线程任务对象包装成线程对象,调用start()方法启动线程。
package cn.guardwhy_03;/** 方式二: a.定义一个线程任务类实现Runnable接口。重写run()方法 b.创建一个线程任务对象 c.把线程任务对象包装成一个线程对象 -- public Thread(Runnable target) d.调用线程对象的start()方法启动线程。 优缺点: 缺点:编程相对复杂,不能直接返回线程的执行结果 优点: 1. 一个任务对象可以被反复包装成多个线程对象。 2. 可以避免java中的单继承的局限性。因为线程任务对象只是实现了接口,还可以继续继承其他类和实现其他接口。 3. 实现解耦操作,线程任务对象代码可以被多个线程共享,代码和线程独立。 4. 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。适合做线程池。 */// 定义一个线程任务类实现Runnable接口。class MyRunnable implements Runnable{ @Override public void run() { for(int i=0; i<10; i++){ System.out.println(Thread.currentThread().getName() + "=>" + i); } }}public class ThreadDemo01 { public static void main(String[] args) { // 创建一个线程任务对象 Runnable target = new MyRunnable(); // 将线程任务对象包装成线程对象 Thread t1 = new Thread(target); // 启动线程 t1.start(); // 创建线程对象 Thread t2 = new Thread(target); // 启动线程 t2.start(); for(int i=0; i<10; i++){ System.out.println(Thread.currentThread().getName() + "=>"+i); } }}
package cn.guardwhy_03;/**匿名内部类简化写法!*/public class ThreadDemo02 { public static void main(String[] args) { // 直接创建Runnable的线程任务对象的匿名内部类形式 /* Runnable target = new Runnable() { @Override public void run() { for(int i=0; i<10; i++){ System.out.println(Thread.currentThread().getName() + "=>" + i); } } }; Thread t1 = new Thread(target); t1.start(); */ // 匿名形式.. new Thread(new Runnable() { @Override public void run() { for(int i=0; i<10; i++){ System.out.println(Thread.currentThread().getName() + "=>"+i); } } }).start(); // 遍历操作 for(int i=0; i<10; i++){ System.out.println(Thread.currentThread().getName()+"=>"+i); } }}
注意
启动线程不能直接使用run()方法?
因为run()方法仅仅是封装线程执行代码,直接调用是普通方法。Start( )方法首先启动了线程,然后由JVM去调用该线程的run()方法。
JVM虚拟机的启动是多线程的,原因是垃圾回收线程要先启动,否则容易出现内存溢出。1) Thread类是Runnable接口的子类,但是Thread类中并且没有完全实现Runnable接口中的run( )方法。
2)如果一个类继承Thread类,不适合于多个线程共享资源,实现Runnable接口,就可以方便地实现资源共享。
定义一个线程任务类实现Callable接口。
package cn.guardwhy_04;import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.FutureTask;/** 创建线程的方式三: a.定义一个线程任务类实现Callable接口。 b.重写call()方法。 c.把线程任务对象包装成一个未来任务对象。 d.把未来任务对象包装成一个线程对象。 e.调用线程对象的start()方法启动线程。优缺点: 缺点:编码复杂。 优点:全是优点。 可以继续继承其他类。可以做线程池。可以得到线程返回的结果。可以做资源共享操作。*///a.定义一个线程任务类实现Callable接口,申明返回值类型class MyCallable implements Callable{ @Override public String call() throws Exception { // 定义计数器 int count = 0; for(int i=0; i<10; i++){ // 1-10的和 count += (i+1); System.out.println(Thread.currentThread().getName()+"=>"+i); } return Thread.currentThread().getName()+"求和结果:" + count; }}public class ThreadDemo01 { public static void main(String[] args) { // 把线程任务对象包装成一个未来任务对象。 MyCallable call = new MyCallable(); /** * 未来任务对象: FutureTask * 1.可以通过未来任务对象去获取线程执行的结果。 * 2.未来任务对象其实就是一个Runnable的对象。 */ FutureTask target = new FutureTask<>(call); // 将未来任务对象包装成一个线程对象 Thread t1 = new Thread(target); // 调用线程对象的start()方法启动线程 t1.start(); for(int i=0; i<10; i++){ System.out.println(Thread.currentThread().getName() + "=>" + i); } try { // 线程的执行的结果 String result = target.get(); System.out.println("线程执行结果:" + result); } catch (Exception e) { e.printStackTrace(); } }}
Callable底层源码
package cn.guardwhy.List01;import java.util.concurrent.Callable;import java.util.concurrent.FutureTask;public class CallableTest01 { public static void main(String[] args) throws Exception { /* * 步骤: * 1. new Thread(new Runable()).start; * 2. new Thread(new FutureTask()).start; * 3. new Thread(new FutureTask (Callable)).start; */ // 创建线程对象 MyThread thread = new MyThread(); // 适配类:未来任务对象 FutureTask FutureTask futureTask = new FutureTask(thread); // 线程操作 new Thread(futureTask, "kobe").start(); // 第二次调用执行,会有结果缓存,不用再次计算 new Thread(futureTask, "curry").start(); // 获取操作,get方法可能会产生阻塞,放到最后 Integer result = (Integer) futureTask.get(); System.out.println(result); }}class MyThread implements Callable { @Override public Integer call() throws Exception { System.out.println("call方法被调用"); // 耗时操作 return 666; }}
1)新建状态 :使用new关键字创建之后进入的状态,此时线程并没有开始执行。
2)就绪状态:调用start方法后进入的状态,此时线程还是没有开始执行。
3) 运行状态:使用线程调度器调用该线程后进入的状态,此时线程开始执行,当线程的时间片执行完毕后任务没有完成时回到就绪状态。
4) 阻塞状态:当线程执行的过程中发生了阻塞事件进入的状态,如:sleep方法。阻塞状态解除后进入就绪状态。
5)死亡状态:线程调用stop( )方法时或者run( )方法执行结束后,即处于死亡状态。处于死亡状态的线程不具有继续运行的能力。
新建-就绪-运行-死亡新建-就绪-运行-就绪-运行-死亡新建-就绪-运行-就绪-运行-死亡新建-就绪-运行-其他阻塞-就绪-运行-死亡新建-就绪-运行-同步阻塞-就绪-运行-死亡新建-就绪-运行-等待阻塞-同步阻塞-就绪-运行-死亡
方法声明 | 功能介绍 |
---|---|
Thread( ) | 使用无参的方式构造对象 |
Thread(String name) | 根据参数指定的名称来构造对象 |
Thread(Runnable target,String name) | 根据参数指定引用和名称来构造对象。 |
void start( ) | 用于启动线程,Java虚拟机会自动调用该线程的run方法 |
long getId( ) | 获取调用对象所表示线程的编号。 |
Thread currentThread( ) | 获取当前正在执行线程的引用。 |
int getPriority( ) | 发挥线程的优先级 |
boolean isInterrupted( ) | 判断目前线程是否被中断 |
void join( ) | 等待线程死亡 |
String getName( ) | 返回线程的名称 |
void yield( ) | 将目前正在执行的线程暂停 |
package cn.guardwhy_02;/**Thread多线程常用API: 线程是有默认名字的:子线程的名称规则Thread_索引, main线程的默认名称就是main 1.public void setName(String name):给线程对象取名字。 2.public String getName():返回线程对象的名字。 3.public static Thread currentThread(): 获取当前线程对象,这个代码是哪个线程在执行就返回哪个线程对象。 */// 定义一个线程类继承Thread类,线程类不是线程对象,是用来创建线程对象的。class MyThread extends Thread{ @Override public void run() { for(int i=0; i < 10; i++){ System.out.println(Thread.currentThread().getName() + "=>" + i); } }}public class ThreadDemo01 { public static void main(String[] args) { // 创建一个子线程对象 MyThread t1 = new MyThread(); // 设置线程 t1.setName("1号线程"); // 启动线程 t1.start(); // 输出t1线程对象 // System.out.println(t1.getName()); // 创建t2子线程对象 MyThread t2 = new MyThread(); t2.setName("2号线程.."); // 启动线程 t2.start(); // 输出t2线程对象 // System.out.println(t2.getName()); // 返回当前线程对象,这个代码是哪个线程在执行就返回哪个线程对象。 Thread main = Thread.currentThread(); // System.out.println(main.getName()); main.setName("最牛逼的线程"); for (int i=0; i< 10; i++){ System.out.println(main.getName()+ "=>" + i); } }}
通过有参数构造器为线程对象取名字
package cn.guardwhy_02;/** 目标:通过有参数构造器为线程对象取名字(拓展) Thread父类的有参数构造器: public Thread(String name): */// 定义一个线程类继承Thread类class MyThread02 extends Thread{ // 代参构造器 public MyThread02(String name) { super(name); } // 重写Thread类的run()方法 @Override public void run() { for(int i=0; i<10; i++){ System.out.println(Thread.currentThread().getName() + "=>" + i); } }}public class ThreadDemo02 { public static void main(String[] args) { // 创建一个子进程对象 MyThread02 t1 = new MyThread02("1号线程"); // 启动线程 t1.start(); MyThread02 t2 = new MyThread02("2号线程"); // 启动线程 t2.start(); // 获取当前线程对象 Thread main = Thread.currentThread(); for(int i=0; i<10; i++){ System.out.println(main.getName() + "=>"+i); } }}
1) 代码示例
package cn.thread.demo01;class MyThread implements Runnable{ @Override public void run() { // 覆写run()方法 for(int i=0; i<10; i++){ // 输出线程名称 System.out.println(Thread.currentThread().getName() + "运行 -->" + i); } }}public class ThreadJoinDemo01 { public static void main(String[] args) { // 实例化对象 MyThread mt = new MyThread(); // 实例化Thread对象 Thread t = new Thread(mt, "线程"); // 线程启动 t.start(); // 循环10次 for(int i=0; i<10; i++){ // 判断变量内容 if(i > 3){ try { // 线程t进行强制运行 t.join(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("Main 线程运行 -->" + i); } }}
2) 执行结果
package cn.guardwhy_10;/** 线程休眠 Thread.sleep(5000):参数是毫秒,让当前所在线程对象休眠5s。 */public class ThreadDemo01 { public static void main(String[] args) { // 条件遍历 for(int i=0; i<10; i++){ System.out.println("输出:" + i); if(i == 5){ try { // 让当前线程休眠5s,休眠是不释放锁的。 Thread.sleep(5000); } catch (Exception e) { e.printStackTrace(); } } } }}
线程问题的核心原因:多个线程操作同一个共享资源的时候可能出现线程安全问题。
1)账户对象
package cn.guardwhy_05;public class Account { // 卡号 private String cardId; // 余额 private double money; // 无参构造器 public Account() { } // 代参构造器 public Account(String cardId, double money) { this.cardId = cardId; this.money = money; } /* get/set方法 */ public String getCardId() { return cardId; } public void setCardId(String cardId) { this.cardId = cardId; } public double getMoney() { return money; } public void setMoney(double money) { this.money = money; } // 取钱地点方法 public void drawMoney(double money){ // 1.先拿到是谁来取钱:拿到当前线程的名字即可,名字是谁就是谁来取钱 String name = Thread.currentThread().getName(); // 2.判断余额是否足够 if(this.money >= money){ // 钱够了 System.out.println(name + "来取钱,余额足够,吐出:" + money); // 更新余额 this.money -= money; System.out.println(name + "取钱剩余:" + this.money); }else { // 钱不够 System.out.println(name + "来取钱,余额不足.."); } }}
2) 取钱的线程类
package cn.guardwhy_05;/** * 取钱的线程类 */public class DrawThread extends Thread{ // 定义一个成员变量接收账户对象 private Account acc; // 带参构造器 public DrawThread(String name, Account acc) { super(name); this.acc = acc; } @Override public void run() { // 去账户acc中取钱 acc.drawMoney(10000); }}
3)转账功能
package cn.guardwhy_05;/** 先模拟一个线程安全问题的案例:转账功能。 分析:整存整取。 (1)定义一个账户(余额,卡号)。 (2)定义一个取钱的线程类 (3)创建一个账户对象,创建2个线程对象,去这个账户对象取钱10000总结: 多个线程操作同一个共享资源的时候可能出现线程安全问题。 */public class ThreadSaveDemo01 { public static void main(String[] args) { // 1.创建一个共享资源:是一个账户对象,这个对象必须只有一个。 Account acc = new Account("ICBC-162", 10000); // 创建2个线程对象代表curry和james DrawThread curry = new DrawThread("curry", acc); // 启动线程 curry.start(); DrawThread james = new DrawThread("james", acc); // 启动线程 james.start(); }}
4) 执行结果
1)引发原因
2)解决方案
用于解决线程安全问题,线程同步就是指线程安全了。线程同步解决线程安全问题的核心思想。
线程同步就是让多个线程实现先后有序的访问共享资源,每次只能一个线程执行完毕,另一个线程才能进行。
1) 账户对象
package cn.guardwhy_06;public class Account { // 卡号 private String cardId; // 余额 private double money; // 无参构造器 public Account() { } // 代参构造器 public Account(String cardId, double money) { this.cardId = cardId; this.money = money; } /* get/set方法 */ public String getCardId() { return cardId; } public void setCardId(String cardId) { this.cardId = cardId; } public double getMoney() { return money; } public void setMoney(double money) { this.money = money; } // 取钱地点方法 public void drawMoney(double money){ // 1.先拿到是谁来取钱:拿到当前线程的名字即可,名字是谁就是谁来取钱 String name = Thread.currentThread().getName(); // 2.判断余额是否足够 synchronized (this){ if(this.money >= money){ // 钱够了 System.out.println(name + "来取钱,余额足够,吐出:" + money); // 更新余额 this.money -= money; System.out.println(name + "取钱剩余:" + this.money); }else { // 钱不够 System.out.println(name + "来取钱,余额不足.."); } } }}
2) 取钱的线程类
package cn.guardwhy_06;/** * 取钱的线程类 */public class DrawThread extends Thread{ // 定义一个成员变量接收账户对象 private Account acc; // 带参构造器 public DrawThread(String name, Account acc) { super(name); this.acc = acc; } @Override public void run() { // 去账户acc中取钱 acc.drawMoney(10000); }}
3)转账功能
package cn.guardwhy_06;/**同步代码块: 思想:把“出现线程安全问题的核心代码”给锁起来,每次只能进入一个线程, 其他线程必须在外面等这个线程执行完毕以后,才能进入执行,这样就线程安全了。格式: synchronized(锁对象){ 出现线程安全问题的核心代码。 }锁对象:原则上可以是任意唯一的Java对象。理论上:在实例方法中推荐用this作为锁.在静态方法中推荐用类名.class字节码文件作为锁对象*/public class ThreadSaveDemo01 { public static void main(String[] args) { // 1.创建一个共享资源:是一个账户对象,这个对象必须只有一个。 Account acc = new Account("ICBC-162", 10000); // 创建2个线程对象代表curry和james DrawThread curry = new DrawThread("curry", acc); // 启动线程 curry.start(); DrawThread james = new DrawThread("james", acc); // 启动线程 james.start(); }}
4) 执行结果
1) 账户对象
package cn.guardwhy_07;public class Account { // 卡号 private String cardId; // 余额 private double money; // 无参构造器 public Account() { } // 代参构造器 public Account(String cardId, double money) { this.cardId = cardId; this.money = money; } /* get/set方法 */ public String getCardId() { return cardId; } public void setCardId(String cardId) { this.cardId = cardId; } public double getMoney() { return money; } public void setMoney(double money) { this.money = money; } // 取钱地点方法 public synchronized void drawMoney(double money){ // 1.先拿到是谁来取钱:拿到当前线程的名字即可,名字是谁就是谁来取钱 String name = Thread.currentThread().getName(); // 2.判断余额是否足够 if(this.money >= money){ // 钱够了 System.out.println(name + "来取钱,余额足够,吐出:" + money); // 更新余额 this.money -= money; System.out.println(name + "取钱剩余:" + this.money); }else { // 钱不够 System.out.println(name + "来取钱,余额不足.."); } }}
2) 取钱的线程类
package cn.guardwhy_07;public class DrawThread extends Thread{ // 定义一个成员变量接收账户对象 private Account acc; // 带参构造器 public DrawThread(String name, Account acc) { super(name); this.acc = acc; } @Override public void run() { // 去账户acc中取钱 acc.drawMoney(10000); }}
3) 转账功能
package cn.guardwhy_07;/** 同步方法: 思想:把"出现线程安全问题的核心方法"给锁起来,每次只能进入一个线程, 其他线程必须在外面等这个线程执行完毕以后,才能进入执行,这样就线程安全了。 只需要在方法上加上一个 synchronized 关键字即可! 原理:同步方法的原理与同步代码块的原理是一样的,只是同步方法是把整个方法体代码都锁起来,同步方法默认也是有锁对象的。 如果同步的方法是实例方法,默认用this作为锁对象。如果同步的方法是静态方法,默认用类名.class作为锁对象。 */public class ThreadSaveDemo01 { public static void main(String[] args) { // 1.创建一个共享资源:是一个账户对象,这个对象必须只有一个。 Account acc = new Account("ICBC-162", 10000); // 创建2个线程对象代表curry和james DrawThread curry = new DrawThread("curry", acc); // 启动线程 curry.start(); DrawThread james = new DrawThread("james", acc); // 启动线程 james.start(); }}
4) 执行结果
死锁:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。
基本结构
线程一执行的代码:public void run(){ synchronized(a){ //持有对象锁a,等待对象锁b synchronized(b){ 编写锁定的代码; } }}线程二执行的代码:public void run(){ synchronized(b){ //持有对象锁b,等待对象锁a synchronized(a){ 编写锁定的代码; } }}注意:在以后的开发中尽量减少同步的资源,减少同步代码块的嵌套结构的使用!
互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
循环等待,即存在一个等待队列:p1要p2的资源,p2要p1的资源。这样就形成了一个等待环路。
package cn.guardwhy_11;/** 实现死锁一般需要进行锁资源的嵌套才会出现死锁。 */public class ThreadDead { // 定义两个静态资源对象 public static Object resources1 = new Object(); public static Object resources2 = new Object(); public static void main(String[] args) { // 实现死锁现象至少存在两个线程 new Thread(new Runnable() { @Override public void run() { synchronized (resources1){ System.out.println("线程对象1对资源1上锁,占用资源1"); System.out.println("线程对象1开始请求资源2"); try { // 线程休眠 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (resources2){ System.out.println("线程对象1对资源2上锁,占用资源2"); } } } }).start(); new Thread(new Runnable() { @Override public void run() { synchronized (resources2){ System.out.println("线程对象2对资源2上锁,占用资源2"); System.out.println("线程对象2开始请求资源1"); try { // 线程休眠 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (resources1){ System.out.println("线程对象2对资源1上锁,占用资源1"); } } } }).start(); }}
方法声明 | 功能介绍 |
---|---|
void wait( ) | 用于使得线程进入等待状态,直到其它线程调用notify()或notifyAll()方法,此方法必须锁对象调用。 |
void wait(long timeout) | 用于进入等待状态,直到其它线程调用方法或参数指定的毫秒数已经过去为止。 |
void notify() | 唤醒当前锁对象上等待状态的某个线程 此方法必须锁对象调用。 |
void notifyAll( ) | 唤醒当前锁对象上等待状态的全部线程 此方法必须锁对象调用。 |
1)账户对象
package cn.guardwhy_09;/** * 账户对象 */public class Account { // 卡号 private String cardId; // 余额 private int money; // 无参构造器 public Account() { } // 代参构造器 public Account(String cardId, int money) { this.cardId = cardId; this.money = money; } /** * get/set方法 * @return */ public String getCardId() { return cardId; } public void setCardId(String cardId) { this.cardId = cardId; } public double getMoney() { return money; } public void setMoney(int money) { this.money = money; } // 取钱方法 public synchronized void drawMoney(int money){ try { // 1.先拿到是谁来取钱:拿到当前线程的名字即可,名字是谁就是谁来取钱 String name = Thread.currentThread().getName(); // 2.判断余额是否足够 if(this.money >= money){ // 更新余额 this.money -= money; System.out.println(name + "来取钱,余额足够.吐出:" + money + "元,剩余" + this.money + "元"); this.notify(); // 钱已经取完了,暂停自己唤醒其他线程! // 等待自己 this.wait(); }else { this.notify(); // 没钱了.唤醒其他线程! // 等待自己 this.wait(); } } catch (Exception e) { e.printStackTrace(); } } // 存钱方法 public synchronized void saveMoney(int money){ try { String name = Thread.currentThread().getName(); // 判断是否有钱 if(this.money == 0){ // 没钱,需要存钱 this.money += money; System.out.println(name + "来存钱" + money + "元成功, 剩余" + this.money + "元"); this.notify(); // 钱已经取完了,暂停自己,唤醒其他线程 this.wait(); // 等待自己 }else { this.notify(); // 唤醒其他线程 this.wait(); // 等待自己 } } catch (Exception e) { e.printStackTrace(); } }}
2) 存钱的线程类
package cn.guardwhy_09;/** 存钱的线程类。 */public class SaveThread extends Thread{ // 定义一个成员变量接收账户对象 private Account acc; // 带参构造器 public SaveThread(String name, Account acc) { super(name); this.acc = acc; } @Override public void run() { // 3个爸爸来反复的存钱. while (true){ try { // 线程休眠 Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } acc.saveMoney(10000); } }}
3) 取钱的线程类
package cn.guardwhy_09;/** 取钱的线程类。 */public class DrawThread extends Thread{ // 定义一个成员变量接收账户对象 private Account acc; // 带参构造器 public DrawThread(String name, Account acc) { super(name); this.acc = acc; } @Override public void run() { // curry和james来取钱 while (true){ // 线程休眠 try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } // 去账户acc中取钱 acc.drawMoney(10000); } }}
4) 线程通信
package cn.guardwhy_09;/** 线程通信:多个线程因为在同一个进程中,所以互相通信比较容易。 线程通信的经典模型:生产者与消费者问题。 生产者负责生成商品,消费者负责消费商品。 生成不能不剩,消费不能没有,是一个同步模型。 线程通信必须先保证线程安全,否则代码会报出异常!!模拟一个案例: curry和james有一个共同账户:共享资源 他们有3个爸爸(亲爸,岳父,干爹)给他们存钱。 模型:curry和james去取钱,如果有钱就取出,等待自己,唤醒他们3个爸爸们来存钱 他们的爸爸们来存钱,如果发现有钱就不存,没钱就存钱,然后等待自己,唤醒孩子们来取钱。 整存整取 10000元。分析: 生产者:亲爸,岳父,干爹 消费者:curry,james 共享资源:账户对象。 */public class ThreadCommunication01 { public static void main(String[] args) { // 创建一个共享资源账户对象 Account acc = new Account("ISBC-162530", 0); // 定义两个取钱线程代表curry和james new DrawThread("curry", acc).start(); new DrawThread("james", acc).start(); // 定义三个存钱线程,分别代表亲爸,岳父,干爹 new SaveThread("亲爸", acc).start(); new SaveThread("干爹", acc).start(); new SaveThread("岳父", acc).start(); }}
5)执行结果
方法声明 | 功能介绍 |
---|---|
ReentrantLock( ) | 使用无参方式构造对象 |
void lock( ) | 获取锁 |
void unlock( ) | 释放锁 |
与synchronized方式的比较
1) 账户对象
package cn.guardwhy_08;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;public class Account { // 卡号 private String cardId; // 余额 private double money; // 无参构造器 public Account() { } // 代参构造器 public Account(String cardId, double money) { this.cardId = cardId; this.money = money; } // 创建一把锁对象:必须保证这个对象唯一 private final Lock lock = new ReentrantLock(); /*** * get/set方法 * @return */ public String getCardId() { return cardId; } public void setCardId(String cardId) { this.cardId = cardId; } public double getMoney() { return money; } public void setMoney(double money) { this.money = money; } // 取钱地点方法 public void drawMoney(double money){ // 1.先拿到是谁来取钱:拿到当前线程的名字即可,名字是谁就是谁来取钱 String name = Thread.currentThread().getName(); // 2.判断余额是否足够,加锁操作 lock.lock(); try { if(this.money >= money){ // 钱够了 System.out.println(name + "来取钱,余额足够,吐出:" + money); // 更新余额 this.money -= money; System.out.println(name + "取钱剩余:" + this.money); }else { // 钱不够 System.out.println(name + "来取钱,余额不足.."); } } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); // 解锁操作 } }}
2) 取钱的线程类
package cn.guardwhy_08;/** * 取钱的线程类 */public class DrawThread extends Thread{ // 定义一个成员变量接收账户对象 private Account acc; // 带参构造器 public DrawThread(String name, Account acc) { super(name); this.acc = acc; } @Override public void run() { // 去账户acc中取钱 acc.drawMoney(10000); }}
3) 转账功能
package cn.guardwhy_08;/** Lock锁 java.util.concurrent.locks.Lock Lock锁也称同步锁,加锁与释放锁方法如下: - public void lock() :加同步锁。 - public void unlock():释放同步锁。 */public class ThreadSaveDemo01 { public static void main(String[] args) { // 1.创建一个共享资源:是一个账户对象,这个对象必须只有一个。 Account acc = new Account("ICBC-162", 10000); // 创建2个线程对象代表curry和james DrawThread curry = new DrawThread("curry", acc); // 启动线程 curry.start(); DrawThread james = new DrawThread("james", acc); // 启动线程 james.start(); }}
4) 执行结果
线程池其实就是一个容纳多个线程的容器,其中的线程可以反复使用。省去了频繁创建和销毁线程对象的操作,无需反复创建线程而消耗过多资源。
1.降低资源消耗,减少了创建和销毁线程的次数。每个工作线程都可以被重复利用,可执行多个任务。
2.提高响应速度,线程池的核心思想:线程复用,同一个线程可以被重复使用。
3.提高线程的可管理性(线程池可以约束系统最多只能有多少个线程,不会因为线程过多而死机)
package cn.guardwhy_12;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;/**线程池在Java中的代表: ExecutorService创建线程池的API: java.util.concurrent.Executors类下: -- public static ExecutorService newFixedThreadPool(int nThreads): 返回线程池对象。(创建的是有界线程池,也就是池中的线程个数可以指定最大数量)总结: 线程池启动后是不会死亡的,因为后续还要重复使用的。 void shutdown():会等全部线程执行完毕才关闭。比较友好! ListshutdownNow():立即关闭,不管是否执行完毕 */class MyRunnable implements Runnable{ @Override public void run() { for(int i=0; i<3; i++){ System.out.println("线程" + Thread.currentThread().getName()+" => " + i); } }}public class ThreadPoolsDemo01 { public static void main(String[] args) { // 创建一个线程池:线程池固定放置三个线程 ExecutorService pools = Executors.newFixedThreadPool(3); // 给线程池提交任务,提交任务的时候会自动创建线程对象 Runnable target = new MyRunnable(); // 提交任务会自动创建线程对象,并自动启动 pools.submit(target); pools.submit(target); pools.submit(target); // 这里不会再创建线程了,因为线程池已经满了,这里会复用之前的线程。 pools.submit(target); // 全部线程执行完毕才关闭 pools.shutdown(); }}
package cn.guardwhy_12;import java.util.concurrent.*;/**线程池的创建方式二。线程池在Java中的代表: ExecutorService创建线程池的API: java.util.concurrent.Executors类下: -- public static ExecutorService newFixedThreadPool(int nThreads): 返回线程池对象。(创建的是有界线程池,也就是池中的线程个数可以指定最大数量)往线程池中创建线程的API: 1.public Future submit(Runnable task) 2.Future submit(Callable task)总结: Callable接口创建线程对象是可以返回线程执行的结果的。 */class Mycallable implements Callable { // 定义变量 private int n; // 带参构造器 public Mycallable(int n) { this.n = n; } @Override public String call() throws Exception { // 定义计数器 int count = 0; for(int i=1; i<=n; i++){ count += i; } return Thread.currentThread().getName()+ "=> 1-" + n + "和是: " + count; }}public class ThreadPoolsDemo02 { public static void main(String[] args) { // 1.创建一个线程池:线程池固定放置三个线程 ExecutorService pools = Executors.newFixedThreadPool(3); // 2.提交任务给线程池 Mycallable t1 = new Mycallable(10); Mycallable t2 = new Mycallable(20); Mycallable t3 = new Mycallable(30); Mycallable t4 = new Mycallable(40); Future rs1 = pools.submit(t1); Future rs2 = pools.submit(t2); Future rs3 = pools.submit(t3); Future rs4 = pools.submit(t4); try { System.out.println(rs1.get()); System.out.println(rs2.get()); System.out.println(rs3.get()); System.out.println(rs4.get()); } catch (Exception e) { e.printStackTrace(); } pools.shutdown(); // 关闭线程池 }}
转载地址:http://jkhk.baihongyu.com/