仔细查看第二个不安全示例中的线程是如何创建3个类,如何创建构造函数,如何控制你和女友两个线程同时操作结婚基金,以及run方法中对于剩余金额的判断不够直接输出金额不够return退出run方法,金额足够则
IDEA构造方法选中多个参数时alt+insert后选择constructor构造器,然后按住shift才能选择多个参数
线程的停止可以通过设置标志位来使线程满足一定的条件时,进行线程的终止,代码演示如下:
一个对象代理多个线程,代码举例如下:
模拟延时Thread.sleep
每个线程都有其自己的内存空间
不安全的买票问题:排队买票时,并没有排队买,所有线程都将对象的数据拷贝到自己的内存中进行操作,可能使最后总的票数呈现负数,可能有10张票卖出了11张。
package duoxiancheng;
//不安全的买票
//线程不安全,有负数
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTicket station=new BuyTicket();
new Thread(station,"小明").start();
new Thread(station,"小红").start();
new Thread(station,"小白").start();
}
}
class BuyTicket implements Runnable{
//票,定义为私有更安全
private int ticketNums=10;
boolean flag=true;//外部停止方式
@Override
public void run() {
//买票
while(flag){
buy();
}
}
private void buy(){
//判断是否有票
if(ticketNums<=0){
flag=false;
return;//如果没票直接退出buy方法,return直接退出方法体
//买票
}
System.out.println(Thread.currentThread().getName()+"拿到了--->第"+ticketNums--+"张票");
}
}
结果如下图所示:
小红拿到了—>第10张票
小白拿到了—>第8张票
小白拿到了—>第6张票
小白拿到了—>第5张票
小明拿到了—>第9张票
小白拿到了—>第4张票
小白拿到了—>第2张票
小红拿到了—>第7张票
小白拿到了—>第1张票
小明拿到了—>第3张票
Process finished with exit code 0
该过程中可能会出现取票错误,可能两个人(进程)同时取到一张票,Thread中可以用sleep使进程延迟
package duoxiancheng;
//不安全的买票
//线程不安全,有负数
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTicket station=new BuyTicket();
new Thread(station,"小明").start();
new Thread(station,"小红").start();
new Thread(station,"小白").start();
}
}
class BuyTicket implements Runnable{
//票,定义为私有更安全
private int ticketNums=10;
boolean flag=true;//外部停止方式
@Override
public void run() {
//买票
while(flag){
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void buy() throws InterruptedException {
//判断是否有票
if(ticketNums<=0){
flag=false;
return;
//买票
}
Thread.sleep(100);//通过Thread中的sleep方法,使进程延迟
System.out.println(Thread.currentThread().getName()+"拿到了--->第"+ticketNums--+"张票");
}
}
延迟后的进程发生了取票错误,出现了两人抢到同一张票代码如下:
小明拿到了—>第9张票
小红拿到了—>第8张票
小白拿到了—>第10张票
小红拿到了—>第7张票
小明拿到了—>第7张票
小白拿到了—>第6张票
小红拿到了—>第5张票
小明拿到了—>第5张票
小白拿到了—>第5张票
小白拿到了—>第4张票
小红拿到了—>第4张票
小明拿到了—>第4张票
小白拿到了—>第3张票
小明拿到了—>第3张票
小红拿到了—>第3张票
小白拿到了—>第2张票
小明拿到了—>第2张票
小红拿到了—>第1张票
Process finished with exit code 0
以上出现了线程不安全的买票的例子,不排队每个线程都直接将数据放入自己的内存中进行处理,比如每个线程都将9张余票这个数据放入自己的内存,都减1,但实际剩余票数不是8
1.不安全的买票演示
注意Thread后面没有.直接是Thread().start();
package syn;
//不安全的买票
//线程不安全,账户余额可能变成负数
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTicket num=new BuyTicket();
new Thread(num,"我").start();//注意Thread后面没有.
new Thread(num,"你").start();//注意Thread后面没有.
new Thread(num,"他").start();//注意Thread后面没有.
}
}
class BuyTicket implements Runnable{
//设置票数
private int ticketNums=10;
boolean flag=true;//线程外部停止方式
@Override
public void run() {
while(flag){
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void buy() throws InterruptedException {
if(ticketNums<=0){
flag=false;
return;
}
//模拟延时,放大线程同步问题
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);
}
}
代码输出结果如下:
你拿到10
我拿到10
他拿到10
你拿到9
他拿到9
我拿到9
他拿到7
我拿到6
你拿到8
你拿到5
我拿到5
他拿到5
你拿到4
他拿到4
我拿到4
你拿到3
他拿到3
我拿到3
你拿到1
我拿到2
他拿到1
Process finished with exit code 0
2.不安全的银行取钱演示
package syn;
//不安全的取钱
//两个人去银行取钱
public class Unsafebank {
public static void main(String[] args) {
//账户
Account account=new Account(100,"结婚基金");
Drawing you=new Drawing(account,50,"你");
Drawing girlfriend=new Drawing(account,100,"女友");
you.start();//you继承了Thread类调用start方法后执行其下的run方法
girlfriend.start();//girlfriend继承了Thread类调用start方法后执行其下的run方法
}
}
//账户
class Account{
int money;//余额
String name;//卡名
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
//银行:模拟取款
//两个线程在交替执行可能执行到一半去执行另一个线程的一半再回来执行前一个线程的后一半,也可能执行一半后将另一个线程执行完再执行之前的那个线程。
//你和女友两个线程共同对结婚基金进行支出,进入run方法有2种可能,一个是判断钱不够了,直接输出当前进程钱不够取,
// 另一个是钱够了输出当前线程取出钱后账户剩余的金额,以及当前线程取了多少钱
class Drawing extends Thread{
Account account;//账户
//取了多少钱
int drawingMoney;
//现在手里还有多少钱
int nowMoney;
//继承后的构造方法无法直接快捷键生成,需要手动构造。
public Drawing(Account account,int drawingMoney,String name) {
super(name);//调用父类的有参构造将当前线程的名字传进去
this.account = account;
this.drawingMoney = drawingMoney;
}
//取钱通过重写run方法来完成
@Override
public void run() {
//判断有没有钱
if (account.money - drawingMoney < 0) {
System.out.println(Thread.currentThread().getName() + "线程的钱不够,取不了");
return;//return后直接退出run方法,后面的代码都将不在执行
}
//卡内余额=余额-你取的钱
account.money=account.money-drawingMoney;
nowMoney=nowMoney+drawingMoney;
//输出账户余额为多少
System.out.println(account.name+"余额为:"+account.money);
//输出你手里的钱
//注意此处Thread.currentThread().getName()=this.getName();
System.out.println(Thread.currentThread().getName()+"手里的钱"+nowMoney);
}
}
输出的可能结果有以下几种:
女友线程的钱不够,取不了
结婚基金余额为:50
你手里的钱50
Process finished with exit code 0
结婚基金余额为:50
女友线程的钱不够,取不了
你手里的钱50
Process finished with exit code 0
结婚基金余额为:0
你线程的钱不够,取不了
女友手里的钱100
Process finished with exit code 0
等等。。。。。
在加入sleep延时放大线程问题的发生性时,使两个线程都能取到钱
加入延时后代码输出结果为:
结婚基金余额为:-50
结婚基金余额为:-50
女友手里的钱100
你手里的钱50
Process finished with exit code 0
结婚基金余额为:50
结婚基金余额为:50
女友手里的钱100
你手里的钱50
Process finished with exit code 0
结婚基金余额为:50
结婚基金余额为:50
你手里的钱50
女友手里的钱100
Process finished with exit code 0
3.不安全的集合演示
由于可能两个线程可能同一时间抢占了同一个内存空间,两个数组添加到了同一个位置,就将前一个线程覆盖掉了,因此线程就会少,所以输出并不是10000个线程,
package syn;
import java.util.ArrayList;
import java.util.List;
//线程不安全的集合
public class UnsafeList {
public static void main(String[] args) {
List<String> list=new ArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
System.out.println(Thread.currentThread().getName());
}).start();
}
System.out.println(list.size());
}
}
输出结果如下:
Thread-0
Thread-5
Thread-4
Thread-2
Thread-1
Thread-6
Thread-3
Thread-7
Thread-9
Thread-8
Thread-10
Thread-11
Thread-12
Thread-13
…
Thread-9979
9975
Thread-9981
Thread-9983
Thread-9984
Thread-9985
Thread-9967
Thread-9986
Thread-9972
Thread-9990
Thread-9987
Thread-9988
Thread-9991
Thread-9993
Thread-9994
Thread-9977
Thread-9998
Thread-9980
Thread-9999
Thread-9970
Thread-9992
Thread-9995
Thread-9997
Process finished with exit code 0
注意:
1,在java中int类型的变量默认赋值为0,string类型的变量默认赋值为null
2,IDEA构造方法选中多个参数时alt+insert后选择constructor构造器,然后按住shift才能选择多个参数