201871010123-吴丽丽 《面向对象程序设计(Java)》第十七周学习总结

                                                                               201871010123-吴丽丽 《面向对象程序设计(Java)》第十七周学习总结

项目 内容
这个作业属于哪个课程 https://www.cnblogs.com/nwnu-daizh/
这个作业的要求在哪里 https://www.cnblogs.com/nwnu-daizh/p/12073034.html
作业学习目标

(1) 理解和掌握线程的优先级属性及调度方法;

(2) 掌握线程同步的概念及实现技术;

(3) Java线程综合编程练习

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

1、多线程并发执行中的问题,
➢多个线程的相对执行顺序不确定。

➢线程执行顺序不确定性会产生执行结果的不确定性。

➢在多线程对共享数据操作时常常会产生不确定性。

2、线程的同步

a)多线程并发运行不确定性问题解决方案:引入线程同步机制

b)在Java中多线程同步方法有两种:

-Java SE 5.0中引入ReentrantLock类

● ReentrantLock()

构建一个可以被用来保护临界区的可重入锁

● ReentrantLock(boolean fair)

构建一个带有公平策略的锁。一个公平锁偏爱等待时间最长的线程。但是,这一公平的保证将大大降低性能。所以,默认情况下,锁没有被强制为公平的。

- 在共享内存的类方法前加synchronized修饰符。

……

public synchronized static void sub(int m)

……

(1)解决方案一:锁对象与条件对象

用ReentrantLock保护代码块的基本结构如下:

myLock.lock();
try {
     critical section
}
finally
{
     myLock.unlock(); 
}

有关锁对象和条件对象的关键要点:

➢ 锁用来保护代码片段,保证任何时刻只能有一个线程执行被保护的代码。

➢ 锁管理试图进入被保护代码段的线程。

➢ 锁可拥有一个或多个相关条件对象
➢ 每个条件对象管理那些已经进入被保护的代码段但还不能运行的线程。

临界区中使用条件对象的await()、signal()、signalAll()方法实现线程之间的交互

●void await()

将该线程放到条件的等待集中。

●void signalAll()

解除该条件的等待集中的所有线程的阻塞状态。

●void signal()

从该条件的等待集合中随机地选择一个线程,解除其阻塞状态。

➢一个线程在临界区中时,可能根据问题的需要,必须使用锁对象的await()方法使本线程等待,暂时让出CPU的使用权,并允许其它线程使用这个同步方法。

➢线程若退出临界区,应用singal方法随机选择一个线程解除其阻塞状态;

➢线程若退出临界区,执行notifyAll()方法通知所有由于等待该临界区的线程结束等待。
(2)解决方案二: synchronized关键字

synchronized关键字作用:

➢某个类内方法用synchronized 修饰后,该方法被称为同步方法;

➢只要某个线程正在访问同步方法,其他线程欲要访问同步方法就被阻塞,直至线程从同步方法返回前唤醒被阻塞线程,其他线程方可能进入同步方法。

3、在同步方法中使用wait()、notify 和notifyAll()方法实现线程之间的交互

●void notifyAll()

解除那些在该对象上调用wait方法的线程的阻塞状态。该方法只能在同步方法或同步块内部调用。如果当前线程不是对象锁的持有者,该方法抛出一个IllegalMonitorStateException异常。

●void notify( )
随机选择一个在该对象上调用wait方法的线程,解除其阻塞状态。该方法只能在一个同步方法或同步块中调用。如果当前线程不是对象锁的持有者,该方法抛出一个IllegalMonitorStateException异常。

●void wait( )
导致线程进人等待状态直到它被通知。该方法只能在一个同步方法中调用。如果当前线程不是对象锁的持有者,该方法抛出一一个llegalMonitorStateException 异常。●void wait(long millis)
●void wait(long millis, int nanos)
导致线程进入等待状态直到它被通知或者经过指定的时间。这些方法只能在一个同步方法中调用。如果当前线程不是对象锁的持有者该方法抛出一个IlalMonitorStateException异常。

参数:millis     毫秒数

          nanos    纳秒数,<1000000

➢一个线程在使用的同步方法中时,可能根据问题的需要,必须使用wait()方法使本线程等待,暂时让出CPU的使用权,并允许其它线程使用这个同步方法。

➢线程如果用完同步方法,应用执行notify()方法随机选择一个由于使用这个同步方法而处于等待的线程结束等待。

➢线程如果用完同步方法,应用执行notifyAl()方法通知所有由于使用这个同步方法而处于等待的线程结束等待。
第二部分:实验部分

实验十七  线程同步控制

实验时间 2018-12-10

1、实验目的与要求

(1) 掌握线程同步的概念及实现技术;

(2) 线程综合编程练习

2、实验内容和步骤

实验1:测试程序并进行代码注释。

测试程序1:

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

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

SynchBankTest.java代码如下:

package synch;

/**
 * 这个程序展示了多线程如何安全地访问数据结构
 * @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是一函数式接口,可使用lambda表达式
         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();
      }
   }
}

Bank.java代码如下:

package synch;

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

/**
 * 一个拥有许多银行帐户的银行,它使用锁来序列化访问
 * @version 1.30 2004-08-01
 * @author Cay Horstmann
 */
public class Bank
{
   private final double[] accounts;
   private Lock bankLock;    //对象锁
   private Condition sufficientFunds;   //条件对象

   /**
    * 构建了银行
    * @param 账户数量
    * @param 每个账户的初始余额
    */
   public Bank(int n, double initialBalance)
   {
      accounts = new double[n];
      Arrays.fill(accounts, initialBalance);
      bankLock = new ReentrantLock();
      sufficientFunds = bankLock.newCondition();
   }

   /**
    * 把钱从一个账户转到另一个账户
    * @param 从账户转出
    * @param 到账转到
    * @param 转帐金额
    */
   public void transfer(int from, int to, double amount) throws InterruptedException
   {
      bankLock.lock();   //加了锁
      try
      {
         while (accounts[from] < amount)
            sufficientFunds.await();   //通过条件对象来调用await方法,进入阻塞状态
         System.out.print(Thread.currentThread());   //输出代表执行线程的Thread对象
         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.signalAll();  //通过条件对象来调用signalAll方法来解除等待线程的阻塞
      }
      finally
      {
         bankLock.unlock();   //解锁
      }
   }

   /**
    * 获取所有帐户余额的总和
    * @return 总平衡
    */
   public double getTotalBalance()
   {
      bankLock.lock();   //加锁
      try
      {
         double sum = 0;

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

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

   /**
    * 获取银行中的帐户编.
    * @return 账户数量
    */
   public int size()
   {
      return accounts.length;
   }
}

程序运行结果截图如下:

测试程序2

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

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

Bank.java代码如下:

package synch2;

import java.util.*;

/**
 * 使用同步原理的具有多个银行帐户的银行
 * @version 1.30 2004-08-01
 * @author Cay Horstmann
 */
public class Bank
{
   private final double[] accounts;

   /**
    * 构建了银行
    * @param 账户数量
    * @param 每个账户的初始余额
    */
   public Bank(int n, double initialBalance)
   {
      accounts = new double[n];
      Arrays.fill(accounts, initialBalance);
   }

   /**
    * 把钱从一个账户转到另一个账户
    * @param 从账户转出
    * @param 到账转到
    * @param 转帐金额
    */
   //类内方法用synchronized修饰后,该方法为同步方法
   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();    //执行notifyAll方法通知所有由于这个同步方法而处于等待的线程结束等待
   }

   /**
    * 获取所有帐户余额的总和
    * @return the total balance
    */
   public synchronized double getTotalBalance()
   {
      double sum = 0;

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

      return sum;
   }

   /**
    * 获取银行中的帐户编号.
    * @return  账户数量
    */
   public int size()
   {
      return accounts.length;
   }
}

SynchBankTest2.java代码如下:

package synch2;

/**
 * 这个程序展示了多线程如何安全地访问一个数据结构,使用同步方法
 * @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();
      }
   }
}

程序运行结果截图如下:

测试程序3

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

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

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();

  }

}

 程序代码如下:

package pro;
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();
 }
}

运行结果截图如下:

 其存在问题是:这两个线程是各做各的,互不影响,它们相对执行顺序不确定,其执行顺序不确定性会产生执行结果的不确定性。

(用synchronized关键字)修改程序后代码如下:

package pro;
class Cbank
	{
    private static int s=2000;
    public synchronized 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();
 }
}

程序运行结果如下截图所示:

实验编程练习

利用多线程及同步方法,编写一个程序模拟火车票售票系统,共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张票

程序设计思路:

首先创建一个售票的一个线程组,其有3个线程售票,然后通过传递sellTicketThreadGroud参数给一个新的线程,重写run方法,执行sell方法,其中sell方法是使用 synchronized 关键字来修饰的,其确保在一个时刻只有一个线程可以进入sell方法执行代码。

流程图如下:

 程序代码如下:

package track;
/**
 * 使用 synchronized 关键字实现线程同步的售卖火车票的测试类
 */
public class SellTickets3 {
	private static int tickets = 1; 
	
	// 使用 synchronized 关键字修饰方法,确保某一时刻只有一个线程能够进入该方法中执行代码
	protected synchronized static void sell() {
		if (tickets <= 10) {
			System.out.println(Thread.currentThread().getName() + "窗口售:第 " + tickets++ + " 张票");
		}
	}
	
	public static void startSell() {
        // 售票线程所在线程组
        ThreadGroup sellTicketThreadGroup = new ThreadGroup("sell ticket thread group");
        // 开启 3个线程售票
        for (int i = 0; i < 3; i++) {
            // 新建售票线程,并将其加入售票线程组中
            new Thread(sellTicketThreadGroup, "Thread-" + (i)) {
                @Override
                public void run() {
                    while (tickets > 0) {
                        sell();
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            // TODO 自动生成的 catch 块
                            e.printStackTrace();
                        }
                    }
                }
            }.start();
        }
        
    }


public static void main(String[] args) {
	SellTickets3.startSell();
}
}

运行结果截图如下:

实验总结:

本周学习了同步线程的相关问题,了解了并发多线程的两种解决方法,一种是锁对象,还有一种是synchronized关键字。还有在同步线程中使用wait()、notify()和notifyAll()方法,使用wait()方法使本线程等待,暂时让出CPU的使用权,并允许其它线程使用这个同步方法。线程如果用完同步方法,应当执行notifyAll()方法通知所有由于使用这个同步方法而处于等待的线程结束等待。在本周的学习中,自己对同步线程有所掌握,懂得了同步线程的基本工作原理,同时对老师所讲的理论知识有了很好的理解,在这星期中收获挺大的。

 

原文地址:https://www.cnblogs.com/lily-2018/p/12078367.html