201871010101-陈来弟《面向对象程序设计(java)》第十六周学习总结
实验十四 应用程序归档与线程初步
实验时间 2019-12-12
第一部分:基础知识
1. 程序与进程:
进程是指一个具有一定独立功能的程序关于某个数据集合的一次运行活动。电脑中时会有很多单独运行的程序,每个程序有一个独立的进程,而进程之间是相互独立存在的。比如下图中的QQ、酷狗播放器、电脑管家等等。
进程想要执行任务就需要依赖线程。换句话说,就是进程中的最小执行单位就是线程,并且一个进程中至少有一个线程。
进程与程序的区别:
1)、 程序是指令的有序集合,是一个静态概念,其本事没有任何运行的含义。而进程是程序在处理机上的一次执行过程,是一动态的概念。
2)、 程序可以作为一种软件资料长期保存,而进程则是有一定生命周期的,它能够动态的产生和消亡。
3)、 进程是一个能独立运行的单位,能与其他进程并行活动。
4)、 进程是竞争计算机系统有限资源的基本单位,也是进行处理机调度的基本单位。程序无此该概念。
5)、 进程与程序之间无一一对应关系。不同的进程可以包含同一程序,同一程序在执行中也可以产生多个进程。
6)、 程序是记录在介质上指令的有序集合,而进程则由程序、数据和进程控制块3部分组成
2. 多线程
用多线程只有一个目的,那就是更好的利用cpu的资源,因为所有的多线程代码都可以用单线程来实现。提到多线程这里要说两个概念,就是串行和并行,搞清楚这个,我们才能更好地理解多线程。
所谓串行,其实是相对于单条线程来执行多个任务来说的,我们就拿下载文件来举个例子:当我们下载多个文件时,在串行中它是按照一定的顺序去进行下载的,也就是说,必须等下载完A之后才能开始下载B,它们在时间上是不可能发生重叠的。
说这个话其实只有一半对,因为反应“多角色”的程序代码,最起码每个角色要给他一个线程吧,否则连实际场景都无法模拟,当然也没法说能用单线程来实现:比如最常见的“生产者,消费者模型”。
很多人都对其中的一些概念不够明确,如同步、并发等等,让我们先建立一个数据字典,以免产生误会。
- 多线程:指的是这个程序(一个进程)运行时产生了不止一个线程
- 并行与并发:
- 并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。
- 并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。并发往往在场景中有公用的资源,那么针对这个公用的资源往往产生瓶颈,我们会用TPS或者QPS来反应这个系统的处理能力。
- 线程安全:经常用来描绘一段代码。指在并发的情况之下,该代码经过多线程使用,线程的调度顺序不影响任何结果。这个时候使用多线程,我们只需要关注系统的内存,cpu是不是够用即可。反过来,线程不安全就意味着线程的调度顺序会影响最终结果,如不加事务的转账代码:
void transferMoney(User from, User to, float amount){
to.setMoney(to.getBalance() + amount);
from.setMoney(from.getBalance() - amount);
}
- 同步:Java中的同步指的是通过人为的控制和调度,保证共享资源的多线程访问成为线程安全,来保证结果的准确。如上面的代码简单加入
@synchronized
关键字。在保证结果准确的同时,提高性能,才是优秀的程序。线程安全的优先级高于性能。
并行:下载多个文件,开启多条线程,多个文件同时进行下载,这里是严格意义上的,在同一时刻发生的,并行在时间上是重叠的。
了解了这两个概念之后,我们再来说说什么是多线程。举个例子,我们打开腾讯管家,腾讯管家本身就是一个程序,也就是说它就是一个进程,它里面有很多的功能,我们可以看下图,能查杀病毒、清理垃圾、电脑加速等众多功能。
按照单线程来说,无论你想要清理垃圾、还是要病毒查杀,那么你必须先做完其中的一件事,才能做下一件事,这里面是有一个执行顺序的。
如果是多线程的话,我们其实在清理垃圾的时候,还可以进行查杀病毒、电脑加速等等其他的操作,这个是严格意义上的同一时刻发生的,没有执行上的先后顺序。
以上就是,一个进程运行时产生了多个线程。
在了解完这个问题后,我们又需要去了解一个使用多线程不得不考虑的问题——线程安全。
3. 线程安全
当多个线程访问某个方法时,不管你通过怎样的调用方式、或者说这些线程如何交替地执行,我们在主程序中不需要去做任何的同步,这个类的结果行为都是我们设想的正确行为,那么我们就可以说这个类是线程安全的。
既然是线程安全问题,那么毫无疑问,所有的隐患都是在多个线程访问的情况下产生的,也就是我们要确保在多条线程访问的时候,我们的程序还能按照我们预期的行为去执行,我们看一下下面的代码。
Integer count = 0;
public void getCount() {
count ++;
System.out.println(count);
}
最常见的一种,就是我们A线程在进入方法后,拿到了count的值,刚把这个值读取出来,还没有改变count的值的时候,结果线程B也进来的,那么导致线程A和线程B拿到的count值是一样的。
那么由此我们可以了解到,这确实不是一个线程安全的类,因为他们都需要操作这个共享的变量。其实要对线程安全问题给出一个明确的定义,还是蛮复杂的,我们根据我们这个程序来总结下什么是线程安全。
当多个线程访问某个方法时,不管你通过怎样的调用方式、或者说这些线程如何交替地执行,我们在主程序中不需要去做任何的同步,这个类的结果行为都是我们设想的正确行为,那么我们就可以说这个类是线程安全的。
4. 线程有几种状态:
1)、新建状态(new)
2)、就绪状态(Runnable)
3)、运行状态(Running)
4)、阻塞状态(Blocked)
5)、死亡状态(Dead)
5. 线程有什么用处
java程序中流传一句话,不会使用线程就别跟别人说自己学过java。目前绝大部分应用程序都会涉及到多并发的问题。只要应用程序涉及到并发,就离不开多线程编程。
6. 线程--如何使用
在java中一个类要当作线程来使用有两种方法。
1)、继承Thread类,并重写run函数
2)、实现Runnable接口,并重写run函数
因为java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时在用继承Thread类方法来创建线程显然不可能java设计者们提供了另外一个方式创建线程,就是通过实现Runnable接口来创建线程。
7. 中断线程
Terminated (被终止) 线程被终止的原因有二:
1)一是run()方法中最后一个语句执行完毕而自 然死亡。
2)二是因为一个没有捕获的异常终止了run方法 而意外死亡。
可以调用线程的stop 方 法 杀 死 一 个 线 程 (thread.stop();),但是,stop方法已过时, 不要在自己的代码中调用它。
其他判断和影响线程状态的方法:
1)join():等待指定线程的终止。
2)join(long millis):经过指定时间等待终止指定 的线程。
3)isAlive():测试当前线程是否在活动。
4)yield():让当前线程由“运行状态”进入到“就 绪状态”,从而让其它具有相同优先级的等待线程 获取执行权。
8. 线程状态
Java中线程的状态分为6种。
1). 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
2). 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。
线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。
3). 阻塞(BLOCKED):表示线程阻塞于锁。
4). 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
5). 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
9. 被阻塞线程和等待线程
当线程处于被阻塞或等待状态时,它暂时不活动。它不运行任何代码且消耗最少的资源。直到线程调度器重新激活它。 细节取决于它是怎样达到非活动状态的。
当一个线程试图获取一个内部的对象锁(而不是 javiutiUoncurrent 库中的锁,) 而该锁被其他线程持有, 则该线程进人阻塞状态 。当所有其他线程释放该锁,并且线程调度器允许本线程持有它的时候,该线程将变成非阻塞状态
当线程等待另一个线程通知调度器一个条件时, 它自己进入等待状态有几个方法有一个超时参数。调用它们导致线程进人计时等待(timed waiting) 状态。这一状态将一直保持到超时期满或者接收到适当的通知
10.
被终止的线程
因为一个没有捕获的异常终止了 run 方法而意外死亡
【API】java.iang.Thread 1.0:
等待终止指定的线程。
void join( long millis )
等待指定的线程死亡或者经过指定的毫秒数。
Thread.State getState () 5 . 0
得到这一线程的状态;NEW、RUNNABLE BLOCKED、 WAITING 、TIMED_WAITNG或 TERMINATED 之一。
停止该线程。这一方法已过时
void suspend()
暂停这一线程的执行。这一方法已过时。
void resume()
恢复线程。这一方法仅仅在调用 suspend() 之后调用。
第二部分:实验
1、实验目的与要求
(1) 掌握Java应用程序的打包操作;
(2) 掌握线程概念;
(3) 掌握线程创建的两种技术。
2、实验内容和步骤
实验1: 导入第13章示例程序,测试程序并进行代码注释。
测试程序1
l 在elipse IDE中调试运行教材585页程序13-1,结合程序运行结果理解程序;
l 将所生成的JAR文件移到另外一个不同的目录中,再运行该归档文件,以便确认程序是从JAR文件中,而不是从当前目录中读取的资源。
l 掌握创建JAR文件的方法;
代码如下:
package resource;
import java.awt.*;
import java.io.*;
import java.net.*;
import java.util.*;
import javax.swing.*;
/**
* @version 1.41 2015-06-12
* @author Cay Horstmann
*/
public class ResourceTest
{
public static void main(String[] args)
{
EventQueue.invokeLater(() -> {
JFrame frame = new ResourceTestFrame();
frame.setTitle("ResourceTest");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
});
}
}
/**
* A frame that loads image and text resources.
*/
class ResourceTestFrame extends JFrame
{
private static final int DEFAULT_WIDTH = 300;
private static final int DEFAULT_HEIGHT = 300;
public ResourceTestFrame()
{
setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
URL aboutURL = getClass().getResource("about.gif");//找到指定位置的图像文件,返回一个可以加载资源的URL
Image img = new ImageIcon(aboutURL).getImage();//将加载的about.gif图像设置为图标
setIconImage(img);
JTextArea textArea = new JTextArea();
InputStream stream = getClass().getResourceAsStream("about.txt");//读取about.txt文本文件内容
try (Scanner in = new Scanner(stream, "UTF-8"))//将读取到的about.txt文本文件里内容显示到文本区
{
while (in.hasNext())//读取文本文件
textArea.append(in.nextLine() + "\n");
}
add(textArea);
}
}
以下是归档:
测试程序2:
l 在elipse IDE中调试运行ThreadTest,结合程序运行结果理解程序;
l 掌握线程概念;
l 掌握用Thread的扩展类实现线程的方法;
l 利用Runnable接口改造程序,掌握用Runnable接口创建线程的方法。
class Lefthand extends Thread { public void run() { for(int i=0;i<=5;i++) { System.out.println("You are Students!"); try{ sleep(500); } catch(InterruptedException e) { System.out.println("Lefthand error.");} } } } class Righthand extends Thread { public void run() { for(int i=0;i<=5;i++) { System.out.println("I am a Teacher!"); try{ sleep(300); } catch(InterruptedException e) { System.out.println("Righthand error.");} } } } public class ThreadTest { static Lefthand left; static Righthand right; public static void main(String[] args) { left=new Lefthand(); right=new Righthand(); left.start(); right.start(); } } |
导入代码如下:
class Lefthand extends Thread {
public void run()
{
for(int i=0;i<=5;i++)
{ System.out.println("You are Students!");
try{ sleep(500); }
catch(InterruptedException e)
{ System.out.println("Lefthand error.");}
}
}
}
class Righthand extends Thread {
public void run()
{
for(int i=0;i<=5;i++)
{ System.out.println("I am a Teacher!");
try{ sleep(300); }
catch(InterruptedException e)
{ System.out.println("Righthand error.");}
}
}
}
public class ThreadTest
{
static Lefthand left;
static Righthand right;
public static void main(String[] args)
{ left=new Lefthand();
right=new Righthand();
left.start();
right.start();
}
}
运行结果:
在java中一个类要当作线程来使用有两种方法。
1、继承Thread类,并重写run函数
2、实现Runnable接口,并重写run函数
因为java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时在用继承Thread类方法来创建线程显然不可能java设计者们提供了另外一个方式创建线程,就是通过实现Runnable接口来创建线程。
测试程序3:
l 在Elipse环境下调试教材625页程序14-1、14-2 、14-3,结合程序运行结果理解程序;
l 对比两个程序,理解线程的概念和用途;
l 掌握线程创建的两种技术。
1 package bounce;
2
3 import java.awt.*;
4 import java.util.*;
5 import javax.swing.*;
6
7 public class BallComponent extends JPanel
8 {
9 private static final int DEFAULT_WIDTH = 450;
10 private static final int DEFAULT_HEIGHT = 350;
11
12 private java.util.List<Ball> balls = new ArrayList<>();
13
14 /** 向组件中添加一个球。*/
15 public void add(Ball b)
16 {
17 balls.add(b);
18 }
19
20 public void paintComponent(Graphics g)
21 {
22 super.paintComponent(g); // 擦除背景
23 Graphics2D g2 = (Graphics2D) g;
24 for (Ball b : balls)
25 {
26 g2.fill(b.getShape());
27 }
28 }
29
30 public Dimension getPreferredSize() { return new Dimension(DEFAULT_WIDTH, DEFAULT_HEIGHT); }
31 }
1 package bounce;
2
3 import java.awt.*;
4 import java.awt.event.*;
5 import javax.swing.*;
6
7 public class Bounce
8 {
9 public static void main(String[] args)
10 {
11 EventQueue.invokeLater(() -> {
12 JFrame frame = new BounceFrame();
13 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
14 frame.setVisible(true);
15 });
16 }
17 }
18 /** 带有球组件和按钮的框架。*/
19 class BounceFrame extends JFrame
20 {
21 private BallComponent comp;
22 public static final int STEPS = 1000;
23 public static final int DELAY = 3;
24
25 /**
26 * 用显示弹跳球和的组件构造框架
27 *启动和关闭按钮
28 **/
29 public BounceFrame()
30 {
31 setTitle("Bounce");
32 comp = new BallComponent();
33 add(comp, BorderLayout.CENTER);
34 JPanel buttonPanel = new JPanel();
35 addButton(buttonPanel, "Start", event -> addBall());
36 addButton(buttonPanel, "Close", event -> System.exit(0));
37 add(buttonPanel, BorderLayout.SOUTH);
38 pack();
39 }
40
41 /**
42 * 将按钮添加到容器中
43 *按钮标题
44 * 按钮的动作监听器
45 */
46 public void addButton(Container c, String title, ActionListener listener)
47 {
48 JButton button = new JButton(title);
49 c.add(button);
50 button.addActionListener(listener);
51 }
52
53 /**
54 *向面板中添加一个弹跳球,使其弹跳1000次。
55 */
56 public void addBall()
57 {
58 try
59 {
60 Ball ball = new Ball();
61 comp.add(ball);
62
63 for (int i = 1; i <= STEPS; i++)
64 {
65 ball.move(comp.getBounds());
66 comp.paint(comp.getGraphics());
67 Thread.sleep(DELAY);
68 }
69 }
70 catch (InterruptedException e)
71 {
72 }
73 }
74 }
1 package bounce;
2
3 import java.awt.geom.*;
4
5 /**
6 * 从长方形边缘上移动和弹跳的球
7 */
8 public class Ball
9 {
10 private static final int XSIZE = 15;
11 private static final int YSIZE = 15;
12 private double x = 0;
13 private double y = 0;
14 private double dx = 1;
15 private double dy = 1;
16
17 /**
18 * 将球移动到下一个位置,如果球碰到其中一条边,则反向移动
19 */
20 public void move(Rectangle2D bounds)
21 {
22 x += dx;
23 y += dy;
24 if (x < bounds.getMinX())
25 {
26 x = bounds.getMinX();
27 dx = -dx;
28 }
29 if (x + XSIZE >= bounds.getMaxX())
30 {
31 x = bounds.getMaxX() - XSIZE;
32 dx = -dx;
33 }
34 if (y < bounds.getMinY())
35 {
36 y = bounds.getMinY();
37 dy = -dy;
38 }
39 if (y + YSIZE >= bounds.getMaxY())
40 {
41 y = bounds.getMaxY() - YSIZE;
42 dy = -dy;
43 }
44 }
45
46 /**
47 * 获取球在当前位置的形状。
48 */
49 public Ellipse2D getShape()
50 {
51 return new Ellipse2D.Double(x, y, XSIZE, YSIZE);
52 }
53 }
运行结果:
l 在Elipse环境下调试教材631页程序14-4,结合程序运行结果理解程序;
package bounceThread;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class BounceThread
{
public static void main(String[] args)
{
EventQueue.invokeLater(() -> {
JFrame frame = new BounceFrame();
frame.setTitle("BounceThread");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
});
}
}
class BounceFrame extends JFrame
{
private BallComponent comp;
public static final int STEPS = 1000;
public static final int DELAY = 5;
public BounceFrame()
{
comp = new BallComponent();
add(comp, BorderLayout.CENTER);
JPanel buttonPanel = new JPanel();
addButton(buttonPanel, "Start", event -> addBall());
addButton(buttonPanel, "Close", event -> System.exit(0));
add(buttonPanel, BorderLayout.SOUTH);
pack();
}
public void addButton(Container c, String title, ActionListener listener)
{
JButton button = new JButton(title);
c.add(button);
button.addActionListener(listener);
}
public void addBall() /** 在画布上添加一个弹跳球,并启动一个线程使其运行*/
{
Ball ball = new Ball();
comp.add(ball);
Runnable r = () -> {
try
{
for (int i = 1; i <= STEPS; i++)
{
ball.move(comp.getBounds());
comp.repaint();
Thread.sleep(DELAY);
}
}
catch (InterruptedException e)
{
}
};
Thread t = new Thread(r);
t.start();
}
}
运行结果:
两个实验对比:第一个程序非线程的,执行一次程序要等执行完才能执行下一次;第二个程序是线程的,执行第一次程序不用等执行完可以接着执行第二次操作。
第三部分:实验总结
这节课结束,一整学期的课程就已经结束了。在做后一章节中,主要学习内容是关于对线程的了解,关于什么是线程,线程安全的集合,中断线程,线程的状态等几个方面进行了学习。而通过本次实验感觉自己也有了一定的进步。Java学了一学期,自己也收获了很多,希望在以后,可以将学到的知识运用到实际生活中。