|
内容 |
这个作业属于哪个课程 |
https://www.cnblogs.com/nwnu-daizh/ |
这个作业的要求在哪里 |
https://www.cnblogs.com/nwnu-daizh/p/12031970.html |
作业学习目标 |
(1) 掌握Java应用程序的打包操作; (2) 掌握线程概念; (3) 掌握线程创建的两种技术。 |
随笔博文正文内容包括:
随笔博文正文内容包括:
第一部分:总结教材14.1-14.3知识内容(20分)
14.1 线程的相关知识
14.1.1.理解程序、进程、线程的概念
程序可以理解为静态的代码
进程可以理解为执行中的程序。
线程可以理解为进程的进一步细分,程序的一条执行路径
14.1.2.如何创建java程序的线程
方式一:
继承于Thread类
class PrintNum extends Thread{ public void run()( //子线程执行的代码 for(int i= 1;i <= 100;i++) if(i % 2 == 0){ System.out.printlnThread.curhrad().getname()+ “:” + i); } } } public PrintNum(String name){ super(name); } } public class TestThread { public static void main(String[] args) { PrintNum p1 = new PrintNum(”线程1"); PrintNum p2 = new PrintNum(“线程2”); p1.setPriority(Thread.MAX_PRIORITY); //10 p2.setPriority(Thread.MIN_PRIORITY);//1 p1.start(); p2.start(); } }
方式二:实现Runnable接口
class SubThread implements Runnable{ public void run()( //子线程执行的代码 for(int i = 1;i <= 100;i++){ if(i % 2 == 0){ System.out.println(Thread.currentThread().getName() + “:” + i); } } } } public class TestThread{ public static void main(String[] args){ SubThread s = new SubThread(); Thread t1 = new Thread(s); Thread t2 = new Thread(s); t1.setName(“线程1”); t2.setName(“线程2”); t1.start(); t2.start(); } }
两种方式的对比:
联系:class Thread implements Runnable
比较:实现的方式较好。
原因:①解决了单继承的局限性。②如果多个线程有共享数据的话,建议使用实现方式,同时,共享数据所在的类可以作为Runnable接口的实现类。
线程里的常用方法:
start() run() currentThread() getName() setName(String name)
yield() join() sleep() isAlive() getPriority() setPriority(int i); wait() notify() notifyAll
14.1.3.线程的生命周期
4.线程的同步机制
前提:
如果我们创建的多个线程,存在着共享数据,那么就有可能出现线程的安全问题:当其中一个线程操作共享数据时,还未操作完成,另外的线程就参与进来,导致对共享数据的操作出现问题。
解决方式:
要求一个线程操作共享数据时,只有当其完成操作完成共享数据,其它线程才有机会执行共享数据。
方式一:同步代码块:
synchronized(同步监视器){
//操作共享数据的代码
}
方式二:同步方法:将操作共享数据的方法声明为synchronized。
比如: public synchronized void show(){ //操作共享数据的代码}
14.1.5.线程的通信:
如下的三个方法必须使用在同步代码块或同步方法中!
wait():当在同步中,执行到此方法,则此线程“等待”, 直至其他线程执行notify()的方法,将其唤醒,唤醒后继续其wait()后的代码notify()/notifyAll():在同步中,执行到此方法,则唤醒其他的某一个或所有的被wait的线程。
14.2 中断线程
14.2.1中断的原理
Java中断机制是一种协作机制,也就是说通过中断并不能直接终止另一个线程,而需要被中断的线程自己处理中断。这好比是家里的父母叮嘱在外的子女要注意身体,但子女是否注意身体,怎么注意身体则完全取决于自己。
Java中断模型也是这么简单,每个线程对象里都有一个boolean类型的标识(不一定就要是Thread类的字段,实际上也的确不是,这几个方法最终都是通过native方法来完成的),代表着是否有中断请求(该请求可以来自所有线程,包括被中断的线程本身)。例如,当线程t1想中断线程t2,只需要在线程t1中将线程t2对象的中断标识置为true,然后线程2可以选择在合适的时候处理该中断请求,甚至可以不理会该请求,就像这个线程没有被中断一样。
java.lang.Thread类提供了几个方法来操作这个中断状态,这些方法包括:
-
public static boolean interrupted
测试当前线程是否已经中断。线程的中断状态 由该方法清除。换句话说,如果连续两次调用该方法,则第二次调用将返回 false(在第一次调用已清除了其中断状态之后,且第二次调用检验完中断状态前,当前线程再次中断的情况除外)。 -
public boolean isInterrupted()
测试线程是否已经中断。线程的中断状态不受该方法的影响。 -
public void interrupt()
中断线程。
其中,interrupt方法是唯一能将中断状态设置为true的方法。静态方法interrupted会将当前线程的中断状态清除,但这个方法的命名极不直观,很容易造成误解,需要特别注意。
上面的例子中,线程t1通过调用interrupt方法将线程t2的中断状态置为true,t2可以在合适的时候调用interrupted或isInterrupted来检测状态并做相应的处理。
此外,类库中的有些类的方法也可能会调用中断,如FutureTask中的cancel方法,如果传入的参数为true,它将会在正在运行异步任务的线程上调用interrupt方法,如果正在执行的异步任务中的代码没有对中断做出响应,那么cancel方法中的参数将不会起到什么效果;又如ThreadPoolExecutor中的shutdownNow方法会遍历线程池中的工作线程并调用线程的interrupt方法来中断线程,所以如果工作线程中正在执行的任务没有对中断做出响应,任务将一直执行直到正常结束。
14.2.2.中断的处理
既然Java中断机制只是设置被中断线程的中断状态,那么被中断线程该做些什么?
14.2.2.1处理时机
显然,作为一种协作机制,不会强求被中断线程一定要在某个点进行处理。实际上,被中断线程只需在合适的时候处理即可,如果没有合适的时间点,甚至可以不处理,这时候在任务处理层面,就跟没有调用中断方法一样。“合适的时候”与线程正在处理的业务逻辑紧密相关,例如,每次迭代的时候,进入一个可能阻塞且无法中断的方法之前等,但多半不会出现在某个临界区更新另一个对象状态的时候,因为这可能会导致对象处于不一致状态。
处理时机决定着程序的效率与中断响应的灵敏性。频繁的检查中断状态可能会使程序执行效率下降,相反,检查的较少可能使中断请求得不到及时响应。如果发出中断请求之后,被中断的线程继续执行一段时间不会给系统带来灾难,那么就可以将中断处理放到方便检查中断,同时又能从一定程度上保证响应灵敏度的地方。当程序的性能指标比较关键时,可能需要建立一个测试模型来分析最佳的中断检测点,以平衡性能和响应灵敏性。
14.2.2.1处理方式
1、 中断状态的管理
一般说来,当可能阻塞的方法声明中有抛出InterruptedException则暗示该方法是可中断的,如BlockingQueue#put、BlockingQueue#take、Object#wait、Thread#sleep等,如果程序捕获到这些可中断的阻塞方法抛出的InterruptedException或检测到中断后,这些中断信息该如何处理?一般有以下两个通用原则:
- 如果遇到的是可中断的阻塞方法抛出InterruptedException,可以继续向方法调用栈的上层抛出该异常,如果是检测到中断,则可清除中断状态并抛出InterruptedException,使当前方法也成为一个可中断的方法。
- 若有时候不太方便在方法上抛出InterruptedException,比如要实现的某个接口中的方法签名上没有throws InterruptedException,这时就可以捕获可中断方法的InterruptedException并通过Thread.currentThread.interrupt()来重新设置中断状态。如果是检测并清除了中断状态,亦是如此。
一般的代码中,尤其是作为一个基础类库时,绝不应当吞掉中断,即捕获到InterruptedException后在catch里什么也不做,清除中断状态后又不重设中断状态也不抛出InterruptedException等。因为吞掉中断状态会导致方法调用栈的上层得不到这些信息。
当然,凡事总有例外的时候,当你完全清楚自己的方法会被谁调用,而调用者也不会因为中断被吞掉了而遇到麻烦,就可以这么做。
总得来说,就是要让方法调用栈的上层获知中断的发生。假设你写了一个类库,类库里有个方法amethod,在amethod中检测并清除了中断状态,而没有抛出InterruptedException,作为amethod的用户来说,他并不知道里面的细节,如果用户在调用amethod后也要使用中断来做些事情,那么在调用amethod之后他将永远也检测不到中断了,因为中断信息已经被amethod清除掉了。如果作为用户,遇到这样有问题的类库,又不能修改代码,那该怎么处理?只好在自己的类里设置一个自己的中断状态,在调用interrupt方法的时候,同时设置该状态,这实在是无路可走时才使用的方法。
2、 中断的响应
程序里发现中断后该怎么响应?这就得视实际情况而定了。有些程序可能一检测到中断就立马将线程终止,有些可能是退出当前执行的任务,继续执行下一个任务……作为一种协作机制,这要与中断方协商好,当调用interrupt会发生些什么都是事先知道的,如做一些事务回滚操作,一些清理工作,一些补偿操作等。若不确定调用某个线程的interrupt后该线程会做出什么样的响应,那就不应当中断该线程。
14.2.3 . 中断的使用
通常,中断的使用场景有以下几个:
- 点击某个桌面应用中的取消按钮时;
- 某个操作超过了一定的执行时间限制需要中止时;
- 多个线程做相同的事情,只要一个线程成功其它线程都可以取消时;
- 一组线程中的一个或多个出现错误导致整组都无法继续时;
14.3 线程状态
14.3.1 Thread和Runnable实现线程的异同
相同点:
都是多线程实现的方式
不同点:
- Thread是类,而Runnable是接口;Thread是实现了Runnable接口的类。
- Runnable具有更好的扩展性,即多个线程都是基于某一个Runnable对象建立的,它们会共享Runnable对象的资源。
14.3.2 Thread类包含的start()和Run()方法的区别
- start():它的作用是启动一个新的线程,新线程会执行相应的Run()方法;start()不能被重复调用。
- run():与普通的成员方法一样,可以被重复调用。单独调用会在当前线程中执行run(),而不会启动新线程。
14.3.3 线程状态sleep()、yield()、wait()区别
- sleep()会给其他线程运行的机会,而不考虑其他线程的优先级,因此会给较低优先级的线程一个运行的机会;yield()只会给不小于自己优先级的线程一个运行的机会。
- 当线程执行sleep()方法后,参数long millis指定睡眠时间,转到阻塞状态;当线程执行yield()方法后进入就绪状态。
- sleep()方法声明抛出InterruptedException异常,而yield()方法没有声明异常。
- sleep()比yield()方法具有更好的移植性
- 线程调用自身的sleep()或者其他线程的join()方法,进入阻塞状态,该状态停止当前线程但不释放资源,当sleep()或者join()的线程结束以后进入就绪状态
- Object.wait()对象锁,释放资源,notify()唤醒回到wait()前中断现场。
第二部分:实验部分
1、实验目的与要求
(1) 掌握Java应用程序的打包操作;
(2) 掌握线程概念;
(3) 掌握线程创建的两种技术。
2、实验内容和步骤
实验1: 导入第13章示例程序,测试程序并进行代码注释。
测试程序1
l 在elipse IDE中调试运行教材585页程序13-1,结合程序运行结果理解程序;
l 将所生成的JAR文件移到另外一个不同的目录中,再运行该归档文件,以便确认程序是从JAR文件中,而不是从当前目录中读取的资源。
l 掌握创建JAR文件的方法;
例题13.1程序代码及注释如下:
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"); System.out.println(aboutURL); Image img = new ImageIcon(aboutURL).getImage();//利用about.gif图像文件制作图标 setIconImage(img); JTextArea textArea = new JTextArea();//创建一个textArea对象 InputStream stream = getClass().getResourceAsStream("about.txt");//读取about.txt文件 try (Scanner in = new Scanner(stream, "UTF-8")) { while (in.hasNext()) textArea.append(in.nextLine() + "\n"); } add(textArea); } }
运行截图如下:
jar 文件的生成:
测试程序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 implements Runnable{ public void run() { for(int i=0;i<=5;i++) { System.out.println("You are Students!"); try{ Thread.sleep(500); } catch(InterruptedException e) { System.out.println("Lefthand error.");} } } } class Righthand implements Runnable { public void run() { for(int i=0;i<=5;i++) { System.out.println("I am a Teacher!"); try{ Thread.sleep(300); } catch(InterruptedException e) { System.out.println("Righthand error.");} } } } public class ThreadTest { static Thread left; static Thread right; public static void main(String[] args) { Runnable lefthand = new Lefthand(); left=new Thread(lefthand); left.start(); Runnable righthand = new Righthand(); right=new Thread(righthand); right.start(); } }
Runnable接口创建
class Lefthand implements Runnable{ public void run() { for(int i=0;i<=5;i++) { System.out.println("You are Students!"); try{ Thread.sleep(500); } catch(InterruptedException e) { System.out.println("Lefthand error.");} } } } class Righthand implements Runnable { public void run() { for(int i=0;i<=5;i++) { System.out.println("I am a Teacher!"); try{ Thread.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) { Runnable lefthand = new Lefthand(); Thread left=new Thread(lefthand); left.start(); Runnable righthand = new Righthand(); Thread right=new Thread(righthand); right.start(); } }
运行截图如下:
测试程序3:
l 在Elipse环境下调试教材625页程序14-1、14-2 、14-3,结合程序运行结果理解程序;
l 在Elipse环境下调试教材631页程序14-4,结合程序运行结果理解程序;
l 对比两个程序,理解线程的概念和用途;
l 掌握线程创建的两种技术。
例题14.1 14.2 14.3程序代码如下:
package bounce; import java.awt.geom.*; /** * A ball that moves and bounces off the edges of a rectangle * 一个在长方形边缘移动或反弹的球 * @version 1.33 2007-05-17 * @author Cay Horstmann */ public class Ball { private static final int XSIZE = 15; private static final int YSIZE = 15; private double x = 0; private double y = 0; private double dx = 1; private double dy = 1; /** * Moves the ball to the next position, reversing direction if it hits one of the edges * 将球移动到下一个位置,碰到其中一个边,就反转方向 */ public void move(Rectangle2D bounds) { x += dx; y += dy; //宽度上的位置与高度上的位置的设置 if (x < bounds.getMinX()) { x = bounds.getMinX(); dx = -dx; } if (x + XSIZE >= bounds.getMaxX()) { x = bounds.getMaxX() - XSIZE; dx = -dx; } if (y < bounds.getMinY()) { y = bounds.getMinY(); dy = -dy; } if (y + YSIZE >= bounds.getMaxY()) { y = bounds.getMaxY() - YSIZE; dy = -dy; } } /** * Gets the shape of the ball at its current position. * 获取球在其当前位置的形状 */ public Ellipse2D getShape() { return new Ellipse2D.Double(x, y, XSIZE, YSIZE); } }
package bounce; import java.awt.*; import java.util.*; import javax.swing.*; /** * The component that draws the balls. * 在组件上画一个球 * @version 1.34 2012-01-26 * @author Cay Horstmann */ public class BallComponent extends JPanel { private static final int DEFAULT_WIDTH = 450; private static final int DEFAULT_HEIGHT = 350; private java.util.Listballs = new ArrayList<>(); /** * Add a ball to the component. * 添加一个球在组件上 * @param b the ball to add */ public void add(Ball b) { balls.add(b); //创建add方法,添加球 } public void paintComponent(Graphics g) { super.paintComponent(g); // 删除背景 Graphics2D g2 = (Graphics2D) g; for (Ball b : balls) { g2.fill(b.getShape());//获取每一个球的位置和形状 } } public Dimension getPreferredSize() { return new Dimension(DEFAULT_WIDTH, DEFAULT_HEIGHT); } }
package bounce; import java.awt.*; import java.awt.event.*; import javax.swing.*; /** * Shows an animated bouncing ball. * 显示动画弹跳球。 * @version 1.34 2015-06-21 * @author Cay Horstmann */ public class Bounce { public static void main(String[] args) { EventQueue.invokeLater(() -> { JFrame frame = new BounceFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); }); } } /** * The frame with ball component and buttons. */ class BounceFrame extends JFrame { private BallComponent comp; public static final int STEPS = 1000; public static final int DELAY = 3; /** * Constructs the frame with the component for showing the bouncing ball and * Start and Close buttons */ public BounceFrame() { setTitle("Bounce"); 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(); } /** * 向容器添加按钮. * @param c the container * @param 为其设置标题 * @param 为其设置监听器 */ public void addButton(Container c, String title, ActionListener listener) { JButton button = new JButton(title); c.add(button); button.addActionListener(listener); } /** * Adds a bouncing ball to the panel and makes it bounce 1,000 times. * 在面板中添加一个弹跳球,使其弹跳1000次。 */ public void addBall() { try { Ball ball = new Ball(); comp.add(ball); for (int i = 1; i <= STEPS; i++) { ball.move(comp.getBounds()); comp.paint(comp.getGraphics()); Thread.sleep(DELAY); } } catch (InterruptedException e)//异常的中断 { } } }
例题14.1 14.2 14.3运行截图如下:
例题14.4程序代码如下:
package bounce; import java.awt.geom.*; /** * A ball that moves and bounces off the edges of a rectangle * 一个在长方形边缘移动或反弹的球 * @version 1.33 2007-05-17 * @author Cay Horstmann */ public class Ball { private static final int XSIZE = 15; private static final int YSIZE = 15; private double x = 0; private double y = 0; private double dx = 1; private double dy = 1; /** * Moves the ball to the next position, reversing direction if it hits one of the edges * 将球移动到下一个位置,碰到其中一个边,就反转方向 */ public void move(Rectangle2D bounds) { x += dx; y += dy; //宽度上的位置与高度上的位置的设置 if (x < bounds.getMinX()) { x = bounds.getMinX(); dx = -dx; } if (x + XSIZE >= bounds.getMaxX()) { x = bounds.getMaxX() - XSIZE; dx = -dx; } if (y < bounds.getMinY()) { y = bounds.getMinY(); dy = -dy; } if (y + YSIZE >= bounds.getMaxY()) { y = bounds.getMaxY() - YSIZE; dy = -dy; } } /** * Gets the shape of the ball at its current position. * 获取球在其当前位置的形状 */ public Ellipse2D getShape() { return new Ellipse2D.Double(x, y, XSIZE, YSIZE); } }
package bounce; import java.awt.*; import java.util.*; import javax.swing.*; /** * The component that draws the balls. * 在组件上画一个球 * @version 1.34 2012-01-26 * @author Cay Horstmann */ public class BallComponent extends JPanel { private static final int DEFAULT_WIDTH = 450; private static final int DEFAULT_HEIGHT = 350; private java.util.Listballs = new ArrayList<>(); /** * Add a ball to the component. * 添加一个球在组件上 * @param b the ball to add */ public void add(Ball b) { balls.add(b); //创建add方法,添加球 } public void paintComponent(Graphics g) { super.paintComponent(g); // 删除背景 Graphics2D g2 = (Graphics2D) g; for (Ball b : balls) { g2.fill(b.getShape());//获取每一个球的位置和形状 } } public Dimension getPreferredSize() { return new Dimension(DEFAULT_WIDTH, DEFAULT_HEIGHT); } }
package bounceThread; import java.awt.*; import java.awt.event.*
; import javax.swing.*; /** * Shows animated bouncing balls. * 显示动画弹跳球 * @version 1.34 2015-06-21 * @author Cay Horstmann */ 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);//创建框架及标题,并将其设置为可见 }); } } /** * The frame with panel and buttons. * 框架存在面板与按钮 */ class BounceFrame extends JFrame { private BallComponent comp; public static final int STEPS = 1000; public static final int DELAY = 5; /** * Constructs the frame with the component for showing the bouncing ball and * Start and Close buttons * 构造包含用于显示弹跳球和开始和关闭按钮的组件的框架 */ 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(); } /** * Adds a button to a container. * @param c the container * @param title the button title * @param listener the action listener for the button */ public void addButton(Container c, String title, ActionListener listener) { JButton button = new JButton(title); c.add(button); button.addActionListener(listener); } /** * Adds a bouncing ball to the canvas and starts a thread to make it bounce * 在画布上添加一个弹跳球并使开始时在一条线使其弹跳 */ 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(); } }
例题14.4程序代码如下:
运行截图如下:
总结:bounceThread中运用了线程,它的start键可以多次点击,小球可以运动而互不影响
实验总结:
1):通过本次课程的学习让我们对Java中第十四章相关内容有了了解,第十四章主要是线程的相关知识,在上课老师的讲解过程中我们对线程有了初步了解,掌握Java应用程序的打包操作;掌握线程概念; 掌握线程创建的两种技术等相关内容。
2):通过老师上课详细的讲解和实验过程中对不懂得知识的查找资料及同学的讲解我们对第十四章的知识点有了进一步的强化认识,在做作业的过程中遇到了一些问题,但都通过问同学或查资料一一解决了,在讨论的过程中了解了自己的不足,掌握了许多不知道不熟悉的知识,由于对知识点了解的不透彻,在结对编程的过程中遇到了许多问题,意识到在课下应该多看书及查看资料来巩固上课所学的知识有进一步熟悉及掌握。
3).通过这次的实验使我学到了不少实用的知识,并且锻炼了自己的动手能力;虽然在学习的过程中遇到了许多不懂得地方,但是课下都通过问同学及查找相关资料等一些方式都一一解决了;在这次实验中意识到自己的动手能力很差,应在课下多学习多交流,相信在以后的学习中,通过不断努力,我可以学到更多的关于Java编程的相关知识。