首先说一下自己做这个创建四个进程的一个小问题:
题目要求说的是:需要你创建四个进程,两个进程为.wait两个进程为.notify,然后我仿照创建了以后是这样子的:
package 任务十__多线程;
/**
* @author ${范涛之}
* @Description
* @create 2021-11-22 21:20
*/
public class Test1 {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized ("锁1") {
System.out.println("t1 start");
try {
// t1 释放锁
"锁1".wait();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("t1 end");
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized ("锁2") {
System.out.println("t2 start");
try {
// 通知 t1 进入等待队列
"锁2".wait();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("t2 end");
}
}
});
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
synchronized ("锁3") {
System.out.println("t3 start");
try {
// 通知 t3 进入等待队列
"锁3".notify();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("t3 end");
}
}
});
Thread t4 = new Thread(new Runnable() {
@Override
public void run() {
synchronized ("锁4") {
System.out.println("t4 start");
try {
// 通知 t3 进入等待队列
"锁4".notify();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("t4 end");
}
}
});
t1.start();
t2.start();
t3.start();
t4.start();
}
}
输出结果是这样:![请添加图片描述](https://i-blog.csdnimg.cn/blog_migrate/3b46311af7132ecc30e923db263e181c.png)
我们可以看到为什么t1和t2没有返回值呢?
我开始好奇,感觉因该是
这个地方出了问题,后来通过询问同学得知:锁是需要钥匙和它对应的,对于一个锁在被wait以后,他需要一个notify来去“救”原来这个wait,而这个notify必须是前面.wait的notify,也就是我们在synchronized上锁以后,会给他传入一个名字:“锁1”,这样的一个字符串,
倘若后面是.wait,那么后续线程中必须有一个同样的synchronized(“锁1”)然后去执行.notify,否则将无法释放锁1的结束语句。
举一个例子,我写了六个进程分别对应锁123451*,前两个是.wait,后四个是.notify,然后按照原理就是,notify的代码不需要“钥匙”去解他所以可以直接输出开始和结束,线程1和2只有1有“钥匙”:也就是第六个线程的.notify,第二个线程的.wait将永远无法输出他的结束:
由图可知确实是这样,1345都有开始结束,只有2不可以结束!
然后我们回归正题去写这个题:(就是让每一个.wait锁都有自己的钥匙)
package 任务十__多线程;
/**
* @author ${范涛之}
* @Description
* @create 2021-11-22 21:20
*/
public class Test1 {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized ("锁1") {
System.out.println("t1 start");
try {
// t1 释放锁
"锁1".wait();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("t1 end");
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized ("锁2") {
System.out.println("t2 start");
try {
// 通知 t1 进入等待队列
"锁2".wait();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("t2 end");
}
}
});
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
synchronized ("锁1") {
System.out.println("t3 start");
try {
// 通知 t3 进入等待队列
"锁1".notify();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("t3 end");
}
}
});
Thread t4 = new Thread(new Runnable() {
@Override
public void run() {
synchronized ("锁2") {
System.out.println("t4 start");
try {
// 通知 t3 进入等待队列
"锁2".notify();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("t4 end");
}
}
});
t1.start();
t2.start();
t3.start();
t4.start();
}
}
结果:第一次:
第二次:
我们可以由此得知线程的启动并不是根据start的顺序来决定的!
柜台卖票:(Therad实现)
package 任务十__多线程;
/**
* @author ${范涛之}
* @Description
* @create 2021-11-24 16:44
*/
public class Tick {
public static void main(String[] args) {
Window w1 = new Window("柜台一");
Window w2 = new Window("柜台二");
Window w3 = new Window("柜台三");
w1.start();
w2.start();
w3.start();
}
}
class Window extends Thread{
private static int number = 100;
public Window(String name){
super(name);
}
@Override
public void run() {
while (number>0){
number--;
System.out.println(getName()+"兑换成功一张票"+"票号为:"+number);
}
}
}
输出结果:
这里有一个小问题就是:一开始的99和98票消失了!
解决方法:为每一个票添加标记,可以通过放到一个数组里面然后去验证一下是否在数组里面
柜台买票:( Runnable 接口实现):
首先说一下自己一开始的想法那就是:存放到集合里面去三个线程遍历一个循环
package 任务十__多线程.售票;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* @author ${范涛之}
* @Description
* @create 2021-11-24 20:25
*/
public class Tick1 {
public static void main(String[] args) {
List list = new ArrayList();
for (int i = 1; i <= 100; i++){
list.add(i);
}
Iterator iterator = list.iterator();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
while (iterator.hasNext()) {
for (int i = 0; i < list.size(); i++) {
synchronized ("lock") {
String threadname = Thread.currentThread().getName();
System.out.println(threadname + "柜台正在兑换" + list.get(i) + "台iPhone");
list.remove(i); // 删除已经遍历的元素
}
}
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
while (iterator.hasNext()) {
for (int i = 0; i < list.size(); i++) {
synchronized ("lock") {
String threadname = Thread.currentThread().getName();
System.out.println(threadname + "柜台正在兑换" + list.get(i) + "台iPhone");
list.remove(i);
}
}
}
}
});
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
while (iterator.hasNext()) {
for (int i = 0; i < list.size(); i++) {
synchronized ("lock") {
String threadname = Thread.currentThread().getName();
System.out.println(threadname + "柜台正在兑换" + list.get(i) + "台iPhone");
list.remove(i);
}
}
}
}
});
t1.start();
t2.start();
t3.start();
}
}
上面这段代码由于自己还没有学习,java委托和事件,所以并没有全部完成,以后我会反过来解决这个题,如何三个线程干同一件事情
然后接下来讲解一下怎样使用Runnable 简单实现买票:
package 任务十__多线程.售票;
/**
* @author ${范涛之}
* @Description
* @create 2021-11-24 21:43
*/
public class Ticket implements Runnable {
private int num1 = 0; //出票数
private int num2 = 100; //剩余票数
@Override
public void run() {
while (true) {
synchronized (this) {
if (num2 <= 0) {
break;
}
num1++;
num2--;
System.out.println("显示出票信息" + Thread.currentThread().getName() + "抢到第" + num1 + "张票,剩余" + num2 + "张票");
}
}
}
static class Test {
public static void main(String[] args) {
Ticket ticket = new Ticket();
Thread g1 = new Thread(ticket,"柜台一");
Thread g2 = new Thread(ticket,"柜台二");
Thread g3 = new Thread(ticket,"柜台三");
g1.start();
g2.start();
g3.start();
}
}
}
这里遇到了一个小问题:就是总是先启动的哪个线程先抢票而且很有可能他会一直抢下去
这里说一个改进方法:
package 任务十__多线程.售票;
/**
* @author ${范涛之}
* @Description
* @create 2021-11-24 23:43
*/
class SaleThread implements Runnable {
/**
* 使用静态成员变量作为100张票的保存变量,是一个共享资源。
*/
private static int tickets = 20;
@Override
public void run() {
// 完成售票过程
while (true) {
/*
字符串可以作为锁对象,因为双引号包含的字符串不管在代码中如何运行,有且只有一个
*/
synchronized (Thread.currentThread().getName()) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "售出了" + tickets + "张票");
tickets--;
} else {
System.out.println(Thread.currentThread().getName() + "售罄!!!");
break;
}
}
}
}
}
public class Demo {
public static void main(String[] args) {
Thread t1 = new Thread(new SaleThread(), "售票人员1");
Thread t2 = new Thread(new SaleThread(), "售票人员2");
Thread t3 = new Thread(new SaleThread(), "售票人员3");
t1.start();
t2.start();
t3.start();
}
}
运行结果:
说一下改进在了什么地方: synchronized (Thread.currentThread().getName()) {}
我们之前都是用的synchronized{}
,但是这样写会导致synchronized (this) 会连续执行当前线程,所以会出现大片大片一样的代码
使用Thread实现:
package 任务十__多线程.售票;
/**
* @author ${范涛之}
* @Description
* @create 2021-11-24 23:41
*/
public class Ticket2 extends Thread {
private int num1 = 0;
private int num2 = 100;
@Override
public void run() {
while (true) {
synchronized (Thread.currentThread().getName()){ ;
if (num2 <= 0) {
break;
}
num1++;
num2--;
System.out.println("显示出票信息" + Thread.currentThread().getName() + "抢到第" + num1 + "张票,剩余" + num2 + "张票");
}
}
}
}
class Function{
public static void main(String[] args) {
Ticket2 ticket2 = new Ticket2();
Thread t1 = new Thread(ticket2,"柜台一");
Thread t2 = new Thread(ticket2,"柜台二");
Thread t3 = new Thread(ticket2,"柜台三");
t1.start();
t2.start();
t3.start();
}
}
总是遇到线程安全性的问题:就是同一个票同时被不同线程拿到的假象,这里写一个真实有效的代码:
package 任务十__多线程.售票;
import java.util.ArrayList;
import java.util.List;
public class TestDemo {
//定义售票线程类(也就是窗口)
public static class Station extends Thread{
//构造方法给线程名字赋值
public Station(String name) {
super(name);
}
//票数要静态定义
static int tick=50;
//静态钥匙
static Object ob ="key"; //值是任意的
//重写run方法,实现售票操作
@Override
public void run() {
List<Integer> list = new ArrayList<>();
while (tick>0) {
synchronized("key") { //必须使用一个同步锁,进去的人会把钥匙拿在手上,出来后才能交出钥匙
if (tick>0) {
System.out.printf("%s卖出了第%d张票 \n",getName(),tick);
list.add(tick);
tick--;
}else {
System.out.printf("%s:票已售空 \n",getName());
}
}
try {
sleep((int)(Math.random()*3000)+1); //随机休息1-3000ms
}catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.printf("%s 销售情况: %s \n",getName(),list.toString());
}
}
public static void main(String[] args) {
//实例化站台对象,并为每一个站台取名字(8个线程窗口一起卖5张票)
for (int i=1; i<=8; i++) {
String sName="窗口" + String.valueOf(i);
Station Station = new Station(sName);
Station.start();
}
}
}
可以看出这里就很正常,现在太晚了脑子有点糊,改日回过神来再看!先就到这里了
这里再说一下吧,关于synchronized(this)这个真的令人可苦恼的东西,他并不是说会造成真正的线程安全问题,他仅仅是显示上的问题比如:
package 任务十__多线程.售票;
import java.util.ArrayList;
import java.util.List;
public class TestDemo {
//定义售票线程类(也就是窗口)
public static class Station extends Thread{
//构造方法给线程名字赋值
public Station(String name) {
super(name);
}
//票数要静态定义
static int tick=50;
//静态钥匙
static Object ob ="key"; //值是任意的
//重写run方法,实现售票操作
@Override
public void run() {
List<Integer> list = new ArrayList<>();
while (tick>0) {
synchronized(this) { //必须使用一个同步锁,进去的人会把钥匙拿在手上,出来后才能交出钥匙
if (tick>0) {
System.out.printf("%s卖出了第%d张票 \n",getName(),tick);
list.add(tick);
tick--;
}else {
System.out.printf("%s:票已售空 \n",getName());
}
}
try {
sleep((int)(Math.random()*3000)+1); //随机休息1-3000ms
}catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.printf("%s 销售情况: %s \n",getName(),list.toString());
}
}
public static void main(String[] args) {
//实例化站台对象,并为每一个站台取名字(8个线程窗口一起卖5张票)
for (int i=1; i<=8; i++) {
String sName="窗口" + String.valueOf(i);
Station Station = new Station(sName);
Station.start();
}
}
}
运行结果:
我们可以看出来其实真正的数据并没有错误!!!!!
这里留下悬念,等待日后的复盘!!
分割线···································································································
线程池:使用 newScheduledThreadPool 线程池实现每隔 1 分钟打印一条消息
首先讲解一下使用 newScheduledThreadPool 是怎样使用的以及一些参数说明:
参数说明:
- command:执行线程
- initialDelay:初始化延时
- period:两次开始执行最小间隔时间
- unit:计时单位
1:首先说一下它的特点:延时启动 、定时启动 、可以自定义最大线程池数量
2:创建实例:
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
这里如果没有特殊需求要指定最大线程池数量的话,建议最大线程池数量=运行程序机器的cpu核心数,即
int cpuNubmer = Runtime.getRuntime().availableProcessors();
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(cpuNubmer);
3:延时运行举例:这里需要用匿名内部类的方式,实现Runnable接口,重写Runnable的run方法,将Runnable类型的参数传入schedule方法中。
final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(1);
System.out.println("提交时间: " + sdf.format(new Date()));
scheduledThreadPool.schedule(new Runnable() {
@Override
public void run() {
System.out.println("运行时间: " + sdf.format(new Date()));
}
}, 3, TimeUnit.SECONDS);//延迟3秒执行
scheduledThreadPool.shutdown();
4:用lambda表达式的写法:函数式编程:
final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(1);
System.out.println("提交时间: " + sdf.format(new Date()));
scheduledThreadPool.schedule(() -> System.out.println("运行时间: " + sdf.format(new Date())), 3, TimeUnit.SECONDS);
scheduledThreadPool.shutdown();
每分钟输出与每秒钟输出:
package 任务十__多线程.周期线程池;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* @author ${范涛之}
* @Description
* @create 2021-11-25 0:49
*/
public class Test {
public static void main(String[] args) {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
pool.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("一分钟过去了");
}
}, 3, 1, TimeUnit.MINUTES);
}
}
改成秒:
package 任务十__多线程.周期线程池;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* @author ${范涛之}
* @Description
* @create 2021-11-25 0:49
*/
public class Test {
public static void main(String[] args) {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
pool.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("一分钟过去了");
}
}, 3, 1, TimeUnit.SECONDS);
}
}