Java多线程,程序运行的堆栈分析
**进程:**一个应用程序,进程与进程之间不能进行数据共享。一个程序可以启动多个线程。
**线程:**一个进程中的执行场景/执行单元,线程与线程之前可以进行数据共享。
进程与线程的关系
进程和线程之间的关系就好比餐厅和员工。一个餐厅A有多个员工,员工与员工之间可以共享这个餐厅A的资源。餐厅B是另一个餐厅,餐厅A和餐厅B之间不能进行数据共享。
QQ音乐,和穿越火线是两个进程,他们是独立的,不共享资源。
Java中的进程与线程:
对Java程序而言,但我们使用 java HellWorld运行Java文件时,会先启动JVM虚拟机,JVM就是一个进程,JVM再启动一个主线程调用main方法,同时启动一个垃圾回收线程负责看护,回收垃圾。
一个Java程序至少有两个线程并发,一个是垃圾回收线程,一个是执行main方法的主线程。
J**ava程序中,线程A和线程B,堆内存和方法区内存共享,但是栈内存独立,一个线程一个栈。**如果有10个线程,就会有十个栈,每个栈之间互不干扰,各自执行各自的,这就是多线程并发。
火车站的卖票窗口就是一个多线程,你可以在任何一个窗口买票,在窗口A买票并不影响在窗口B买票的乘客。火车票是他们的共享资源。
Java引入多线程,主要是为了提高程序的处理效率。
Java程序中,线程A和线程B,堆内存和方法区内存共享,但是栈内存独立,一个线程一个栈。如果有10个线程,就会有十个栈,每个栈之间互不干扰,各自执行各自的。
**问题:**使用了多线程后,main方法结束,程序是否就一定结束?
main方法结束只是主线程结束,主栈空了,但是其他的栈(线程)可能还在运行
单核CPU:
不能做到多线程并发,但是可以给人一种多线程并发的错觉。单核cpu在一个时间点上只能处理一件事情但是由于CPU处理极快,多个线程之间频繁切换,给人的感觉是多个事情同时在做。
线程A:播放音乐,线程B:运行魔兽游戏。
线程A与线程B的频繁切换执行,给人一种音乐和游戏同时运行,给我们并发的错觉。
多核CPU
多核CPU在同一个时间节点上,可以真正的多个进行并发执行。
多线程并发
线程a执行线程a的,线程b执行线程b的,二者不会相互影响。
创建线程的方式
启动线程
调用线程的.start()方法
public class ThreadTest01 {
public static void main(String[] args) {
MyThread01 myThread = new MyThread01();
myThread.start();
for (int i = 0; i <1000 ; i++) {
System.out.println("主线程------"+i);
}
}
}
class MyThread01 extends Thread{
//调用start方法后,JVM会默认调用run方法
public void run(){
for (int i = 0; i <1000 ; i++) {
System.out.println("子线程------"+i);
}
}
}
public class ThreadTest02 {
public static void main(String[] args) {
MyThread02 myThread = new MyThread02();
Thread thread = new Thread(myThread);
thread.start();
for (int i = 0; i < 100; i++) {
System.out.println("主线程------" + i);
}
}
}
class MyThread02 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("子线程------" + i);
}
}
}
public class ThreadTest03 {
public static void main(String[] args) {
//写法一:
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("子线程1------" + i);
}
}
}).start();
//写法二:
new Thread(()->{
for (int i = 0; i < 100; i++) {
System.out.println("子线程2------" + i);
}
}).start();
for (int i = 0; i < 100; i++) {
System.out.println("主线程------" + i);
}
}
}
Runnable接口是一个函数式接口,因此我们可以使用Lambda表达式。
@FunctionalInterface
public interface Runnable {public abstract void run();}
课堂练习
public class ThreadTest01 {
public static void main(String[] args) {
//创建子线程对象 方式一:继承Thread
Thread myThread01 = new MyThread01();
myThread01.start();
//创建子线程对象 方式二:实现Runnable接口
MyThread02 myThread = new MyThread02();
Thread myThread02 = new Thread(myThread);
myThread02.start();
//创建子线程对象 方式三:通过匿名内部类创建
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("子线程03" + "------" + i);
}
}
}).start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
System.out.println("子线程04" + "------" + i);
}
}).start();
//主线程
for (int i = 0; i < 10; i++) {
System.out.println("主线程" + "------" + i);
}
}
}
class MyThread01 extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("子线程01" + "------" + i);
}
}
}
class MyThread02 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("子线程02" + "------" + i);
}
}
}
public class ThreadTest01 {
public static void main(String[] args) {
MyThread01 myThread = new MyThread01();
myThread.start();
for (int i = 0; i <1000 ; i++) {
System.out.println("主线程------"+i);
}
}
}
class MyThread01 extends Thread{
//调用start方法后,JVM会默认调用run方法
public void run(){
for (int i = 0; i <100 ; i++) {
System.out.println("子线程------"+i);
}
}
}
结果:主线程和子线程交替输出
问题:为什么主线程和子线程是交替输出的?
运行逻辑图
public class ThreadTest01 {
public static void main(String[] args) {
MyThread01 myThread = new MyThread01();
myThread.run();
for (int i = 0; i <100 ; i++) {
System.out.println("主线程------"+i);
}
}
}
class MyThread01 extends Thread{
//调用start方法后,JVM会默认调用run方法
public void run(){
for (int i = 0; i <100 ; i++) {
System.out.println("子线程------"+i);
}
}
}
运行结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HORWD6gp-1639296073442)(C:\Users\DELL\AppData\Roaming\Typora\typora-user-images\image-20211204210031229.png)]
运行逻辑图
线程的生命周期分为5个状态:
主线程的默认名称为main.
子线程的默认名称为Thread-0,随着子线程的增加,后面的编号也随之增加
public class ThreadTest02 {
public static void main(String[] args) {
System.out.println("主线程:" + Thread.currentThread().getName() + "开始------------");
//获取当前线程名称
Thread.currentThread().setName("main1");
String name = Thread.currentThread().getName();
System.out.println("主线程:" + name);
Thread thread = new Thread(new MyThread02());
System.out.println("子线程"+thread.getName());
thread.start();
System.out.println("主线程:" + Thread.currentThread().getName() + "结束------------");
}
}
class MyThread02 implements Runnable {
@Override
public void run() {
System.out.println("子线程:" + Thread.currentThread() + "开始------------");
Thread.currentThread().setName("子线程01");
System.out.println("子线程:" + Thread.currentThread() + "结束------------");
}
}
使用sleep来阻塞线程,此时线程的时间片会被CPU回收,当阻塞结束之后,线程会重新向CPU请求资源。
扩展:我们可以通过阻塞线程,来实现一些定时任务,比如整点的时候,我们向用户推送信息。或者实现倒计时功能。
注意:sleep是静态方法,Thread在哪里写,表示的是当前的线程对象。在main方法中,表示的是main,在子线程的run方法中写,表示的是子线程。
public class ThreadTest04 {
public static void main(String[] args) {
//创建子线程
Thread thread = new Thread(new MyThreadTest04());
thread.start();
try {
System.out.println("主线程:"+Thread.currentThread().getName()+"开始休眠");
//此处虽然调用了thread子线程的sleep方法,但是sleep是静态方法,所以休眠的还是当前线程main
thread.sleep(5000);
System.out.println("主线程:"+Thread.currentThread().getName()+"休眠结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class MyThreadTest04 implements Runnable {
@Override
public void run() {
try {
System.out.println("子线程" + Thread.currentThread().getName() + "开始休眠");
//当前线程休眠5s
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子线程" + Thread.currentThread().getName() + "休眠结束");
}
}
线程睡眠后,在睡眠中时,可以通过interrupt中断线程睡眠,让线程直接进入就绪状态.
interrupt会通过产生异常的方式来中断线程睡眠。
public class ThreadTest04 {
public static void main(String[] args) {
//创建子线程
Thread thread = new Thread(new MyThreadTest04());
thread.start();
try {
System.out.println("主线程:" + Thread.currentThread().getName() + "开始休眠");
//此处虽然调用了thread子线程的sleep方法,但是sleep是静态方法,所以休眠的还是当前线程main
thread.sleep(5000);
System.out.println("主线程:" + Thread.currentThread().getName() + "休眠结束");
System.out.println("中断子线程睡眠");
thread.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class MyThreadTest04 implements Runnable {
@Override
public void run() {
try {
System.out.println("子线程" + Thread.currentThread().getName() + "开始休眠");
//当前线程休眠5s
Thread.sleep(50000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子线程" + Thread.currentThread().getName() + "休眠结束");
}
}
stop相当于直接结束程序,如果某些数据在内存中,需要保存,很可能没有保存就退出程序造成数据丢失。
public class ThreadTest04 {
public static void main(String[] args) {
//创建子线程
Thread thread = new Thread(new MyThreadTest04());
thread.start();
try {
System.out.println("主线程:" + Thread.currentThread().getName() + "开始休眠");
//此处虽然调用了thread子线程的sleep方法,但是sleep是静态方法,所以休眠的还是当前线程main
thread.sleep(5000);
thread.stop();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class MyThreadTest04 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
try {
System.out.println(i+"子线程" + Thread.currentThread().getName() + "开始休眠");
//当前线程休眠5s
Thread.sleep(2000);
System.out.println(i+"子线程" + Thread.currentThread().getName() + "休眠结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
在结束直接,可以进行一些业务处理,比如保存数据。
public class ThreadTest04 {
public static void main(String[] args) {
//创建子线程
MyThreadTest04 threadChild = new MyThreadTest04();
Thread thread = new Thread(threadChild);
thread.start();
try {
System.out.println("主线程:" + Thread.currentThread().getName() + "开始休眠");
//此处虽然调用了thread子线程的sleep方法,但是sleep是静态方法,所以休眠的还是当前线程main
thread.sleep(5000);
threadChild.flag=false;
System.out.println("结束子线程");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class MyThreadTest04 implements Runnable {
boolean flag = true;
@Override
public void run() {
for (int i = 0; i < 5; i++) {
try {
if (flag) {
System.out.println(i + "子线程" + Thread.currentThread().getName() + "开始休眠");
//当前线程休眠5s
Thread.sleep(2000);
System.out.println(i + "子线程" + Thread.currentThread().getName() + "休眠结束");
}else {
System.out.println("子线程被外界终止,开始保存信息");
return;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
常见的线程调度模型:
优先级高的线程获取CPU时间片可能会多一些(不完全是,大概率是)。指线程运行的时间相对多一些。
public class ThreadTest05 {
public static void main(String[] args) {
System.out.println("最高优先级" + Thread.MAX_PRIORITY);
System.out.println("最低优先级" + Thread.MIN_PRIORITY);
System.out.println("默认优先级" + Thread.NORM_PRIORITY);
//获取主线程的优先级
Thread currentThread = Thread.currentThread();
System.out.println(currentThread.getName() + "线程的默认优先级是:" + currentThread.getPriority());
//创建子线程
MyThread05 myThread05 = new MyThread05();
Thread childThread = new Thread(myThread05);
childThread.start();
//修改子线程的优先级
childThread.setPriority(10);
for (int i = 0; i <100 ; i++) {
System.out.println(Thread.currentThread().getName()+"----"+i);
}
}
}
class MyThread05 implements Runnable {
@Override
public void run() {
Thread currentThread = Thread.currentThread();
System.out.println(currentThread.getName() + "线程的默认优先级是:" + currentThread.getPriority());
for (int i = 0; i <100 ; i++) {
System.out.println(Thread.currentThread().getName()+"----"+i);
}
}
}
使用Thread.yield()方法,暂停当前执行的线程对象,并执行其他线程。让当前运行的线程从运行状态返回到就绪状态。
public class ThreadTest05 {
public static void main(String[] args) {
//创建子线程
MyThread05 myThread05 = new MyThread05();
Thread childThread = new Thread(myThread05);
childThread.start();
for (int i = 0; i <100 ; i++) {
System.out.println(Thread.currentThread().getName()+"----"+i);
}
}
}
class MyThread05 implements Runnable {
@Override
public void run() {
Thread currentThread = Thread.currentThread();
//对5取余,看是否实现线程让位
for (int i = 0; i <100 ; i++) {
if (i%5==0){
Thread.yield();
System.out.println(Thread.currentThread().getName()+"----"+i);
}
}
}
}
使用join( )方法,在线程A中调用线程B的join方法,此时线程A进入阻塞状态,并释放CPU资源,当线程B内的方法执行完毕后,线程A的阻塞停止,进入就绪状态。
public class ThreadTest07 {
public static void main(String[] args) {
//创建子线程
MyThread07 myThread07 = new MyThread07();
Thread childThread = new Thread(myThread07);
childThread.start();
try {
//合并线程,合并后childThread执行完才会执行主线程
childThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "----" + i);
}
}
}
class MyThread07 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 5 == 0) {
System.out.println(Thread.currentThread().getName() + "----" + i);
}
}
}
}
在开发中,我们基本不同过手动创建线程的方式来实现多线程,我们的项目是部署在服务器上的,服务器已经将线程的定义,线程对象的创建,线程的启动等都已经实现,因此我们基本不需要手动创建线程。
也就是说,我们的项目是运行在一个多线程的环境下的,我们需要关注的是,在多线程条件下,我们的数据会不会出现问题,数据是否安全。
产生数据安全问题的条件
满足上面三个条件后,就会存在线程安全问题。如在银行进行取钱操作
解决方式
如果要解决多线程并发的安全问题,我们需要使用线程同步(这里同步指的是线程排队,数据同步。而非线程同时获取数据。)
同步编程模型
线程1和线程2,在线程1执行的时候,必须等待线程2执行结束,反之同理。两个线程之间发生了等待关系,即同步编程模型。
优点:数据同步。缺点:效率低。
异步编程模型
线程1和线程2各自执行各自的,互不影响,谁也不需要等待谁即异步编程模型。
优点:效率高。缺点:发生数据共享且修改数据时,大概率造成数据不同步。
模拟银行取钱
使用两个线程共享一个账户,来模拟取钱操作。
//账户类
class Account {
//账号
private String actno;
//余额
private double balance;
public Account(String actno, double balance) {
this.actno = actno;
this.balance = balance;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
public void withDraw(double money) {
double before = this.getBalance();
double after = before - money;
//如果一条线程完成了after操作,但还没来的及更新,另一条线程又进来了,此时就会产生问题。
//模拟网络延迟
try {
System.out.println(Thread.currentThread().getName()+"取钱中");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBalance(after);
System.out.println(Thread.currentThread().getName() + "取了"+money+"剩余余额" + after);
}
}
//用户类
class Person implements Runnable {
Account account;
public Person(Account account) {
this.account = account;
}
@Override
public void run() {
account.withDraw(500);
}
}
//银行
public class BankSystem {
public static void main(String[] args) {
//账户account存储在堆中,thread1和thread2二者为两个线程栈,共享account这一个对象。
Account account = new Account("actno_1", 1000);
Thread thread1 = new Thread(new Person(account));
thread1.setName("张三");
Thread thread2 = new Thread(new Person(account));
thread2.setName("张三的妻子");
thread1.start();
thread2.start();
}
}
张三取钱等待5s,张三妻子取钱,可以直接取,这样可以实现数据一致。
弊端:
改造后的run方法
@Override
public void run() {
if (Thread.currentThread().getName().equals("张三")) {
System.out.println(Thread.currentThread().getName() + "开始取钱");
try {
Thread.sleep(10000);//张三取钱,睡眠10s
} catch (InterruptedException e) {
e.printStackTrace();
}
account.withDraw(500);
} else {
System.out.println(Thread.currentThread().getName() + "开始取钱");
account.withDraw(500);
}
}
synchronized(线程的共享对象){
//要同步进行的内容
}
线程的共享对象:
这个是多个线程的共享的数据,这样才能达到多线程排队。
如果我们又1,2,3,4,5个线程,如果1,2,3需要排队,我们就需要在里面写1,2,3的共享对象,这个对象对于4,5来说是不共享的。
加锁相当于进行了阻塞。
执行原理
改编银行取钱操作,实现同步
public void withDraw(double money) {
//为当前操作加锁
synchronized (this) { //这里的this,指的是调用当前方法的account对象。
double before = this.getBalance();
double after = before - money;
try {
System.out.println(Thread.currentThread().getName() + "取钱中");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBalance(after);
System.out.println(Thread.currentThread().getName() + "取了" + money + "剩余余额" + after);
}
}
虽然我们实现了加锁,但是系统的执行效率也大大降低。
注意:线程同步块越小,效率越高
我们也可以在调用withDraw方法的地方添加synchronized,只是效率会降低。
class Person implements Runnable {
Account account;
public Person(Account account) { this.account = account; }
@Override
public void run() {
synchronized (account){ //共享对象是当前账户
account.withDraw(500);
}
}
}
改变共享对象
使用account的成员变量和字符串对象
创建第三个线程李四,单独一个账户
public static void main(String[] args) {
Account account = new Account("actno_1", 1000);
Thread thread1 = new Thread(new Person(account));
thread1.setName("张三");
Thread thread2 = new Thread(new Person(account));
thread2.setName("张三的妻子");
thread1.start();
thread2.start();
//创建李四,单独一个账户
Account account2 = new Account("actno_2",5000);
Thread thread3 = new Thread(new Person(account2));
thread3.setName("李四");
thread3.start();
}
成员变量作为共享对象
//object是Account类的一个成员变量
Object object = new Object();
public void withDraw(double money) {
//为当前操作加锁
synchronized (object) {
double before = this.getBalance();
double after = before - money;
try {
System.out.println(Thread.currentThread().getName() + "取钱中");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBalance(after);
System.out.println(Thread.currentThread().getName() + "取了" + money + "剩余余额" + after);
}
}
结果:张三或者张三妻子的其中一人,和李四同时进行取钱。
张三和张三妻子共享一个对象,排序取。他们和李四不共享一个对象,可以其中一人和李四同时取。
字符串作为共享对象
三个人共享一个“object”字符串共享对象
public void withDraw(double money) {
//为当前操作加锁
synchronized ("object") {
double before = this.getBalance();
double after = before - money;
try {
System.out.println(Thread.currentThread().getName() + "取钱中");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBalance(after);
System.out.println(Thread.currentThread().getName() + "取了" + money + "剩余余额" + after);
}
}
结果:张三或者张三妻子或者李四排队取钱。
即在实例方法或者静态方法上添加synchronized。表示整个方法体都需要同步。
缺点:扩大同步范围,效率降低,不常用。
优点:精简代码。
对象锁
类锁
//为当前方法加锁
public synchronized void withDraw(double money) {
double before = this.getBalance();
double after = before - money;
try {
System.out.println(Thread.currentThread().getName() + "取钱中");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBalance(after);
System.out.println(Thread.currentThread().getName() + "取了" + money + "剩余余额" + after);
}
变量类型:
堆和方法区中的变量共享,每个线程一个栈,栈中的变量不会发生共享。
实例变量和静态变量是多线程共享的,局部变量不可能发生共享。
StringBuilder和StringBuffer
StringBuilder:线程不同步
StringBuffer:线程同步
使用字符串局部变量时,使用StringBuilder效率更高。因为局部变量没有线程安全问题。
如果使用StringBuffer,每次调用append方法,程序都会去锁池中寻找对应的锁,多了一个寻锁步骤,影响效率。
StringBuffer源码:
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
张三先取钱,张三妻子接着存钱
main方法
public class BankSystem {
public static void main(String[] args) {
Account account = new Account("actno_1", 1000);
Thread thread1 = new Thread(new Person(account));
thread1.setName("张三");
thread1.start();
//让线程张三先取钱
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread thread2 = new Thread(new Person(account));
thread2.setName("张三的妻子");
thread2.start();
}
}
class Person implements Runnable {
Account account;
public Person(Account account) {
this.account = account;
}
@Override
public void run() {
if (Thread.currentThread().getName().equals("张三")) {
account.withDraw(500);
} else account.addDraw(500);
}
}
存钱取钱操作
//存钱
public void withDraw(double money) {
double before = this.getBalance();
double after = before - money;
try {
System.out.println(Thread.currentThread().getName() + "取钱中");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBalance(after);
System.out.println(Thread.currentThread().getName() + "取了" + money + "剩余余额" + after);
}
//取钱
public void addDraw(double money) {
double before = this.getBalance();
double after = before + money;
try {
System.out.println(Thread.currentThread().getName() + "存钱中");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBalance(after);
System.out.println(Thread.currentThread().getName() + "存了" + money + "剩余余额" + after);
}
结果:张三取钱的同时,他妻子可以同时存钱。
结果:张三取钱的同时,他妻子可以同时存钱,但是不能同时取钱。
结果:张三取钱的同时,他妻子不能同时存钱
注意:虽然我们是将两个方法加了锁,但是这两个方法使用的共享对象都是当前对象this,所以说他们使用的是一个共享对象,只有一个锁。
//取钱操作
public synchronized void withDraw(double money) {
double before = this.getBalance();//0
double after = before - money;//-1000
try {
System.out.println(Thread.currentThread().getName() + "取钱中");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBalance(after);
System.out.println(Thread.currentThread().getName() + "取了" + money + "剩余余额" + this.balance);
}
//存钱操作
public synchronized void addDraw(double money) {
double before = this.balance;
double after = before + money;//1000
try {
System.out.println(Thread.currentThread().getName() + "存钱中");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBalance(after);//1000
System.out.println(Thread.currentThread().getName() + "存了" + money + "剩余余额" + this.balance);
}
结果:张三取钱的同时,他妻子不能同时存钱
//取钱操作
public synchronized static void withDraw(double money) {
double before = Account.balance;//0
double after = before - money;//-1000
try {
System.out.println(Thread.currentThread().getName() + "取钱中");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Account.balance=after;
System.out.println(Thread.currentThread().getName() + "取了" + money + "剩余余额" + Account.balance);
}
//存钱操作
public synchronized static void addDraw(double money) {
double before =Account.balance;
double after = before + money;//1000
try {
System.out.println(Thread.currentThread().getName() + "存钱中");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Account.balance=after;
System.out.println(Thread.currentThread().getName() + "存了" + money + "剩余余额" + Account.balance);
}
synchronized属于排它锁,一次只能有一个线程获取锁操作相关数据。
synchronized会让程序效率降低,用户体验不好,系统的用户吞吐量降级,用户体验差(原则上,一个接口让用户的等待时间最多不能超过5s)
线程1操作方法1时,需要获取锁o1,操作之后执行方法2,需要锁o2。线程2与之同理。
此时,线程1调用完方法1(o1)还没有释放,去调用方法二,而此时线程2刚执行完方法二,准备去执行方法一,但是o2没有释放。
此时,线程1获取不到锁o2,并且没有释放锁o1,而线程2获取不到锁o1,并且没有释放锁o2,则出现死锁。
出现死锁时,程序不会中断,且不容易排查,就停在那里等待。
案例分析
张三先进行存钱操作,再进行取钱操作。
张三的妻子先进行取钱操作,再进行存钱操作。
存钱操作和取钱操作使用不同的对象进行加锁。
class Account{
//账号
private String actno;
//余额
private double balance;
//表示操作:save 表示存储,take表示拿
Object save;
Object take;
public Account(String actno, double balance,Object save,Object take) {
this.actno = actno;
this.balance = balance;
this.save=save;
this.take=take;
}
//存钱
public void withDraw(double money) {
double before = this.getBalance();
double after = before - money;
this.setBalance(after);
System.out.println(Thread.currentThread().getName() + "取了" + money + "剩余余额" + after);
}
//取钱
public void addDraw(double money) {
double before = this.getBalance();
double after = before + money;
this.setBalance(after);
System.out.println(Thread.currentThread().getName() + "存了" + money + "剩余余额" + after);
}
}
class Person implements Runnable {
Account account;
public Person(Account account) {
this.account = account;
}
@Override
public void run() {
if (Thread.currentThread().getName().equals("张三")) {
System.out.println(account);
synchronized (account.save) {
account.addDraw(500);
try {
//阻塞线程
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (account.take) {
account.withDraw(500);
}
}
} else {
synchronized (account.take) {
try {
//阻塞线程
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.addDraw(500);
synchronized (account.save) {
account.withDraw(500);
}
}
}
}
}
public class BankSystem {
public static void main(String[] args) {
Account account = new Account("actno_1", 1000, new Object(), new Object());
Thread thread1 = new Thread(new Person(account));
thread1.setName("张三");
thread1.start();
Thread thread2 = new Thread(new Person(account));
thread2.setName("张三的妻子");
thread2.start();
}
}
程序永远地停在了这里。
Java中的线程分类:
案例
保安作为银行的守护者,当有顾客时,需要时刻关注周围的环境。当顾客离开后,则可以下班。
当不使用 thread3.setDaemon(true);的时候,说明该线程是一个用户线程,因为该线程是一个死循环,所以即使其他用户线程执行结束,该线程还在运行,程序并不会中断。
当时thread3.setDaemon(true);的时候,说明该线程是一个守护线程,当用户线程执行完,该线程完成使命,自动退出,程序结束。
线程
public static void main(String[] args) {
Account account = new Account("actno_1", 1000, new Object(), new Object());
Thread thread1 = new Thread(new Person(account));
thread1.setName("张三");
thread1.start();
Thread thread2 = new Thread(new Person(account));
thread2.setName("张三的妻子");
thread2.start();
Thread thread3 = new Thread(new Safer());
thread3.setName("保安");
thread3.setDaemon(true);
thread3.start();
}
保安类
class Safer implements Runnable {
@Override
public void run() {
while (true) {
System.out.println(Thread.currentThread().getName() + "查看门口状况");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
不开启守护线程:
即使用户取钱完毕,保安还是会每个5s查看状况。
开启守护线程:
用户取钱完毕,保安也不干了。
作用: 间隔特定的时间,执行特定的程序。如:每周要进行银行账户的总账操作,每天要进行数据的备份操作。
定时器方式实现 :
可以使用sleep方法,睡眠,设置睡眠时间,没到这个时间点醒来,执行任务。这种方式是最原始的定时器。(比较low)
使用定时器 java.util.Timer。不过,实际开发中也很少用,因为现在有很多高级框架都是支持定时任务的。
在实际的开发中,目前使用较多的是Spring框架中提供的springTask或者Quartz 框架。
使用Timer实现:
构造方法:
Timer() :创建一个新的计时器。
Timer(boolean isDaemon) :创建一个新的定时器的线程可以被指定 run as a daemon。(守护线程)
Timer(String name) :创建一个新的计时器,该计时器的关联线程具有指定的名称。
Timer(String name, boolean isDaemon) :创建一个新的定时器的相关线程指定名称,可以指定(守护线程) 。
常用方法:
void cancel() :终止此计时器,丢弃任何当前计划的任务。
int purge() :从这个计时器的任务队列中移除所有已取消的任务。
void schedule(TimerTask task, Date time) :在指定的时间计划执行指定的任务。
void schedule(TimerTask task, Date firstTime, long period): 计划重复固定延迟执行指定的任务,开始在指定的时间。
void schedule(TimerTask task, long delay) :指定在指定的延迟后执行指定的任务的时间
void schedule(TimerTask task, long delay, long period): 计划重复固定延迟执行指定的任务,在指定的延迟后开始。
void scheduleAtFixedRate(TimerTask task, Date firstTime, long period) :计划重复固定利率执行指定的任务,开始在指定的时间。
void scheduleAtFixedRate(TimerTask task, long delay, long period): 计划重复固定利率执行指定的任务,在指定的延迟后开始。
案例:
通过定时任务实现保安每隔10s进行情况查看
线程
public static void main(String[] args) {
Account account = new Account("actno_1", 1000, new Object(), new Object());
Thread thread1 = new Thread(new Person(account));
thread1.setName("张三");
thread1.start();
Thread thread2 = new Thread(new Person(account));
thread2.setName("张三的妻子");
thread2.start();
Timer time = new Timer("保安定时查看任务");
time.schedule(new Safer(), new Date(), 1000 * 10L);
}
附:匿名内部类写法
Timer time = new Timer("保安定时查看任务");
// time.schedule(new Safer(), new Date(), 1000 * 10L);
time.schedule(new TimerTask() {
@Override
public void run() {
System.out.println(new Date() + Thread.currentThread().getName() + "查看门口状况");
}
},new Date(), 1000 * 10L);
保安
class Safer extends TimerTask {
@Override
public void run() {
System.out.println(new Date() + Thread.currentThread().getName() + "查看门口状况");
}
}
实现callable接口(jdk8的新特性JUC包下)这种方式实现的线程可以获取线程的返回值。
继承Thead和实现Runnable接口的方式是无法获取线程返回值的,因为run方法返回void。
应用场景:
系统委派一个线程去执行一个任务,该线程执行完任务之后,可能会有一个执行结果,需要拿到这个结果。
**优点:**可以获取线程的执行结果
**缺点:**效率比较低,获取线程结果的时候,当前线程受阻塞,效率较低
Callable接口与Runnable接口的区别
我们可以通过FutureTask来进行相应的线程创建,FutureTask就是一个线程,并且实现了Runnable接口。
常用方法:
FutureTask(Callable callable) :创建一个 FutureTask会在运行,执行给定的 Callable。
FutureTask(Runnable runnable, V result) :创建一个 FutureTask会在运行,执行给定的 Runnable,并安排 get将给定的成功完成的结果返回。
实现方式:
案例
public class ThreadTest06 {
public static void main(String[] args) {
FutureTask futureTask = new FutureTask(() -> {
System.out.println("数据开始统计");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("数据开始完毕");
return 100;
});
Thread task = new Thread(futureTask);
task.start();
for (int i = 0; i < 10; i++) {
if (i== 5) {
try {
Integer result = (Integer)futureTask.get();
System.out.println("数据统计结果"+result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "正在执行" + i);
}
}
}
public class FutrueTaskThead {
public static void main(String[] args) {
FutureTask futureTask = new FutureTask(new Callable() {
int a = 0;
@Override
public Object call() throws Exception {
int a = 0;
for (int i = 0; i < 10; i++) {
System.out.println("10086套餐了解一下");
a++;
}
return a;
}
});
Thread thread = new Thread(futureTask);
thread.start();
try {
Object o = futureTask.get();
System.out.println(o);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
lambda写法
FutureTask futureTask = new FutureTask(() -> {
int a = 0;
for (int i = 0; i < 10; i++) {
System.out.println("10086套餐了解一下");
a++;
}
return a;
});
方法介绍
方法作用:
1、使用wait方法和notify方法实现“生产者和消费者模式”
生产线程负责生产,消费线程负责消费。生产线程和消费线程要达到均衡。
生产者和消费者模式一种特殊的业务需求,在这种特殊的情况下需要使用wait方法和notify方法。
需求:生产者生产苹果,消费者消费苹果。仓库一次只能存一个苹果。
public class ThreadTest08 {
public static void main(String[] args) {
List<Apple> appleList = new ArrayList<>();
Thread consumer = new Thread(new Consumer(appleList));
consumer.setName("消费者");
consumer.start();
Thread producer = new Thread(new Producer(appleList));
producer.setName("生产者");
producer.start();
}
}
class Apple {}
class Consumer implements Runnable {
List<Apple> appleList;
public Consumer(List<Apple> appleList) {
this.appleList = appleList;
}
@Override
public void run() {
while (true) {
synchronized (appleList) {
if (appleList.size() == 0) {
try {
appleList.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Apple apple = appleList.remove(0);
System.out.println(Thread.currentThread().getName() + "---消费了一个苹果" + apple);
appleList.notify();
}
}
}
}
class Producer implements Runnable {
List<Apple> appleList;
public Producer(List<Apple> appleList) {
this.appleList = appleList;
}
@Override
public void run() {
while (true) {
synchronized (appleList) {
if (appleList.size() > 0) {
try {
appleList.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Apple apple = new Apple();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
appleList.add(apple);
System.out.println(Thread.currentThread().getName() + "---生成了一个苹果" + apple);
appleList.notify();
}
}
}
}
public class BankSystem {
public static void main(String[] args) {
Account account = new Account("actno_1", 1000, new Object(), new Object());
Thread thread1 = new Thread(new Person(account));
thread1.setName("张三");
thread1.start();
Thread thread2 = new Thread(new Person(account));
thread2.setName("张三的妻子");
thread2.start();
Thread thread3 = new Thread(new Person(account));
thread3.setName("张三的儿子");
thread3.start();
}
}
class Person implements Runnable {
Account account;
public Person(Account account) {
this.account = account;
}
@Override
public void run() {
while (true) {
synchronized (account) {
if (Thread.currentThread().getName().equals("张三")) {
account.addDraw(500);
} else {
account.withDraw(500);
}
}
}
}
}
class Account {
//账号
private String actno;
//余额
private double balance;
public Account(String actno, double balance) {
this.actno = actno;
this.balance = balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
//取钱
public void withDraw(double money) {
double after = this.balance - money;
if (after < 500) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
this.setBalance(this.balance - money);
this.notify();
System.out.println(Thread.currentThread().getName() + "qu了" + money + "剩余余额" + this.balance);
}
}
//存钱
public void addDraw(double money) {
double after = this.balance + money;
if (after > 2000) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
this.setBalance(this.balance + money);
System.out.println(Thread.currentThread().getName() + "存了" + money + "剩余余额" + this.balance);
this.notify();
}
}
}