201871010132--张潇潇--《面向对象程序设计(java)》第十七周学习总结

博文正文开头格式:(2分)

项目

内容

这个作业属于哪个课程

 https://www.cnblogs.com/nwnu-daizh/

这个作业的要求在哪里

   https://www.cnblogs.com/nwnu-daizh/p/11435127.html

作业学习目标

(1) 掌握菜单组件用途及常用API;

(2) 掌握对话框组件用途及常用API;

(3) 学习设计简单应用程序的GUI。

随笔博文正文内容包括:

第一部分:总结线程同步技术(10分)

14.5 同步
如果两个线程存取相同的对象,并且每一个线程都调用了一个修改该对象状态的方法,这样一个情况通常称为竞争条件( race condition )。
14.5.1 竞争条件的一个例子
银行转账例子
14.5.2 竞争条件详解
一条名利是由几条指令组成的,执行它们的线程可以在任何一条指令点上被中断。
14.5.3 锁对象
有两种机制防止代码块受并发访问的干扰。Java语言提供一个 synchronized 关键字达到这一目的,并且 Java SE 5.0 引入了 ReentrantLock 类。 synchronized 关键字自动提供了一个锁以及相关的“条件”,对于大多数需要显示锁的情况,这是很遍历的。 java.util.concurrent 框架为这些基础机制提供独立的类。
用 ReentrantLock 保护代码块的基本结构如下:
 myLock.lock(); //a ReentrantLock object
 try
 {
     critical section
 }
 finally
 {
     myLock.unlock();//make sure the lock is unlocked even if an exception is three
 }
这一结构确保任何时刻只有一个线程进入临界区。一旦一个线程封锁了锁对象,其他任何线程都无法通过 lock 语句。当其他线程调用 lock 时,它们被阻塞,直到第一个线程释放锁对象。
把解锁操作放在 finally 子句之内是至关重要的。如果在临界区的代码抛出异常,锁必须被释放。否则,其他线程将永远阻塞。
如果使用锁,就不能使用带资源的 try 语句。首先,解锁方法名不是 close 。不过,即使将它重命名,带资源的 try 语句也无法正常工作。它的首部希望声明一个新变量。但是如果使用一个锁,可能想使用多个线程共享的那个变量(而不是新变量)。
锁是可重入的,因为线程可以重复地获得已经持有的锁。锁保持一个持有计数( hold count )来跟踪对 lock 方法的嵌套调用。线程在每一次调用lock都要调用 unlock 来释放锁。由于这一特性,被一个锁保护的代码可以调用另一个使用相同的锁的方法。
通常,可能想要保护需若干个操作来更新或检查共享对象的代码块。要确保这些操作完成后,另一个线程才能使用相同对象。
要留心临界区中的代码,不要因为异常的抛出而跳出了临界区。如果在临界区代码结束之前抛出了异常, finally 子句将释放锁,但会使对象可能处于一种受损状态。
java.util.concurrent.locks.Lock 5.0
void lock()
获取这个锁;如果锁同时被另一个线程拥有则发生阻塞。
void unlock()
释放这个锁。
java.util.concurrent.locks.ReentrantLock 5.0
ReentrantLock()
构建一个可以被用来保护临界区的可重入锁。
ReentrantLock(boolean fair)
构建一个带有公平策略的锁。一个公平锁偏爱等待时间最长的线程。但是么这一公平的保证将大大降低性能。所以,默认情况下,锁没有被强制为公平的。
听起来公平锁更合理一些,但是使用公平锁比使用常规锁要慢很多。只有当你确实了解自己要做什么并且对于你要解决的问题有一个特定的理由必须使用公平锁的时候,才可以使用公平锁。即使使用公平锁,也无法确保线程调度器是公平的。如果线程调度器选择忽略一个线程,而该线程为了这个锁已经等待了很长时间,那么就没有机会公平地处理这个锁了。
14.5.4 条件对象
通常,线程进入临界区,却发现在某一条件满足之后它才能执行。要使用一个条件对象来管理那些已经获得了一个锁但是却不能做有用工作的线程。由于历史的原因,条件对象经常被称为条件变量( conditional variable )。
一个锁对象可以有一个或多个相关的条件对象。可以用 newCondition 方法获得一个条件对象。习惯上给每一个条件对象命名为可以反映它锁表达的条件的名字。如果条件不满足,调用 Condition.await() 。当前线程现在被阻塞了,并放弃了锁。
等待获得锁的线程和调用 await 方法的线程存在本质上的不同。一旦一个线程调用 await 方法,它进入该条件的等待集。当锁可用时,该线程不能马上解除阻塞。相反,它处于阻塞状态,直到另一个线程调用同一条件上的 singalAll 方法时为止。 singalAll() 调用重新激活因为这一条件而等待的所有线程。当这些线程从等待集中移出时,它们再次成为可运行的,调度器将再次激活它们。同时,它们将试图重新进入该对象。一旦锁成为可用的,它们中的某个将从 await 调用返回,获得该锁并从被阻塞的地方继承执行。
此时,线程应该再次测试该条件。由于无法确保该条件被满足 -signalAll 方法仅仅是通知正在等待的线程:此时有可能已经满足条件,值得再次去检测该条件。
通常,对 await 的调用应该在如下形式的循环体中:
 while(!(ok to proceed))
     condition.await();
至关重要的是最终需要某个其他线程调用 signalAll 方法。当一个线程调用 await 时,它没有办法重新激活自身。它寄希望于其他线程。如果没有其他线程来重新激活等待的线程,它就永远不再运行了。这将导致令人不快的死锁( deadlock )现象。如果所有其他线程被阻塞,最后一个活动线程在解除其他线程的阻塞状态之前就调用 await 方法,那么它也被阻塞。没有任何线程可以解除其他线程的阻塞,那么该程序就挂起了。
应该何时调用 signalAll 呢?经验上讲,在对象的状态有利于等待线程的方向改变时调用 signalAll 。
注意调用 signalAll 不会立即激活一个等待线程。它仅仅解除等待线程的阻塞,以便这些线程可以在当前线程退出同步方法之后,通过竞争实现对对象的访问。
另一个方法 signal ,则是随机解除等待集中某个线程的阻塞状态。这比解除所有线程的阻塞更加有效,但也存在危险。如果随机选择的线程发现自己仍然不能运行,那么它再次被阻塞。如果没有其他线程再次调用 signal ,那么系统就死锁了。
当一个线程拥有某个条件的锁时,它仅仅可以在该条件上调用 await 、 signalAll 或 signal 方法。
java.util.concurrent.locks.Lock 5.0
Condition newCondition()
返回一个与该锁相关的条件对象。
java.util.concurrent.locks.Condition 5.0
void await()
将该线程放到条件的等待集中。
void signalAll()
解除该条件的等待集中的所有线程的阻塞状态。
void signal()
从该条件的等待集中随机地选择一个线程,解除其阻塞状态。
14.5.5 synchronized关键字
锁和条件的关键之处:
锁用来保护代码片段,任何时刻只能有一个线程执行被保护的代码。
锁可以管理试图进入被保护代码段的线程。
锁可以拥有一个或多个相关的条件对象。
每个条件对象管理那些已经进入被保护的代码段但还不能运行的线程。
从 1.0 版开始, Java 中的每一个对象都有一个内部锁。如果一个方法用 synchronized 关键字声明,那么对象的锁将保护整个方法。也就是说,要调用该方法,线程必须获得内部的对象锁。
内部对象锁只有一个相关条件。 wait 方法添加一个线程到等待集中, notifyAll/notify 方法解除等待线程的阻塞状态。换句话说,调用 wait 或 notifyAll 等价于
 intrinsicCondition.await();
 intrinsicCondition.signalAll();
wait 、 notifyAll 以及 notify 方法是 Object 类的 final 方法。 Condition 方法必须被命名为 await 、 signalAll 和 signal 以便它们不会与那些方法发生冲突。
将静态方法声明为 synchronized 也是合法的。如果调用这个方法,该方法获得相关的类对象的内部类。因此,没有其他线程可以调用同一个类的这个或任何其他的同步静态方法。
内部锁和条件存在一些局限。包括:
不能中断一个正在试图获得锁的线程。
试图获得锁时不能设定超时。
每个锁仅有单一的条件,可能是不够的。
在代码中应该使用哪一种? Lock 和 Conditon 对象还是同步方法?下面是一些建议:
最好既不使用 Lock/Condition 也不实用 synchronized 关键字。在许多情况下你可以使用 java.util.concurrent 包中的一种机制,它会为你处理所有的加锁。
如果 synchronized 关键字适合你的程序,那么请尽量使用它。这样可以减少编写的代码数量,减少出错的几率。
如果特别需要 Lock/Condition 结构提供的特有特性时,才使用 Lock/Condition 。
java.lang.Object 1.0
void notifyAll()
解除那些在该对象上调用 wait 方法的线程的阻塞状态。该方法只能在同步方法或同步块内部调用。如果当前线程不是对象锁的持有者,该方法抛出一个 IllegalMonitorStateException 异常。
void notify()
随机选择一个在该对象上调用 wait 方法的线程,解除其阻塞状态。改方法只能在一个同步方法或同步块中调用。如果当前线程不是对象锁的持有者,该方法抛出一个 IllegalMonitorStateException 异常。
void wait()
导致线程进入等待状态直到它被通知。该方法只能在一个同步方法中调用。如果当前线程不是对象锁的持有者,该方法抛出一个 IllegalMonitorStateException 异常。
void wait(long millis)
void wait(long millis,int nanos)
导致线程进入等待状态直到它被通知或者经过指定的时间。这些方法只能在一个同步方法中调用。如果当前线程不是对象锁的持有者该方法抛出一个 IllegalMonitorStateException 异常。
参数:millis 毫秒数
nanos 纳秒数,<1 000 000
14.5.6 同步阻塞
每一个 Java 对象有一个锁。线程可以通过调用同步方法获得锁。还有另一种机制可以获得锁,通过进入一个同步阻塞。当线程进入如下形式的阻塞:
 synchronized(obj) //this is the     synchronized block
 {
     critical section
 }
于是它获得 obj 的锁。
使用一个对象的锁来实现额外的原子操作,实际上称为客户端锁定( client-slide locking )。客户端锁定是非常脆弱的,通常不推荐使用。
14.5.7 监视器概念
锁和条件是线程同步的强大工具,但是,严格来讲,它们不是面向对象的。多年来,研究人员努力寻找一种方法,可以在不需要程序员考虑如何加锁的情况下,就可以保证多线程的安全性。最成功的解决方案之一是监视器( monitor ),这一概念最早是由 Per Brinch Hansen 和 Tony Hoare 在 20 世纪 70 年代提出的。用 Java 的术语来讲,监视器具有如下特性:
监视器是只包含私有域的类。
每个监视器的对象有一个相关的锁。
使用该锁对所有的方法进行加锁。
该锁可以有任意多个相关条件。
Java 设计者以不是很精确的方式采用了监视器概念, Java 中的每一个对象有一个内部的锁和内部的条件。如果一个方法用 synchronized 关键字声明,那么,它表现的就像是一个监视器方法。通过调用 wait/notifyAll/notify 访问条件变量。
在下述的 3 个方面 Java 对象不同于监视器,从而使得线程的安全性下降。
域不要求必须是 private 。
方法不要求必须是 synchronized 。
内部锁对客户是可用的。
14.5.8 Volatile 域
有时,仅仅为了读写一个或两个实例域就使用同步,显得开销过大了。毕竟,什么地方能出错呢?遗憾的是,使用现代的处理器与编译器,出错的可能性很大。
多处理器的计算机能够暂时在寄存器或本地内存缓冲区中保存内存中的值。结果是,运行在不同处理器上的线程可能在同一个内存位置取到不同的值。
编译器可以改变指令执行的顺序以使吞吐量最大化。这种顺序上的变化不会改变代码语义,但是编译器假定内存的值仅仅在代码中有显式的修改指令时才会改变。然而,内存的值可以被另一个线程改变。
如果你使用锁来保护可以被多个线程访问的代码,那么可以不考虑这种问题。编译器被要求通过在必要的时候刷新本地缓存来保持锁的效应,并且不能不正当地重新排序指令。
volatile 关键字为实例域的同步访问提供了一种免锁机制。如果声明一个域为 volatile ,那么编译器和虚拟机就知道该域是可能被另一个线程并发更新的。
Volatile 变量不能提供原子性。例如,方法
private volatile boolean done;
public void flipDone(){done = !done;} //not atomic
不能确保翻转域中的值。
14.5.9 final 变量
除非使用域或 volatile 修饰符,否则无法从多个线程安全地读取一个域。还有一种情况可以安全地访问一个共享域,即这个域声明为 final 时。考虑以下声明:
 final Map<String,Double> accounts = new HashMap();
其他线程会在构造函数完成构造之后才看到这个 accounts 变量。
如果不使用 final ,就不能保证其他线程看到的是 accounts 更新后的值,它们可能都只是看到 null ,而不是新构造的 HashMap 。
对这个映射表的操作并不是线程安全的。如果多个线程在读写这个映射表,仍然需要进行同步。
14.5.10 原子性
假设对共享变量除了赋值之外并不完成其他操作,那么可以将这些共享变量声明为 volatic 。
java.util.concurrent.atomic 包中有很多类使用了很高效的机器级指令(而不是使用锁)来保证其他操作的原子性。例如, AtomicInteger 类提供了方法 incrementAndGet 和 decrementAndGet ,它们分别以原子方式将一个整数自增或自减。可以安全地使用 AtomicInteger 作为共享计数器而无须同步。
另外这个包中还包含 AtomicBoolean 、 AtomicLong 和 AtomicReference 以及 Boolean 值、整数、 long 值和引用的原子数组。应用程序员不应该使用这些类,它们仅供那些开发并发工具的系统程序员使用。
14.5.11 死锁
有可能会因为每一个线程要等待条件而导致所有线程都被阻塞。这样的状态称为死锁( deadlock )。
Java 编程语言中没有任何东西可以避免或打破这种死锁现象,必须仔细设计程序,以确保不会出现死锁。
14.5.12 线程局部变量
有时可能要避免共享变量,使用 ThreadLocal 辅助类为各个线程提供各自的实例。
要为每个线程构造一个实例,可以使用以下代码:
 public static final ThreadLocal< SimpleDateFormat > dateFormat = new ThreadLocal< SimpleDateFomrat >()
 {
     protected SimpleDateFormat initialValue()
     {
         return new SimpleDateFormat("yyyy-MM-dd");
     }
 }
要访问具体的格式化方法,可以调用:
 String dateStamp = dateFormat.get().format(new Date());
在一个给定线程中首次调用 get 时,会调用 initilaValue 方法。在此之后, get 方法会返回属于当前线程的那个实例。
在多个线程中生成随机数也存在类似的问题。 java.util.Random 类是线程安全的。但是如果多个线程需要等待一个共享的随机数生成器,这会很低效。
可以使用 ThreadLocal 辅助类为各个线程提供一个单独的生成器,不过 Java SE 7 还另外提供一个便利类。只需要做一下调用:
 int random = ThreadLocalRandom.current().nextInt(upperBound);
ThreadLocalRandom.current() 调用会返回特定于当前线程的 Random 类实例。
java.lang.ThreadLocal< T > 1.2
T get()
得到这个线程的当前值。如果是首次调用get,会调用 initialize 来得到这个值。
protected initialize()
应覆盖这个方法来提供一个初始值。默认情况下,这个方法返回 null 。
void set(T t)
为这个线程设置一个新值。
void remove()
删除对应这个线程的值。
java.util.concurrent.ThreadLocalRandom 7
static ThreadLocalRandom current()
返回特定于当前线程的 Random 类实例。
14.5.13 锁测试与超时
线程在调用 lock 方法来获得另一个线程所持有的锁的时候,很可能发生阻塞。应该更加谨慎地申请锁。
TimeUnit 是一个枚举类型,可以取的值包括 SECONDS 、 MILLISECONDS 、 MICROSECONDS 和 NANOSECONDS 。
lock 方法不能被中断。如果一个线程在等待获得一个锁时被中断,中断线程在获得锁之前一直处于阻塞状态。如果出现死锁,那么, lock 方法就无法终止。
然而,如果调用带有用超时参数的 tryLock ,那么如果线程在等待期间被中断,将抛出 InterruptedException 异常。这是一个非常有用的特性,因为允许程序打破死锁。
也可以调用 lockInterruptibly 方法。它就相当于一个超时设为无限的 tryLock 方法。
在等待一个条件时,也可以提供一个超时:
 myCondition.await(100,TimeUnit.MILLISECONDS)
如果一个线程被另一个线程通过调用 signalAll 或 signal 激活,或者超时时限已达到,或者线程被中断,那么 await 方法将返回。
如果等待的线程被中断, await 方法将抛出一个 InterruptedException 异常。在你希望出现这种情况时线程继续等待(可能不太合理),可以使用 awaitUniterruptibly 方法代替 await 。
java.util.concurrent.locks.Lock 5.0
boolean tryLock()
尝试获得锁而没有发生阻塞;如果成功返回真。这个方法会抢夺可用的锁,即使该锁有公平加锁策略,即便其他线程已经等待很久也是如此。
boolean tryLock(long time,TimeUnit unit)
尝试获得锁,阻塞时间不会超过给定的值;如果成功返回 true 。
void lockInterruptibly()
获得锁,但是会不确定地发生阻塞。如果线程被中断,抛出一个 InterruptedException 异常。
java.util.concurrent.locks.Condition 5.0
boolean await(long time,TimeUnit unit)
进入该条件的等待集,直到线程从等待集中移出或等待了指定的时间之后才解除阻塞。。如果因为等待时间到了而返回就返回 false ,否则返回 true 。
void awaitUninterruptinly()
进入该条件的等待集,直到线程从等待集移出才解除阻塞。如果线程被中断,该方法不会抛出 InterruptedException 异常。
14.5.14 读/写锁
java.util.concurrent.locks包定义了两个锁类, ReentrantLock 类和 ReentrantReadWriteLock 类。如果很多线程从一个数据结构读取数据而很少线程修改其中数据的话,后者是十分有用的。在这种情况下,允许对读者共享访问是合适的。当然,写者线程依然必须是互斥访问的。
使用读/写锁的必要步骤:
(1)构造一个ReentrantReadWriteLock对象:
 private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
(2)抽取读锁和写锁:
 private Lock readLock = rwl.readLock();
 private Lock writeLock = rwl.writeLock();
(3)对所有的获取方法加读锁:
 public double getTotalBalance()
 {
     readLock.lock();
     try{...}
     finally{readLock.unlock();}
 }
(4)对所有的修改方法加写锁:
 public void transfer(...)
 {
     writeLock.lock();
     try{...}
     finally{writeLock.unlock();}
 }
java.util.concurrent.locks.ReentrantReadWriteLock 5.0
Lock readLock()
得到一个可以被多个读操作共用的读锁,但会排斥所有写操作。
Lock writeLock()
得到一个写锁,排斥所有其他的读操作和写操作。

第二部分:实验部分

实验1:测试程序1(5分)

l  在Elipse环境下调试教材651页程序14-7,结合程序运行结果理解程序;

l  掌握利用锁对象和条件对象实现的多线程同步技术。

实验代码如下:

package synch;

import java.util.*;
import java.util.concurrent.locks.*;

/**
 * A bank with a number of bank accounts that uses locks for serializing access.
 * @version 1.30 2004-08-01
 * @author Cay Horstmann
 */
public class Bank
{
   private final double[] accounts;//银行运转的基本数据
   private Lock bankLock;//锁对象
   private Condition sufficientFunds;//

   /**
    * Constructs the bank.
    * @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);
      bankLock = new ReentrantLock();
      sufficientFunds = bankLock.newCondition();
   }

   /**
    * Transfers money from one account to another.
    * @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) throws InterruptedException
   {
      bankLock.lock();
      try
      {//锁对象的引用条件对象
         while (accounts[from] < amount)
           sufficientFunds.await();
         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());
         sufficientFunds.signal();
      }
      finally
      {
         bankLock.unlock();
      }
   }

   /**
    * Gets the sum of all account balances.
    * @return the total balance
    */
   public double getTotalBalance()
   {
      bankLock.lock();//加锁
      try
      {
         double sum = 0;

         for (double a : accounts)
            sum += a;

         return sum;
      }
      finally
      {
         bankLock.unlock();//解锁
      }
   }

   /**
    * Gets the number of accounts in the bank.
    * @return the number of accounts
    */
   public int size()
   {
      return accounts.length;
   }
}

  

package synch;

/**
 * This program shows how multiple threads can safely access a data structure.
 * @version 1.31 2015-06-21
 * @author Cay Horstmann
 */
public class SynchBankTest
{
   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:测试程序2(5分)

l  在Elipse环境下调试教材655页程序14-8,结合程序运行结果理解程序;

l  掌握synchronized在多线程同步中的应用。

实验代码如下:

package synch2;

import java.util.*;

/**
 * A bank with a number of bank accounts that uses synchronization primitives.
 * @version 1.30 2004-08-01
 * @author Cay Horstmann
 */
public class Bank
{
   private final double[] accounts;

   /**
    * Constructs the bank.
    * @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);
   }

   /**
    * Transfers money from one account to another.
    * @param from the account to transfer from
    * @param to the account to transfer to
    * @param amount the amount to transfer
    */
   public synchronized void transfer(int from, int to, double amount) throws InterruptedException
   {
      while (accounts[from] < amount)
         wait();//导致线程进入等待状态直到它被通知。该方法只能在一个同步方法中调用。
      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());
      notifyAll();//解除那些在该对象上调用wait方法的线程阻塞状态。该方法只能在同步方法或同步块内部调用。
   }

   /**
    * Gets the sum of all account balances.
    * @return the total balance
    */
   public synchronized double getTotalBalance()
   {
      double sum = 0;

      for (double a : accounts)
         sum += a;

      return sum;
   }

   /**
    * Gets the number of accounts in the bank.
    * @return the number of accounts
    */
   public int size()
   {
      return accounts.length;
   }
}

  

package synch2;

/**
 * This program shows how multiple threads can safely access a data structure,
 * using synchronized methods.
 * @version 1.31 2015-06-21
 * @author Cay Horstmann
 */
public class SynchBankTest2
{
   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:测试程序3(5分)

l  在Elipse环境下运行以下程序,结合程序运行结果分析程序存在问题;

l  尝试解决程序中存在问题。

实验代码如下:

class Cbank
{
     private static int s=2000;
     public   static void sub(int m)
     {
           int temp=s;
           temp=temp-m;
          try {
              Thread.sleep((int)(1000*Math.random()));
            }
           catch (InterruptedException e)  {              }
              s=temp;
              System.out.println("s="+s);
        }
    }
 
 
class Customer extends Thread
{
  public void run()
  {
   for( int i=1; i<=4; i++)
     Cbank.sub(100);
    }
 }
public class Thread3
{
 public static void main(String args[])
  {
   Customer customer1 = new Customer();
   Customer customer2 = new Customer();
   customer1.start();
   customer2.start();
  }
}

  运行结果如下:

实验2:结对编程练习包含以下4部分(10分)

利用多线程及同步方法,编写一个程序模拟火车票售票系统,共3个窗口,卖10张票,程序输出结果类似(程序输出不唯一,可以是其他类似结果)。

Thread-0窗口售:第1张票

Thread-0窗口售:第2张票

Thread-1窗口售:第3张票

Thread-2窗口售:第4张票

Thread-2窗口售:第5张票

Thread-1窗口售:第6张票

Thread-0窗口售:第7张票

Thread-2窗口售:第8张票

Thread-1窗口售:第9张票

Thread-0窗口售:第10张票

1)   程序设计思路简述;

在主函数当中创建三个线程,写一个线程类,是实现Runnable接口的类,调用synchronized关键字实现线程的同步。

2)   符合编程规范的程序代码;

3)   程序运行功能界面截图;

代码如下:

class thread implements Runnable {
    int ticket = 1;
    boolean flag = true;
 
    @Override
    public void run() {
        while (flag) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
 
            synchronized (this) {
                if (ticket <= 10) {
                    System.out.println(Thread.currentThread().getName() + "窗口售:第" + ticket + "张票");
                    ticket++;
                }
                if (ticket > 10) {
                    flag = false;
                }
            }
        }
    }
}
 
public class Demo {
    public static void main(String[] args) {
        thread mythread = new thread();
        Thread ticket1 = new Thread(mythread);
        Thread ticket2 = new Thread(mythread);
        Thread ticket3 = new Thread(mythread);
        ticket1.start();
        ticket2.start();
        ticket3.start();
    }
}

  

实验总结:

     本周学习内容较少,理解起来也比较容易,学习了同步线程的相关问题,了解了并发多线程的两种解决方法,一种是锁对象,还有一种是synchronized关键字。学习到关键字synchronized取得的锁都是对象锁,而不是把一段代码或者方法(函数)当作锁。synchronized关键字有如下两种用法:1.需要同步的方法的方法签名中加入synchronized关键字;2、使用synchronized块对需要进行同步的代码段进行同步。

很感谢学长一学期的帮助,这周学长演示的东西基本都理解了。

posted @ 2019-12-23 16:49  计师一班张潇潇  阅读(151)  评论(1编辑  收藏  举报