当前位置:首页 » 《休闲阅读》 » 正文

界面编程实战分析------计算器实现用户交互;多线程---售票窗口分析;一文带你分析界面编程与多线程【Java养成】_a23452的博客

21 人参与  2022年01月02日 15:24  分类 : 《休闲阅读》  评论

点击全文阅读


Java学习打卡:第十二天

内容导航

  • Java学习打卡:第十二天
    • 内容管理
      • 前言(分享)
      • 界面编程实例(简单计算器)
        • 程序编写提示
        • 程序代码(注解详细 )
        • 监听类
          • 适配器
      • 多线程
        • 进程
        • 线程
        • 如何实现多线程
          • extends Thread 实例分析
          • 实现Runnable接口 方式
          • 如何保证线程安全
            • 线程同步synchronized

Java养成计划(打卡第12天)


内容管理

前言(分享)

Hello! 我是C风,转眼我们就将面向对象要学完了,但是对于JAVA SE 基础还有几个重要的点:反射机制,多线程和网络编程;这几个我接下来就会分享,当讲框架学习完之后,我会继续再过一遍基础,这一遍将更加扎实,每一个模块就会以代码居多,因为基础的知识已经过了一遍,当将Java SE过完之后,也许就10月中旬了,之后就会写几个项目,比如简易聊天室和文字游戏,还会做一个仓储管理,当然会结合界面编程,实现简单的与用户交互

界面编程实例(简单计算器)

我们之前已经大概了解过界面编程的基础知识,接下来就分享一个题目,两数之和--------是不是觉得很简单,当然简单,只是我们现在要提供一个界面与用户交互了

先介绍一下基本的监听器和事件

  • ActionEvent and ActionListener 事件所在的包就是Java.awt.event
    • 监听器的类型:interface ActionListener ;--------所有行为类事件的监听器都要实现ActionListener接口,在ActionListener中只定义了一个actionPerformed()方法,对于每一个注册了这个监听器的组件,只要发生了行为类事件(比如按钮按下)就会调用该方法来相应该事件
public interface ActionListener{
    public void actionPerformed(ActionEvent e);//必须要实现该方法,方法体
}
  • 可能触发事件用到的组件有JRadioButton(单选按钮) JButton(按钮) JTextFiled(文本框)

首先按照之前的讲解我们首先应该创建一个顶级组件来继承这个JFrame,当然中级容器JPanel也可以,之后在类中设置我们的基本的组件

程序编写提示

我们这里设置三个面板,面板的布局是BorderLayout,西面板设置提示信息,面板按照网格布局GridLayout;提示信息为3行1列;中面板设置文本框,文本框colum设置为10;东面板为按钮面板,上面有两个按钮,加法和减法;之后我们设置一个监听器,当监听器Action识别为+我们进行加法,并将结果显示到AnswerLabel上;这里我们用as捕捉异常,防止越界,将错误信息也给AnswerLabel.这里我们使用一个内部类,因为属性是私有的。

监听器我们一般写成内部类,因为我们只是在监听的时候才会使用到这个类,访问外部类的私有属性比较方便

程序代码(注解详细 )

package Calculator;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

@SuppressWarnings("serial")
public class Calculator extends JFrame{//JFrame在swing包中
	private JPanel leftPanel;
	private JPanel centerPanel;//私有属性
	private JPanel buttonPanel;
	private JTextField input1TextField;
	private JTextField input2TextField;
	private JLabel answerLable;
	private JButton plusButton;
	private JButton minusButton;
	public static void main(String[] args) {
//		JFrame frame = new JFrame("加减法计算器");
//		frame.add(new Calculator());//这里已经是一个顶级容器了,所以我们这里不能将顶级容器放入容器,会报错
		Calculator frame = new Calculator("简单加减法计算器");//直接使用这个顶级容器,去查看JFrame的构造方法
		frame.setSize(600, 200);
		frame.setVisible(true);
	}
	public Calculator(String title) {
		super(title);//有参了
		setLayout(new BorderLayout());//java.awt布局管理;中级容器的布局
		leftPanel = new JPanel();//中级容器
		leftPanel.setLayout(new GridLayout(3,1));//每个面板的网格布局
		/**
		 * 接下来将提示语言放在左边的网格化面板上
		 */
		JLabel inputOne = new JLabel("Input 1 :  ");//不可编辑
		JLabel inputTwo = new JLabel("Input 2 :  ");
		JLabel answer = new JLabel("Aswer : ");
		
		leftPanel.add(inputOne);
		leftPanel.add(inputTwo);
		leftPanel.add(answer);
		add(leftPanel,BorderLayout.WEST);//将面板放在西边
		/**
		 * 放置中央面板网格
		 */
		centerPanel = new JPanel();
		centerPanel.setLayout( new GridLayout(3,1));
		input1TextField = new JTextField(10);
		input2TextField = new JTextField(10);//设置参数
		answerLable = new JLabel();//属性都还没有给对象
		centerPanel.add(input1TextField);
		centerPanel.add(input2TextField);
		centerPanel.add(answerLable);//不可编辑
		add(centerPanel,BorderLayout.CENTER);//将面板放在中央
		/**
		 * 将按钮面板放在右边
		 */
		buttonPanel = new JPanel();
		buttonPanel.setLayout(new GridLayout(2,1));//面板的网格布局
		plusButton = new JButton("+");
		minusButton = new JButton("-");
		buttonPanel.add(plusButton);
		buttonPanel.add(minusButton);
		add(buttonPanel,BorderLayout.EAST);//放在东边
		/**
		 * 设置监听器和事件
		 */
		ListenerOne listener = new ListenerOne();//创建监听器,实现好了
		plusButton.addActionListener(listener);//点击按钮触发事件
		minusButton.addActionListener(listener);
	}
	
	class ListenerOne implements ActionListener{//实现接口用implements
		//我们这里这个类只是辅助类
			@Override
			public void actionPerformed(ActionEvent e) {
				try {//监听两个按钮
					@SuppressWarnings("removal")
					double d1 = new Double(input1TextField.getText()).doubleValue();
					@SuppressWarnings("removal")
					double d2 = new Double(input2TextField.getText()).doubleValue();
					if(e.getSource() == plusButton)
					{
						answerLable.setText(""+(d1+d2));
					}
					else {
						answerLable.setText(""+(d1-d2));//String类型
					}
				}catch(Exception as) {
					answerLable.setText(as.getMessage());//展示错误信息
				}	
			}

		}
//内部类
}

运行结果是正常的(如下)如果代码有疑惑的欢迎咨询
在这里插入图片描述

监听类

大部分是通过继承事件监听器接口来处理事件的。但是继承java接口必须实现出接口中所有的方法

但是有的接口中包含了大量的函数比如WindowsListener MouseListener FocusListener),如果实现所有函数是一件麻烦的事情,Java中就定义了相应接口的适配器来解决这种情况

适配器

适配器已经实现了接口中的所有方法,只要继承适配器就可,并覆写想要实现的方法即可,比如WindowListener WindowAdapter

多线程

进程

在了解多线程之前我们先来了解一下进程,什么是进程呢,进程就是我们的应用程序的执行实例,比如打卡PPT,那么进程就是这个WPS程序执行,而结束进程就是关闭程序的执行。运行的每一个应用程序都是进程,它有独立的内存空间和系统资源;

线程

线程就是CPU调度和分派的基本单位,是进程中执行运算的最小单位,可完成一个独立的顺序控制流程。比如我们的eclipse是一个进程,那么其中我们编写的一个程序就是一个线程

随便上段代码

static int[] F = new int[201];
	public static void DFS(int n)
	{
		if(n < 3)
		{
			F[n] = 1;
			return;
		}
		DFS(n-1);
		DFS(n-2);
		F[n] = F[n-1] + F[n-2];
	}
	
	public static void main(String[] args) {
		int n;
		Scanner in = new Scanner(System.in);
		n = in.nextInt();
		DFS(n); // 先执行
		System.out.println(F[n]);
		System.out.println("echo"); //后执行
	}

这段代码我们知道,一定是先执行这里的DFS,之后才会执行我们的打印语句echo;那我们可不可以同时执行这两个代码块呢,我们在计算机中可以同时开多个进程,比如打开浏览器的同时打开eclipse;但是这里这段程序是做不到这点的,因为这段程序是单线程

我们目前所写的程序都是单线程的,就是只能从上到下依次执行,当然不能乱用goto语句的,那我们如果要同时执行这些入局要怎么做呢,那就要使用到我们的多线程的方式

如何实现多线程

在Java里面要实现多线程有两种方式,一种就是让我们想要实现多线程的类去继承我们的线程类Thread,这个词很常见啊

Exception in thread "main"

比如这就是我们很常见的报错,就是指线程main中存在例外(异常),我们继承这个类之后重写里面的run();之后我们在mian线程中new一个对象时就开辟了新的线程,这个时候我们就object.start()就可以让多个线程执行,只是还是需要系统的CPU来分配我们线程的执行时间

package Luogu;

import java.util.Scanner;

public class Luogu extends Thread{
	
	static int[] F = new int[201];
	
	@Override
	public void run() {//这是新线程
		for(int i = 1;i <= 10;i++)
		{
			System.out.println("hi ,how are you")
		}
	}
	
	public static void DFS(int n)
	{//计算斐波拉契数列
		if(n < 3)
		{
			F[n] = 1;
			return;
		}
		DFS(n-1);
		DFS(n-2);
		F[n] = F[n-1] + F[n-2];
	}
	
	public static void main(String[] args) {
		int n;
		Scanner in = new Scanner(System.in);
		n = in.nextInt();
		Luogu luo = new Luogu();//调用实例方法,只是处于可执行状态,就是准备好执行
		luo.start();//启动新线程,但是这不意味着马上就去执行这个线程,需要CPU来分配执行时间
		DFS(n);//是按照时间执行来分配的
		System.out.println(F[n]);
		System.out.println("echo");
	}
}//这里就有两个线程,一个就是main线程,第二个就是新定义对象对应0号线程,这里线程之间是互相不影响的,一个报错,另外的还是可以正常执行

进入mian方法时,就会启动mian 线程,进入创建对象后,再start就启动了新线程,这里的启动不是说就执行了,还是CPU来分配线程的执行时间,线程之间是互相不影响的,我故意在run中语句出现语法错误,之后运行

10
55
echo
Exception in thread “Thread-0” java.lang.Error: Unresolved compilation problem:
Syntax error, insert “;” to complete BlockStatements

at Luogu.Luogu.run(Luogu.java:13)

这里的main线程还是正常执行,只是Thrad-0报错了,不能执行

线程的执行是CPU分配的,不一定一直都main线程先执行

12
144
hi ,how are you
hi ,how are you
hi ,how are you
hi ,how are you
hi ,how are you
hi ,how are you
hi ,how are you
hi ,how are you
hi ,how are you
hi ,how are you

这里是main线程先执行,我们再改一改数据试一试

20
hi ,how are you
hi ,how are you
hi ,how are you
hi ,how are you
hi ,how are you
hi ,how are you
hi ,how are you
hi ,how are you
hi ,how are you
hi ,how are you
6765

这次又是run新线程先执行了,因为CPU判断执行主线程的时间相较run线程时间长很多,所以就选择让新线程先执行了

CPU就可能将执行权限交给不同的线程,这都是CPU决定的。

extends Thread 实例分析

那我们这种开辟线程的方法就是我们定义run之后然后创建四个对象,因为new就是开辟一个线程,那我们现在来模拟一下卖票的小程序,我们单线程就是只开辟一个窗口进行卖票,效率太低了,那我们现在就开辟四个窗口卖票

这里可以得到线程名称 Thread.currentThread().getName(); 还有就是要启动每一个线程,必须要start();

package Luogu;

public class SellTicket extends Thread{
	private int ticket = 10;
	@Override
	public void run() {
		while(ticket > 0)
		{//得到线程的名称
			System.out.println(Thread.currentThread().getName()+"号窗口"+"卖出第"+ ticket-- +"张票");
		}
	}
	public static void main(String[] args) {
		SellTicket window1 = new SellTicket();
		SellTicket window2 = new SellTicket();
		SellTicket window3 = new SellTicket();
		SellTicket window4 = new SellTicket();
		window1.start();//不写这个就没有启动线程
		window2.start();
		window3.start();
		window4.start();//表示启动线程,一般第一个是0号线程,之后就是1号
	}
}

这里的运行结果是这样的

Thread-1号窗口卖出第10张票
Thread-0号窗口卖出第10张票
Thread-3号窗口卖出第10张票
Thread-2号窗口卖出第10张票
Thread-2号窗口卖出第9张票
Thread-3号窗口卖出第9张票
Thread-0号窗口卖出第9张票
Thread-0号窗口卖出第8张票
Thread-1号窗口卖出第9张票
Thread-0号窗口卖出第7张票
Thread-3号窗口卖出第8张票
Thread-2号窗口卖出第8张票
Thread-3号窗口卖出第7张票
Thread-0号窗口卖出第6张票
Thread-1号窗口卖出第8张票
Thread-0号窗口卖出第5张票
Thread-3号窗口卖出第6张票
Thread-2号窗口卖出第7张票
Thread-3号窗口卖出第5张票
Thread-0号窗口卖出第4张票
Thread-1号窗口卖出第7张票
Thread-0号窗口卖出第3张票
Thread-3号窗口卖出第4张票
Thread-2号窗口卖出第6张票
Thread-3号窗口卖出第3张票
Thread-0号窗口卖出第2张票
Thread-1号窗口卖出第6张票
Thread-3号窗口卖出第2张票
Thread-2号窗口卖出第5张票
Thread-2号窗口卖出第4张票
Thread-2号窗口卖出第3张票
Thread-2号窗口卖出第2张票
Thread-2号窗口卖出第1张票
Thread-3号窗口卖出第1张票
Thread-0号窗口卖出第1张票
Thread-1号窗口卖出第5张票
Thread-1号窗口卖出第4张票
Thread-1号窗口卖出第3张票
Thread-1号窗口卖出第2张票
Thread-1号窗口卖出第1张票

这好像和我们想象的不一样,就是我们其实卖了40张票,但是我们也确实发现不同的线程在同时执行

这里为什么会出现40张票呢,那是因为我们这里的票是成员变量,然后我们开辟线程靠的是创建对象,所以每个对象就都具备了这个属性,就是每个线程窗口之前都有10张票,那我们这么解决这个问题,就是共享信息呢

我们还是先总结一下扩展Thread方式的缺点

  • 就是每个线程都具有自己的私有的属性,就是属性不共享,这是由对象不同导致的
  • 我们要通过继承的方式,那该类就不能去继承其他的类了,因为Java是单继承。这会让我们的程序不能继承其他的类,有很多就局限了

那就要引出另外一种方式了

实现Runnable接口 方式

这和上面的方式没有太大的区别,只是这个是实现接口而已

public class SellTicket implements Runnable{};

就这样就实现了接口,我们之后就重写里面的run方法就好了

只是现在我们实现多线程就和第一种有点区别了

我们知道上一种方式是这样

extends Thread
    
SellTicket s1 = new SellTicket();
SellTicket s2 = new SellTicket();
s1.start();
s2.start();

也就是创建多个对象,每个对象都是一个线程,而现在我们就只需要创建一个对象,然后创建多个Thread对象,在构造方法中传入我们上面创建的一个对象就可以了。这样就可以只有一个属性了。

implements Runnable
    
SellTicket s = new SellTicket();
Thread t1 = new Thread(s);
Thread t2 = new Thread(s);
t1.start();
t2.start();

但是我们还是要使用start()来启动我们的线程,我们在企业开发中常使用的还是这种方式

package Luogu;

//public class SellTicket extends Thread{
public class SellTicket implements Runnable{
	private int ticket = 10;
	@Override
	public void run() {
		while(ticket > 0)
		{
			System.out.println(Thread.currentThread().getName()+"号窗口"+"卖出第"+ ticket-- +"张票");
		}
	}
	public static void main(String[] args) {
		SellTicket s = new SellTicket();
		Thread thread1 = new Thread(s);
		Thread thread2 = new Thread(s);
		Thread thread3 = new Thread(s);
		Thread thread4 = new Thread(s);
		thread1.start();
		thread2.start();
		thread3.start();
		thread4.start();
	}
}

运行结果是可能变化的,但是这次卖票的总数就是10了

Thread-1号窗口卖出第10张票
Thread-3号窗口卖出第7张票
Thread-0号窗口卖出第9张票
Thread-2号窗口卖出第8张票
Thread-0号窗口卖出第4张票
Thread-3号窗口卖出第5张票
Thread-3号窗口卖出第1张票
Thread-1号窗口卖出第6张票
Thread-0号窗口卖出第2张票
Thread-2号窗口卖出第3张票

每次运行的结果都不一样,那是因为CPU分配的运行权力每次都不一样,就是说有一种执行的机会,比如我们的线程0在打印语句的时候,突然CPU将执行权给了另外的线程就会出现重复卖票的可能,多模拟几次就可以发现,这就是CPU随时切换执行权导致的

就是比如线程0执行卖第10张票,还没有执行完语句,突然CPU切换到了线程2,这个时候系统判断还是有10张票,然后就还是卖第10张票,就出现了问题。就出现了数据不安全的异常

如何保证线程安全

这里我们想要保证线程安全,就必须让一个线程还没有执行完的时候,其他的线程不要执行

线程同步synchronized

那我们就要使用线程同步来实现这个这个功能,我们就使用关键字synchronized来对需要处理的片段上🔒,注意这里synchronized里面可以传入任何一个对象就可以保证上🔒

while(ticket > 0)
		{
			synchronized(obj)//加上一个同步锁
			{
				System.out.println(Thread.currentThread().getName()+"号窗口"+"卖出第"+ ticket-- +"张票");
			}
		}

这样之后,就不会再出现重复卖票的行为,但是出现了新的问题就是一直只有一个线程执行完才会其他线程,并且会出现负数

Thread-0号窗口卖出第10张票
Thread-0号窗口卖出第9张票
Thread-0号窗口卖出第8张票
Thread-0号窗口卖出第7张票
Thread-0号窗口卖出第6张票
Thread-0号窗口卖出第5张票
Thread-0号窗口卖出第4张票
Thread-0号窗口卖出第3张票
Thread-0号窗口卖出第2张票
Thread-0号窗口卖出第1张票
Thread-3号窗口卖出第0张票
Thread-2号窗口卖出第-1张票
Thread-1号窗口卖出第-2张票

这里就是当还剩1张票的时候,还没有执行同步代码块时,机会给了3号线程,同时又给了1线程和2号线程。还是存在问题,这个时候我们就在同步锁里面再加上一个判断语句,只有里面满足才执行

synchronized(obj)//加上一个同步锁
			{
    			if(ticket > 0)
                {
					System.out.println(Thread.currentThread().getName()+"号窗口"+"卖出第"+ ticket-- +"张票");
                }
			}

这样就不会出现负数的行为了,就只有0号线程执行完才会去执行其余线程

Thread-0号窗口卖出第10张票
Thread-0号窗口卖出第9张票
Thread-0号窗口卖出第8张票
Thread-0号窗口卖出第7张票
Thread-0号窗口卖出第6张票
Thread-0号窗口卖出第5张票
Thread-0号窗口卖出第4张票
Thread-0号窗口卖出第3张票
Thread-0号窗口卖出第2张票
Thread-0号窗口卖出第1张票

这样我们就不会出现负数了,你可能疑惑这里又只有一个线程执行了,那是这种情况而已,还有就是数据不够大,当你切换之后我们就可以发现时几个线程都在执行

好了~~~今天的分析结束了,我们主要分享了一个界面编程的案例,那就是做一个界面的计算器,要实现用户交互,之后我们又通过售票的方式来讲解我们的多线程知识~


点击全文阅读


本文链接:http://zhangshiyu.com/post/32546.html

卖出  线程  窗口  
<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1