博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
JavaSE- 多线程
阅读量:134 次
发布时间:2019-02-26

本文共 30070 字,大约阅读时间需要 100 分钟。

1-进程和线程

1.1 什么是进程

  • 程序是静止的,运行中的程序就是进程,是系统的进行资源分配和调用的独立单位。
  • 每一个进程都有它自己的内存空间和系统资源。

1.1.1 进程的特征

1)动态性: 进程是运行中的程序,要动态的占用内存,CPU和网络等资源。

2)独立性 : 进程与进程之间是相关独立的,彼此有自己的独立内存区域。

3)并发和并行

并行: 某个时间段同时运行多个程序。

并发: 在某个时间点同时运行多个程序。

1.2 什么是线程

  • 线程是属于进程的。一个进程可以包含多个线程,这就是多线程。
  • 在同一个进程内可以执行多个任务,而这每一个任务就可以看成是一个线程。
  • 线程是程序的执行单位,执行路径是程序使用cpu的最基本单位。线程也支持并发性。

1.2.1 线程的作用

  • 可以提高程序的效率,可以有更多机会得到CPU。多线程可以解决很多业务模型。
  • 大型高并发技术的核心技术,设计到多线程的开发可能都比较难理解。

1.2.2 进程与线程的区别

  • 线程是在进程的基础上划分的。
  • 线程消失了,进程不会消失,进程如果消失了,则线程肯定消失。

2- 线程创建

2.1 继承Thread类

2.1.1Thread类基本概念

  • java.lang.Thread类代表线程,任何线程对象都是Thread类(子类)的实例。
  • Thread类是线程的模板,封装了复杂的线程开启等操作,封装了操作系统的差异性。

定义一个线程类继承Thread类,然后重写run()方法,再创建线程对象,调用start()方法启动线程。

2.1.2 代码示例

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

2.2 Runnable接口

2.2.1 Runnable基本概念

定义一个线程任务类实现Runnable接口,然后重写run()方法,创建线程任务对象,把线程任务对象包装成线程对象,调用start()方法启动线程。

2.2.2 代码示例(普通方式)

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

2.2.3 代码示例(匿名内部类)

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虚拟机的启动是多线程的,原因是垃圾回收线程要先启动,否则容易出现内存溢出。

2.2.4 Thread和Runnable区别

1) Thread类是Runnable接口的子类,但是Thread类中并且没有完全实现Runnable接口中的run( )方法。

2)如果一个类继承Thread类,不适合于多个线程共享资源,实现Runnable接口,就可以方便地实现资源共享。

2.3 Callable接口

2.3.1 基本概念

2.3.2 原理分析

2.3.3 代码示例(传统方式)

定义一个线程任务类实现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(); } }}

2.3.4 代码示例(jdk1.8)

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

2.3.5 Callable与Runnable区别

  • 是否有返回值, 是否抛异常。
  • 方法不一样,一个是call,一个是run。

3- 线程状态

3.1 线程的生命周期

3.1.1 图示生命周期

3.1.2 线程的状态

1)新建状态 :使用new关键字创建之后进入的状态,此时线程并没有开始执行。

2)就绪状态:调用start方法后进入的状态,此时线程还是没有开始执行。

3) 运行状态:使用线程调度器调用该线程后进入的状态,此时线程开始执行,当线程的时间片执行完毕后任务没有完成时回到就绪状态。

4) 阻塞状态:当线程执行的过程中发生了阻塞事件进入的状态,如:sleep方法。阻塞状态解除后进入就绪状态。

5)死亡状态:线程调用stop( )方法时或者run( )方法执行结束后,即处于死亡状态。处于死亡状态的线程不具有继续运行的能力。

3.1.3 生命周期路线

新建-就绪-运行-死亡新建-就绪-运行-就绪-运行-死亡新建-就绪-运行-就绪-运行-死亡新建-就绪-运行-其他阻塞-就绪-运行-死亡新建-就绪-运行-同步阻塞-就绪-运行-死亡新建-就绪-运行-等待阻塞-同步阻塞-就绪-运行-死亡

3.2 线程操作方法

2.2.1 常用方法API

方法声明 功能介绍
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( ) 将目前正在执行的线程暂停

3.2.2 取得线程名称

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

3.2.3 有参构造器

通过有参数构造器为线程对象取名字

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

3.2.4 强制运行

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) 执行结果

3.2.5 线程休眠

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

4- 线程同步机制

4.1 基本概念

  • 当多个线程同时访问同一种共享资源时,可能会造成数据的覆盖等不一致性问题,此时就需要对线程之间进行通信和协调。
  • 多个线程并发读写同一个临界资源时会发生线程并发安全问题。
  • 异步操作:多线程并发的操作,各自独立运行。同步操作:多线程串行的操作,先后执行的顺序。

4.2 线程安全问题

线程问题的核心原因:多个线程操作同一个共享资源的时候可能出现线程安全问题。

4.2.1代码示例

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) 执行结果

4.2.2 解决方案

1)引发原因

  • 当两个线程同时对同一个账户进行取款时,导致最终的账户余额不合理。
  • 线程一执行取款时还没来得及将取款后的余额写入后台,线程二就已经开始取款。

2)解决方案

  • 让线程一执行完毕取款操作后,再让线程二执行即可,将线程的并发操作改为串行操作。

4.3 线程同步

4.3.1 线程同步的作用

  • 用于解决线程安全问题,线程同步就是指线程安全了。线程同步解决线程安全问题的核心思想。

  • 线程同步就是让多个线程实现先后有序的访问共享资源,每次只能一个线程执行完毕,另一个线程才能进行。

4.3.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) 执行结果

4.3.3 同步方法

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) 执行结果

5- 死锁

5.1 产生死锁

5.1.1 基本概念

死锁:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。

基本结构

线程一执行的代码:public void run(){
synchronized(a){
//持有对象锁a,等待对象锁b synchronized(b){
编写锁定的代码; } }}线程二执行的代码:public void run(){
synchronized(b){
//持有对象锁b,等待对象锁a synchronized(a){
编写锁定的代码; } }}注意:在以后的开发中尽量减少同步的资源,减少同步代码块的嵌套结构的使用!

5.1.2 死锁产生条件

互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用

不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。

请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。

循环等待,即存在一个等待队列:p1要p2的资源,p2要p1的资源。这样就形成了一个等待环路。

5.1.3 代码示例

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

5.1.4 执行结果

5.2 线程通信

5.2.1 Object类常用方法

方法声明 功能介绍
void wait( ) 用于使得线程进入等待状态,直到其它线程调用notify()或notifyAll()方法,此方法必须锁对象调用。
void wait(long timeout) 用于进入等待状态,直到其它线程调用方法或参数指定的毫秒数已经过去为止。
void notify() 唤醒当前锁对象上等待状态的某个线程 此方法必须锁对象调用。
void notifyAll( ) 唤醒当前锁对象上等待状态的全部线程 此方法必须锁对象调用。

5.2.2 生产者及消费者

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)执行结果

5.3 线程同步(Lock锁)

5.3.1 常用的方法

方法声明 功能介绍
ReentrantLock( ) 使用无参方式构造对象
void lock( ) 获取锁
void unlock( ) 释放锁

与synchronized方式的比较

  • Lock是显式锁,需要手动实现开启和关闭操作,而synchronized是隐式锁,执行锁定代码后自动释放。
  • Lock只有同步代码块方式的锁,而synchronized有同步代码块方式和同步方法两种锁。
  • 使用Lock锁方式时,Java虚拟机将花费较少的时间来调度线程,因此性能更好。

5.3.2 代码示例

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) 执行结果

6- 线程池

6.1 线程池概述

6.1.1 什么是线程池

线程池其实就是一个容纳多个线程的容器,其中的线程可以反复使用。省去了频繁创建和销毁线程对象的操作,无需反复创建线程而消耗过多资源。

6.1.2 线程池好处

1.降低资源消耗,减少了创建和销毁线程的次数。每个工作线程都可以被重复利用,可执行多个任务。

2.提高响应速度,线程池的核心思想:线程复用,同一个线程可以被重复使用。

3.提高线程的可管理性(线程池可以约束系统最多只能有多少个线程,不会因为线程过多而死机)

6.2 创建线程池

6.2.1 创建方式一

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():会等全部线程执行完毕才关闭。比较友好!     List
shutdownNow():立即关闭,不管是否执行完毕 */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(); }}

6.2.2 创建方式二

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(); // 关闭线程池 }}

6.2.3 执行结果

转载地址:http://jkhk.baihongyu.com/

你可能感兴趣的文章