做以下练习的基本步骤:
- 定义共享变量(若要创建多个对象使用静态);
- 写循环(while)
- 有时候不写while,比如说抢红包每条线程只能抢一次,就不用while循环
- 同步代码块
- 判断(已经到末尾)
- 再判断(没有到末尾)
重点1:要知道共享变量是定义在javaBean内,还是定义在main方法中(创建对象时传递过来)
重点2:要清楚while循环的结束条件是什么,
1.
SelTickets类:
public class SellTickets extends Thread{
private static int tickets=1;
@Override
public void run() {
//1循环
while(true){
//2同步代码块确保线程安全
synchronized (SellTickets.class){
//3判断
if (tickets==1000){
break;
}else {
//让当前线程睡30毫秒
try {
Thread.sleep(30);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName()+":正在卖第"+tickets+"张票");
tickets++;
}
}
}
}
}
main:
public class Test {
public static void main(String[] args) {
//创建两个线程
SellTickets s1=new SellTickets();
SellTickets s2=new SellTickets();
//设名字
s1.setName("窗口一");
s2.setName("窗口二");
//开始
s1.start();
s2.start();
}
}
2.
public class MyRun implements Runnable{
//第二种方式实现多线程,测试类中MyRunable只创建一次,所以不需要加static
private int gift=100;
@Override
public void run() {
//1.循环
while(true){
//2.同步代码块确保线程安全
synchronized (MyRun.class){
//让当线程小睡一会
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//3.判断
if (gift<10){
break;
}else {
gift--;
System.out.println(Thread.currentThread().getName()+"正在送,还剩:"+gift+"份");
}
}
}
}
}
main
public class Test {
public static void main(String[] args) {
//线程任务
MyRun m=new MyRun();
//创建线程
Thread t1=new Thread(m);
Thread t2=new Thread(m);
//设置名字
t1.setName("小王");
t2.setName("小李");
//开始
t1.start();
t2.start();
}
}
3
使用第三种方法
public class MyRunable implements Runnable {
//第二种方式实现多线程,测试类中MyRunable只创建一次,所以不需要加static
private int number = 1;
@Override
public void run() {
//循环
while (true) {
//同步代码块确保线程安全
synchronized (MyCall.class) {
//判断
if (number <= 100) {
if (number % 2 == 1) {
System.out.println(Thread.currentThread().getName() + "打印数字" + number);
}
number++;
} else {
break;
}
}
}
}
}
main
public class Test {
public static void main(String[] args) {
MyRunable mc=new MyRunable();
//创建线程传入mc参数
Thread t1=new Thread(mc);
Thread t2=new Thread(mc);
t1.setName("线程一");
t2.setName("线程二");
t1.start();
t2.start();
}
}
4.
.
改进前:
public class Grab extends Thread {
//使用第一种方法,可能创建多个线程使用静态修饰
private static double money = 100;
//红包个数
static int count = 3;
//最小的中奖金额
static final double MIN = 0.01;
@Override
public void run() {
//循环舍弃:抢红包一人只能抢一次
//同步代码块保证线程安全
synchronized (Grab.class) {
if (count == 0) {
//如果没红包了就说明没抢到
System.out.println(getName() + "没抢到红包");
} else {
//还有红包
//定义一个变量表示中奖的金额
double prize = 0;
if (count == 1) {
//表示是最后一个红包,抽取剩下的钱即可
prize = money;
} else {
//第一个和第二个红包
Random r = new Random();
//要知道第一个红包最多抽到99.98元 (因为100要分成3个包)
//100-(count-1)*min,在这个范围内抽
//若第一个红包最多抽到99.98元,则第二个红包最多抽到0.01元 (因为100要分成3个包)
//100-99.98-(count-1)*min,在这个范围内抽
prize = r.nextDouble(money - (count - 1) * MIN);
//又因为抽红包有可能抽到比0.01还小的数,所以强制改为0.01
if (prize < MIN) {
prize = MIN;
}
}
System.out.println(getName() + "抢到了" + prize);
//更改红包数
count--;
//更改money
money -= prize;
}
}
}
}
main
public class Test {
public static void main(String[] args) {
//创建5个线程
Grab g1=new Grab();
Grab g2=new Grab();
Grab g3=new Grab();
Grab g4=new Grab();
Grab g5=new Grab();
//给线程设置名字
g1.setName("小A");
g2.setName("小B");
g3.setName("小C");
g4.setName("小D");
g5.setName("小E");
//启动线程
g1.start();
g2.start();
g3.start();
g4.start();
g5.start();
}
}
发现有问题:小数位并没有保留两位,不贴近现实
改进后:
public class Grab2 extends Thread {
//使用第一种方法,可能创建多个线程使用静态修饰
private static BigDecimal money = BigDecimal.valueOf(100);
//红包个数
static int count = 3;
//最小的中奖金额
static final BigDecimal MIN = BigDecimal.valueOf(0.01);
@Override
public void run() {
//循环舍弃:抢红包一人只能抢一次
//同步代码块确保线程安全
synchronized (Grab2.class) {
if (count == 0) {
//如果没红包了就说明没抢到
System.out.println(getName() + "没抢到红包");
} else {
//还有红包
//定义一个变量表示中奖的金额
BigDecimal prize;
if (count == 1) {
//表示是最后一个红包,抽取剩下的钱即可
prize = money;
} else {
//第一个和第二个红包
Random r = new Random();
//要知道第一个红包最多抽到99.98元 (因为100要分成3个包)
//100-(count-1)*min,在这个范围内抽
//若第一个红包最多抽到99.98元,则第二个红包最多抽到0.01元 (因为100要分成3个包)
//100-99.98-(count-1)*min,在这个范围内抽
double bounds = money.subtract(BigDecimal.valueOf(count - 1).multiply(MIN)).doubleValue();
prize = BigDecimal.valueOf(r.nextDouble(bounds));
//又因为抽红包有可能抽到比0.01还小的数,所以
}
//四舍五入
prize = prize.setScale(2, RoundingMode.HALF_UP);
System.out.println(getName() + "抢到了" + prize);
//更改红包数
count--;
//更改money
money=money.subtract(prize);
}
}
}
}
main
public class Test {
public static void main(String[] args) {
Grab2 g1=new Grab2();
Grab2 g2=new Grab2();
Grab2 g3=new Grab2();
Grab2 g4=new Grab2();
Grab2 g5=new Grab2();
//给线程设置名字
g1.setName("小A");
g2.setName("小B");
g3.setName("小C");
g4.setName("小D");
g5.setName("小E");
//启动线程
g1.start();
g2.start();
g3.start();
g4.start();
g5.start();
}
}
5.
这里的难点是这个奖池应该定义在哪, javabean的成员位置还是?
在Java中,如果你在main方法中创建了两个JavaBean对象,并且在构造这两个对象时都传递了同一个List集合的引用,那么这两个JavaBean对象将操作同一个集合。这意味着对任何一个对象通过该集合进行的增删改查操作,都会影响到另一个对象通过该集合看到的数据。
具体见:**基本数据类型和引用数据类型的特性
public class Prize implements Runnable {
ArrayList<Integer> list;
//构造,从创建对象时传递一个集合过来
public Prize(ArrayList<Integer> list) {
this.list = list;
}
@Override
public void run() {
//循环
while (true) {
//同步代码块,确保线程安全
synchronized (Prize.class) {
if (list.isEmpty()) {
break;
}
//随机获取集合元素----抽奖
Collections.shuffle(list);
Integer num = list.remove(0);
System.out.println(Thread.currentThread().getName() + "又产生了一个" + num + "元的大奖");
}
//让当前线程睡一会,给别的线程机会
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
main
public class Test {
public static void main(String[] args) {
//创建奖池
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list,10,5,20,50,100,200,500,800,2,80,300,700);
//传入参数
Prize t = new Prize(list);
//创建线程并传入t
Thread t1=new Thread(t);
Thread t2=new Thread(t);
//设置名字
t1.setName("抽奖箱1");
t2.setName("抽奖箱2");
//启动线程
t1.start();
t2.start();
}
}
6.
public class Prize implements Runnable {
ArrayList<Integer> list;
//构造,从main方法传递一个集合过来
public Prize(ArrayList<Integer> list) {
this.list = list;
}
//用两个变量记录 两个抽奖箱产生中奖次数
int count1 = 0;
int count2 = 0;
//用两个集合存储奖项
ArrayList<Integer> list1 = new ArrayList<>();
ArrayList<Integer> list2 = new ArrayList<>();
@Override
public void run() {
//循环
while (true) {
//同步代码块,确保线程安全
synchronized (Prize.class) {
if (list.isEmpty()) {
//当奖箱全部获取完了就开始打印
if ("抽奖箱1".equals(Thread.currentThread().getName())) {
//求最大值
Integer max = Collections.max(list1);
System.out.println("抽奖箱1" + list1+":"+count1+"个奖项,最大值是"+max);
} else {
//求最大值
Integer max = Collections.max(list2);
System.out.println("抽奖箱2" + list2+":"+count2+"个奖项,最大值是"+max);
}
break;
} else {
//list大小不是0,继续抽奖
//随机获取元素
Collections.shuffle(list);
Integer prize = list.remove(0);
if ("抽奖箱1".equals(Thread.currentThread().getName())) {
//添加到list1
list1.add(prize);
//个数++
count1++;
} else {
//添加到list2
list2.add(prize);
//个数++
count2++;
}
}
}
//让当前线程睡一会,给别的线程机会
try {
Thread.sleep(30);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
main
public class Test {
public static void main(String[] args) {
//创建奖池
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list,10,5,20,50,100,200,500,800,2,80,300,700);
Prize t = new Prize(list);
//创建线程
Thread t1=new Thread(t);
Thread t2=new Thread(t);
//设置名字
t1.setName("抽奖箱1");
t2.setName("抽奖箱2");
//启动线程
t1.start();
t2.start();
}
}
这种方法的弊端:当有非常多的抽奖箱时(线程),不可能一个个创建集合来存储中奖项
改进:在run方法内定义一个box集合,创建多个线程时会调用run方法来创建线程对应的集合
public class Prize implements Runnable {
ArrayList<Integer> list;
//构造,从main方法传递一个集合过来
public Prize(ArrayList<Integer> list) {
this.list = list;
}
//用两个变量记录 两个抽奖箱产生中奖次数
int count1 = 0;
int count2 = 0;
@Override
public void run() {
//在run方法内创建一个集合,创建多少个线程就会创建多少个相应的集合
ArrayList<Integer>box=new ArrayList<>();
//循环
while (true) {
//同步代码块,确保线程安全
synchronized (Prize.class) {
if (list.isEmpty()) {
//当奖箱全部获取完了就开始打印
if ("抽奖箱1".equals(Thread.currentThread().getName())) {
int max = Collections.max(box);
System.out.println("抽奖箱1" + box + ":" + count1 + "个奖项,最大值是" + max);
} else {
int max = Collections.max(box);
System.out.println("抽奖箱2" + box + ":" + count2 + "个奖项,最大值是" + max);
}
break;
} else {
//继续抽奖(打乱获取第一个)
Collections.shuffle(list);
Integer prize = list.remove(0);
if ("抽奖箱1".equals(Thread.currentThread().getName())) {
//放入box
box.add(prize);
//奖项数
count1++;
} else {
//放入box
box.add(prize);
//奖项数
count2++;
}
}
}
//让当前线程睡一会,给别的线程机会
try {
Thread.sleep(30);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
为什么可以这样改进?
内存图:
7.
需求:最后一句
思路:
首先代码一定是写在while循环之后的,因为抽奖已经结束
首先要知道抽奖箱1和2的奖项的最大值,我们可以使用第三种方式创建多线程,因为call方法有返回值
public class Prize implements Callable<Integer> {
//定义一个变量
ArrayList<Integer> list;
//构造,从main方法传递一个集合过来
public Prize(ArrayList<Integer> list) {
this.list = list;
}
//用两个变量记录 两个抽奖箱产生中奖次数
int count1 = 0;
int count2 = 0;
@Override
public Integer call() throws Exception {
在run方法内创建一个集合,创建多少个线程就会创建多少个相应的集合
//线程1 //线程2
ArrayList<Integer> box = new ArrayList<>();
//循环
while (true) {
//同步代码块,确保线程安全
synchronized (Prize.class) {
if (list.isEmpty()) {
//当奖箱全部获取完了就开始打印
if ("抽奖箱1".equals(Thread.currentThread().getName())) {
int max = Collections.max(box);
System.out.println("抽奖箱1" + box + ":" + count1 + "个奖项,最大值是" + max);
} else {
int max = Collections.max(box);
System.out.println("抽奖箱2" + box + ":" + count2 + "个奖项,最大值是" + max);
}
break;
} else {
//继续抽奖
Collections.shuffle(list);
Integer prize = list.remove(0);
if ("抽奖箱1".equals(Thread.currentThread().getName())) {
box.add(prize);
count1++;
} else {
box.add(prize);
count2++;
}
}
}
//让当前线程睡一会,给别的线程机会
Thread.sleep(10);
}
----while循环外
//抽奖结束,循环结束,返回box中的最大奖项
//要知道线程一或二的box中的奖项有可能没有,要特别考虑
if (box.isEmpty()){
return null;
}else {
return Collections.max(box);
}
}
}
main、
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建奖池
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list,10,5,20,50,100,200,500,800,2,80,300,700);
//创建多线程要运行的参数对象
Prize t = new Prize(list);
//*这里创建俩个FutureTask对象,因为你不知道的是javabean返回的最大值是线程1还是2的,所以创建两个分别管理线程
//线程一的任务管理器,接收返回值
FutureTask<Integer> ft1=new FutureTask<>(t);
//线程二的任务管理器,接受返回值
FutureTask<Integer> ft2=new FutureTask<>(t);
//创建线程1、2
Thread t1=new Thread(ft1);
Thread t2=new Thread(ft2);
//设置名字
t1.setName("抽奖箱1");
t2.setName("抽奖箱2");
//启动线程
t1.start();
t2.start();
//调用get方法获取返回值
//线程1的最大值
Integer i1 = ft1.get();
//线程2的最大值
Integer i2 = ft2.get();
//打印
System.out.println(i1+" "+i2);
if (i1>i2){
System.out.println("在此次抽奖过程中,抽奖箱1产生了最大奖项,该奖项金额为800");
}else {
System.out.println("在此次抽奖过程中,抽奖箱2产生了最大奖项,该奖项金额为800");
}
}
}