实验十六 线程技术
实验时间 2017-12-8
一 基础知识
程序是一段静态的代码,它是应用程序执行的蓝本。进程是程序的一次动态执行,它对应了从代码加载、执行至执行完毕的一个完整过程。操作系统为每个进程分配一段独立的内存空间和系统资源,包括:代码数据以及堆栈等资源。每一个进程的内部数据和状态都是完全独立的。多任务操作系统中,进程切换对CPU资源消耗较大。
多线程是进程执行过程中产生的多条执行线索。线程是比进程执行更小的单位。线程不能独立存在,必须存在于进程中,同一进程的各线程间共享进程空间的数据。每个线程有它自身的产生、存在和消亡的过程,是一个动态的概念。多线程意味着一个程序的多行语句可以看上去几乎在同一时间内同时运行。线程创建、销毁和切换的负荷远小于进程,又称为轻量级进程。
Java实现多线程有两种途径:创建Thread类的子类;在程序中定义实现Runnable接口的类。
用Thread类的子类创建线程:首先需从Thread类派生出一个子类,在该子类中重写run()方法。然后用创建该子类的对象Lefthand left=new Lefthand(); Righthand right=new Righthand();最后用start()方法启动线程 left.start(); right.start();
用Thread类的子类创建多线程的关键性操作:定义Thread类的子类并实现用户线程操作,即run()方法的实现。在适当的时候启动线程。由于Java只支持单重继承,用这种方法定义的类不可再继承其他父类。
用Runnable()接口实现线程:首先设计一个实现Runnable接口的类;然后在类中根据需要重写run方法;再创建该类对象,以此对象为参数建立Thread 类的对象;调用Thread类对象的start方法启动线程,将 CPU执行权转交到run方法。
Thread(Runnable r):创建一个新线程,它调用r的run(), r是一个实现了Runnable接口的类的实例。
线程两种创建方法比较:实现Runnable接口的优势:符合OO设计的思想;便于用extends继承其它类。采用继承Thread类方法的优点:代码简单。
线程的终止:当线程的run方法执行方法体中最后一条语句后,或者出现了在run方法中没有捕获的异常时,线程将终止,让出CPU使用权。调用interrupt()方法也可终止线程。 void interrupt() :向一个线程发送一个中断请求,同时把这个线程的“interrupted”状态置为true。若该线程处于 blocked 状 态,会抛出 InterruptedException。
测试线程是否被中断的方法:static boolean interrupted() :检测当前线程是否已被中断,并重置状态 “interrupted”值为false。 boolean isInterrupted() :检测当前线程是否已被中断 , 不改变状态 “interrupted”值 。
二 实验部分
1、实验目的与要求
(1) 掌握线程概念;
(2) 掌握线程创建的两种技术;
(3) 理解和掌握线程的优先级属性及调度方法;
(4) 掌握线程同步的概念及实现技术;
2、实验内容和步骤
实验1:测试程序并进行代码注释。
测试程序1:
l 在elipse IDE中调试运行ThreadTest,结合程序运行结果理解程序;
l 掌握线程概念;
l 掌握用Thread的扩展类实现线程的方法;
package aha; //从Thread类中派生出一个子类Lefthand 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.");} } } } //从Thread类中派生出一个子类Lefthand 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();//创建Lefthand类对象 right=new Righthand();//创建Righthand类对象 //用start()方法启动线程 left.start(); right.start(); } }
l 利用Runnable接口改造程序,掌握用Runnable接口创建线程的方法。
package shiyan; //创建实现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.");} } } } //创建实现Runnable类的接口 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 left=new Lefthand(); Thread l=new Thread (left);//转换为 Thread类对象 l.start();//启动线程 Runnable right=new Righthand(); Thread r=new Thread (right);//转换为 Thread类对象 r.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(); } } |
测试程序2:
l 在Elipse环境下调试教材625页程序14-1、14-2 、14-3,结合程序运行结果理解程序;
package bounceThread; 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.List<Ball> balls = new ArrayList<>(); /** * Add a ball to the component. * @param b the ball to add */ public void add(Ball b) { balls.add(b); } public void paintComponent(Graphics g) { super.paintComponent(g); // erase background 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());//将按钮放入buttonPanel addButton(buttonPanel, "Close", event -> System.exit(0)); add(buttonPanel, BorderLayout.SOUTH);//将buttonPanel放入边界管理器的南端 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 panel and makes it bounce 1,000 times. */ 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)//中断异常 { } } }
l 在Elipse环境下调试教材631页程序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.List<Ball> balls = new ArrayList<>(); /** * Add a ball to the component. * @param b the ball to add */ public void add(Ball b) { balls.add(b); } public void paintComponent(Graphics g) { super.paintComponent(g); // erase background 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(); } }
l 对比两个程序,理解线程的概念和用途;
l 掌握线程创建的两种技术。
测试程序3:
分析以下程序运行结果并理解程序。
class Race extends Thread { public static void main(String args[]) { Race[] runner=new Race[4]; for(int i=0;i<4;i++) runner[i]=new Race( ); for(int i=0;i<4;i++) runner[i].start( ); runner[1].setPriority(MIN_PRIORITY); runner[3].setPriority(MAX_PRIORITY);} public void run( ) { for(int i=0; i<1000000; i++); System.out.println(getName()+"线程的优先级是"+getPriority()+"已计算完毕!"); } } |
package fds; class Race extends Thread { public static void main(String args[]) { Race[] runner=new Race[4]; for(int i=0;i<4;i++) runner[i]=new Race( ); for(int i=0;i<4;i++) runner[i].start( ); //改变1号线程与3号线程的优先级 runner[1].setPriority(MIN_PRIORITY); runner[3].setPriority(MAX_PRIORITY);} public void run( ) { for(int i=0; i<1000000; i++);//延时的作用 System.out.println(getName()+"线程的优先级是"+getPriority()+"已计算完毕!"); } }
测试程序4
l 教材642页程序模拟一个有若干账户的银行,随机地生成在这些账户之间转移钱款的交易。每一个账户有一个线程。在每一笔交易中,会从线程所服务的账户中随机转移一定数目的钱款到另一个随机账户。
l 在Elipse环境下调试教材642页程序14-5、14-6,结合程序运行结果理解程序;
package unsynch; import java.util.*; /** *一个有许多银行账户的银行。 * @version 1.30 2004-08-01 * @author Cay Horstmann */ public class Bank { private final double[] accounts; /** * 建造银行 * @param n the number of accounts * @param initialBalance the initial balance for each account */ public Bank(int n, double initialBalance) { accounts = new double[n]; Arrays.fill(accounts, initialBalance); } /** * 把钱从一个账户转到另一个账户。 * @param from the account to transfer from * @param to the account to transfer to * @param amount the amount to transfer */ public void transfer(int from, int to, double amount) { if (accounts[from] < amount) return; System.out.print(Thread.currentThread()); accounts[from] -= amount; System.out.printf(" %10.2f from %d to %d", amount, from, to); accounts[to] += amount; System.out.printf(" Total Balance: %10.2f%n", getTotalBalance()); } /** *获取所有账户余额的总和。 * @return the total balance */ public double getTotalBalance() { double sum = 0; for (double a : accounts) sum += a; return sum; } /** * 获取银行账户的数目 * @return the number of accounts */ public int size() { return accounts.length; } }
package unsynch; /** * This program shows data corruption when multiple threads access a data structure. * @version 1.31 2015-06-21 * @author Cay Horstmann */ public class UnsynchBankTest { public static final int NACCOUNTS = 100; public static final double INITIAL_BALANCE = 1000; public static final double MAX_AMOUNT = 1000; public static final int DELAY = 10; public static void main(String[] args) { Bank bank = new Bank(NACCOUNTS, INITIAL_BALANCE); for (int i = 0; i < NACCOUNTS; i++) { int fromAccount = i; Runnable r = () -> { try { while (true) { int toAccount = (int) (bank.size() * Math.random()); double amount = MAX_AMOUNT * Math.random(); bank.transfer(fromAccount, toAccount, amount); Thread.sleep((int) (DELAY * Math.random())); } } catch (InterruptedException e) { } }; Thread t = new Thread(r); t.start(); } } }
综合编程练习
编程练习1
- 设计一个用户信息采集程序,要求如下:
(1) 用户信息输入界面如下图所示:
(2) 用户点击提交按钮时,用户输入信息显示控制台界面;
(3) 用户点击重置按钮后,清空用户已输入信息;
(4) 点击窗口关闭,程序退出。
运行结果如下
代码如下
package o; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.GridLayout; import javax.swing.BorderFactory; import javax.swing.ButtonGroup; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JRadioButton; import javax.swing.JTextField; public class DemoJFrame extends JFrame { private JPanel jPanel1; private JPanel jPanel2; private JPanel jPanel3; private JPanel jPanel4; private JTextField fieldname; private JComboBox comboBox; private JTextField fieldadress; private ButtonGroup bg; private JRadioButton Male; private JRadioButton Female; private JCheckBox read; private JCheckBox sing; private JCheckBox dance; public DemoJFrame() { // 设置窗口大小 this.setSize(800, 400); // 设置可见性 this.setVisible(true); // 设置标题 this.setTitle("编程练习一"); // 设置关闭操作 this.setDefaultCloseOperation(EXIT_ON_CLOSE); // 设置窗口居中 WinCenter.center(this); // 创建四个面板对象 jPanel1 = new JPanel(); setJPanel1(jPanel1); jPanel2 = new JPanel(); setJPanel2(jPanel2); jPanel3 = new JPanel(); setJPanel3(jPanel3); jPanel4 = new JPanel(); setJPanel4(jPanel4); // 设置容器的为流布局 FlowLayout flowLayout = new FlowLayout(); this.setLayout(flowLayout); // 将四个面板添加到容器中 this.add(jPanel1); this.add(jPanel2); this.add(jPanel3); this.add(jPanel4); } /* * 设置面一 */ private void setJPanel1(JPanel jPanel) { // TODO 自动生成的方法存根 jPanel.setPreferredSize(new Dimension(700, 45)); // 给面板的布局设置为网格布局 一行4列 jPanel.setLayout(new GridLayout(1, 4)); JLabel name = new JLabel("name:"); name.setSize(100, 50); fieldname = new JTextField(""); fieldname.setSize(80, 20); JLabel study = new JLabel("qualification:"); comboBox = new JComboBox(); comboBox.addItem("初中"); comboBox.addItem("高中"); comboBox.addItem("本科"); jPanel.add(name); jPanel.add(fieldname); jPanel.add(study); jPanel.add(comboBox); } /* * 设置面板二 */ private void setJPanel2(JPanel jPanel) { // TODO 自动生成的方法存根 jPanel.setPreferredSize(new Dimension(700, 50)); // 给面板的布局设置为网格布局 一行4列 jPanel.setLayout(new GridLayout(1, 4)); JLabel name = new JLabel("address:"); fieldadress = new JTextField(); fieldadress.setPreferredSize(new Dimension(150, 50)); JLabel study = new JLabel("hobby:"); JPanel selectBox = new JPanel(); selectBox.setBorder(BorderFactory.createTitledBorder("")); selectBox.setLayout(new GridLayout(3, 1)); read = new JCheckBox("reading"); sing = new JCheckBox("singing"); dance = new JCheckBox("danceing"); selectBox.add(read); selectBox.add(sing); selectBox.add(dance); jPanel.add(name); jPanel.add(fieldadress); jPanel.add(study); jPanel.add(selectBox); } /* * 设置面板三 */ private void setJPanel3(JPanel jPanel) { // TODO 自动生成的方法存根 jPanel.setPreferredSize(new Dimension(700, 150)); FlowLayout flowLayout = new FlowLayout(FlowLayout.LEFT); jPanel.setLayout(flowLayout); JLabel sex = new JLabel("性别:"); JPanel selectBox = new JPanel(); selectBox.setBorder(BorderFactory.createTitledBorder("")); selectBox.setLayout(new GridLayout(2, 1)); bg = new ButtonGroup(); Male = new JRadioButton("male"); Female = new JRadioButton("female"); bg.add(Male ); bg.add(Female); selectBox.add(Male); selectBox.add(Female); jPanel.add(sex); jPanel.add(selectBox); } /* * 设置面板四 */ private void setJPanel4(JPanel jPanel) { // TODO 自动生成的方法存根 jPanel.setPreferredSize(new Dimension(700, 150)); FlowLayout flowLayout = new FlowLayout(FlowLayout.CENTER, 50, 10); jPanel.setLayout(flowLayout); jPanel.setLayout(flowLayout); JButton sublite = new JButton("提交"); JButton reset = new JButton("重置"); sublite.addActionListener((e) -> valiData()); reset.addActionListener((e) -> Reset()); jPanel.add(sublite); jPanel.add(reset); } /* * 提交数据 */ private void valiData() { // TODO 自动生成的方法存根 // 拿到数据 String name = fieldname.getText().toString().trim(); String xueli = comboBox.getSelectedItem().toString().trim(); String address = fieldadress.getText().toString().trim(); System.out.println(name); System.out.println(xueli); String hobbystring=""; if (read.isSelected()) { hobbystring+="reading "; } if (sing.isSelected()) { hobbystring+="singing "; } if (dance.isSelected()) { hobbystring+="dancing "; } System.out.println(address); if (Male.isSelected()) { System.out.println("male"); } if (Female.isSelected()) { System.out.println("female"); } System.out.println(hobbystring); } /* * 重置 */ private void Reset() { // TODO 自动生成的方法存根 fieldadress.setText(null); fieldname.setText(null); comboBox.setSelectedIndex(0); read.setSelected(false); sing.setSelected(false); dance.setSelected(false); bg.clearSelection(); } }
package o; import java.awt.EventQueue; import javax.swing.JFrame; public class Main { public static void main(String[] args) { EventQueue.invokeLater(() -> { DemoJFrame page = new DemoJFrame(); }); } }
package o; import java.awt.Dimension; import java.awt.Toolkit; import java.awt.Window; public class WinCenter { public static void center(Window win){ Toolkit tkit = Toolkit.getDefaultToolkit(); Dimension sSize = tkit.getScreenSize(); Dimension wSize = win.getSize(); if(wSize.height > sSize.height){ wSize.height = sSize.height; } if(wSize.width > sSize.width){ wSize.width = sSize.width; } win.setLocation((sSize.width - wSize.width)/ 2, (sSize.height - wSize.height)/ 2); } }
编程练习2
2.创建两个线程,每个线程按顺序输出5次“你好”,每个“你好”要标明来自哪个线程及其顺序号。
package aha; class xiancheng1 extends Thread { public void run() { for(int i=1;i<=5;i++) { System.out.println( i+ "你好~"+"(线程1)"); try{ sleep(500); } catch(InterruptedException e)//异常捕获 { System.out.println("Lefthand error.");} } } } class xiancheng2 extends Thread { public void run() { for(int i=1;i<=5;i++) { System.out.println( i+ "你好~"+"(线程2)"); try{ sleep(500); } catch(InterruptedException e)//异常捕获 { System.out.println("Lefthand error.");} } } } public class xiancheng { //属性 static xiancheng1 xian1; static xiancheng2 xian2; public static void main(String[] args) { xian1=new xiancheng1(); xian2=new xiancheng2(); //用start()方法启动线程 xian1.start(); xian2.start(); } }
3. 完善实验十五 GUI综合编程练习程序。
实验总结:掌握线程概念和线程创建的两种技术;理解和掌握了线程的优先级属性及调度方法;掌握了线程同步的概念及实现技术;