201871010112- Liang Lizhen "object-oriented programming (java)" Week 17 learning summary

 

project

content

This work belongs courses

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

Where this requirement in the job

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

Job learning objectives

(1) understand and master the property and priority scheduling method thread;

(2) to grasp the concept and implementation of thread synchronization technology;

(3) Java threads comprehensive programming exercises

Part I: summary of thread synchronization technology

What is the thread synchronization? 

When multiple threads to access the same data, very prone to thread-safety issues (such as multiple threads are operating in the same data leads to inconsistent data), so we use the synchronization mechanism to solve these problems. 

State of the thread

State of the thread represents the active threads in a certain period of time and tasks to be carried out. There are threads created, ready, running, blocking, died five states. Has a thread of life, always in one of five states.

 

 

 

 

 

 

  1. Create state

Examples of the Thread object, but does not state the method calls start (). E.g.

ThreadTest test = new ThreadTest();
//or
Thread t = new Thread(test);

 At this time, although Thread object is created, but they are temporarily unable to pass isAlive () test.

 2. Ready

       Although the threads are eligible to run, but the scheduler has not selected it as the state of the running thread is in. In this case, the thread with the conditions of operation, once selected, can immediately run.
       After the thread is created, call the start () method, the thread is not running, but can isAlive () test. And after thread running or from being blocked, or sleep after waiting to come back, can immediately enter the ready state.
3. run state
  Pool from the ready state (note that is not a queue, a pool) which is selected as the currently executing thread state.
 4. Wait, obstruction or sleep state
    线程依然是活的,但是缺少运行的条件.一旦具备了条件,就可以转为就绪状态,不能直接转为运行状态.另外,suspend()和stop()方法已经被废弃了,比较危险,不要在使用了.
 5.死亡状态
   一个线程的run()方法运行结束,那么该线程完成其使命,它的栈结构将解散,也就是死亡了.但是它依然是一个Thread对象,仍可以被引用,这一点与其他对象一样,而且被引用的对象也不会被垃圾回收器回收.
   一旦线程死去,它就永远不能重新启动了,也就是说,不能再用start()方法让它运行.下面的实例会抛出IllegalThreadStateException异常
    t.start();
    t.start();

这是错误的用法

注意:线程的启动要调用start()方法,只有这样才能创建新的调用栈.而直接调用run()方法的话,就不会创建新的调用栈,也就不会创建新的线程,run()方法就普通方法没什么两样了.

Thread类实现了Runnable

线程同步

  线程共享了相同的资源,但是在某些重要的情况下,一次只能让一个线程来访问共享资源,例如,作为银行账户这样的共享资源,如果多个线程可以同时使用该资源,就会出出现银行账户数据安全上的问题.

1.共享变量

       要使多个线程在一个程序中有用,必须有某种方法实现线程间的互相通信或共享结果,最简单的方法是使用共享变量.使用同步来确保值从一个线程正确传播到另一个线程,以及防止当一个线程正在更新一些相关数据项时,另一个线程看到不一致的中间结果.

2.存在于同一内存空间中的所有线程

       线程与进程有许多共同点,不同的是线程与同一进程中的其他线程共享相同进程上的下文,包括内存空间,只要访问共享变量(静态或者实例变量),线程就可以方便地互相交换数据,但必须确保线程以受控的方式访问共享变量,以免它们互相干扰对方的更改.

3.受控访问的同步

       为了确保可以在线程之间以受控方式共享数据,Java语言提供了两个关键字:synchronizedvolatile.

synchronized有两个重要的含义

  一次只有一个线程可以执行代码的受保护部分.
       一个线程更改的数据对于其他线程是可见的.
       如果没有同步,数据就很容易处于不一致状态.例如,如果一个线程正在更新两个相关值,而另一个线程正在读取这两个值,有可能在第一个线程只写了一个值,还没有写另外一个值的时候,调度第二个线程运行,这样它就会看到一个旧值和一个新值.
synchronized(对象) {  // 任意对象都可以。这个对象就是锁。
	需要被同步的代码;
}

public synchronized void transfer() {

}
4.确保共享数据更改的可见性.
同步可以让用户确保线程看到一致的内存视图.
       处理器可以使用高速缓存加速对内存的访问(或者编译器可以将值存储到寄存器中,以便进行更快的访问).这表示在这样的系统上,对于同一个变量,在两个不同的处理器上执行的两个线程可能会看到两个不同的值,如果没有正确的同步,线程可能会看到旧的变量值,或者引起其他形式的数据损坏.
       volatile 比同步简单,只适合于控制对基本变量(整数,布尔变量等) 的单个实例的访问.当一个变量被声明成volatile,任何对该变量的写操作都会绕过高速缓存,直接写入主内存,而任何对该变量的读取也都绕过高速缓存,直接取自主内存.这表示所有线程在任何时候看到的volatile变量值都相同.
private volatile double balance;
5.用锁保护的原子代码块
       volatile对于确保每个线程看到最新的变量值非常有用,但实际上经常需要保护代码片段,同步使用监控器(monitor)或锁的概念,以协调对特定代码块的访问.
       每个Java对象都有一个相关的锁,同一时间只能有一个线程持有Java锁.当线程进入synchronized 代码块时,线程会阻塞并等待,直到锁可用.当线程处于就绪状态时,并且获得锁后,将执行代码块,当控制退出受保护的代码块,即到达了代码块末尾或者抛出了没有在synchronized 块中捕获的异常时,它就会释放该锁.
       这样,每次只有一个线程可以执行受给监控器保护的代码块.从其他线程的角度看,该代码块可以看做是原子的,它要么全部执行,要么不执行.
6.Java锁定

   Java锁定可以保护许多代码块或方法,每次只有一个线程可以持有锁.

       反之,仅仅因为代码块由锁保护并不代表两个线程不能同时执行该代码块.它只表示如果两个线程正在等待相同的锁,则它们不能同时执行该代码.

7.同步的方法

 创建synchronized 块的最简单方法是将方法声明成synchronized.这表示在进入方法主体之前,调用者必须获得锁.示例代码如下:

public class Point{
    public synchronized void setXY(int x,int y){
        this.x = x;
        this.y = y;
    }
}
对于普通的synchronized方法,这个锁是一个对象,将针对它调用方法,对于静态的synchronized方法,这个锁是与Class对象相关的监控器,在该对象中声明了方法.
       setXY()方法被声明成了synchronized ,并不表示两个不同的线程不能同时执行setXY()方法,只要它们调用不同Point实例的setXY()方法,就可同时执行.对于一个Point实例,一次只能有一个线程执行setXY()方法,或Point的任何其他synchronized方法.
8.同步的方法
   创建synchronized 块的最简单方法是将方法声明成synchronized.这表示在进入方法主体之前,调用者必须获得锁.示例代码如下:
public class Point{
    public synchronized void setXY(int x,int y){
        this.x = x;
        this.y = y;
    }
}
对于普通的synchronized方法,这个锁是一个对象,将针对它调用方法,对于静态的synchronized方法,这个锁是与Class对象相关的监控器,在该对象中声明了方法.
       setXY()方法被声明成了synchronized ,并不表示两个不同的线程不能同时执行setXY()方法,只要它们调用不同Point实例的setXY()方法,就可同时执行.对于一个Point实例,一次只能有一个线程执行setXY()方法,或Point的任何其他synchronized方法.
9.同步的块
 synchronized块的语法比synchronized方法稍微复杂一点,因为还需要显式地指定锁要保护哪个块.
public class Point{
    public synchronized void setXY(int x,int y){
        synchronized (this) {
            this.x = x;
            this.y = y;
        }
    }
}
使用this引用作为锁很常见,但这不是必须的.使用this引用作为锁表示该代码与这个类中的synchronized方法使用一个锁.
       由于同步防止了多个线程同时执行一个代码块,因此性能上就有问题,即使是在单处理器的系统上,也最好在尽可能小的需要保护的代码上使用同步.
       访问基于堆栈的局部变量从来不需要受到保护,因此它们只能受到自己所属的线程访问.

10.大多数类并没有同步

       因为同步会带来小小的性能损失,大多数通用类,如java.util中的Collection类,不在内部使用同步,这表示在没有附加同步的情况下,不能在多个线程中使用诸如HashMap这样的类.

Lock显示锁

  java从1.5版本之后,提供了Lock接口。这时候,直接将锁封装成了对象。线程进入同步就是具备了锁,执行完,离开同步,就是释放了锁。在后期对锁的分析过程中,发现,获取锁,或者释放锁的动作应该是锁这个事物更清楚。所以将这些动作定义在了锁当中,并把锁定义成对象。所以,同步是隐式的锁操作,而Lock对象是显示的锁操作。

bankLock.lock(); //a ReentrantLock object
      try
      {
         //临界区
      }
      finally
      {
         bankLock.unlock();  //如果在临界区抛出异常,必须保证锁被释放
      }

另外,有一个需要注意的地方是,锁的唤醒机制的不同。

   在用同步synchronized来实现加锁时,由于这时的锁用的是任意对象,所以如wait,notify,notifyAll等操作锁的等待唤醒的方法都定义在Object中。

 而现在用Lock时,用的锁是Lock对象。所以查找等待唤醒机制方式需要通过Lock接口来完成。而Lock接口中并没有直接操作等待唤醒的方法,而是将这些方式又单独封装到了一个对象中。这个对象就是Condition,将Object中的三个方法进行单独的封装。并提供了功能一致的方法 await()、signal()、signalAll()体现新版本对象的好处。

下面是java核心技术卷1中关于显示锁核心代码:

/**
    * 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.signalAll();
      }
      finally
      {
         bankLock.unlock();
      }
   }

这里使用一个锁来保护Bank中的transfer方法,模拟现实中转账操作。

  假定一个线程调用transfer时,在执行结束前被剥夺了运行权。假定第二个线程也调用transfer,由于第二个线程不能获得锁,将在调用lock方法时被阻塞。它必须等待第一个线程完成transfer方法的执行之后才能再度激活。当第一个线程释放锁时,那么第二个线程才可以运行。
锁是可重入的,因为线程可以重复地获得已经持有的锁。锁保持一个持有计数来跟踪对lock方法的嵌套调用。线程在每一次调用lock都要使用unlock来释放锁。由于这一特性,被一个锁保护的代码可以调用另一个使用相同的锁的方法。例如,当transfer方法调用getTotalBalance方法,这也会封锁bankLock对象,此时bankLock持有的计数为2.当getTotalBalance方法退出是,持有计数变为1.当transfer方法退出的时候,持有的计数变为0.线程释放锁。上面实例代码中调用的方法getTotalBalance与transfer方法使用的是同一个锁。

/**
    * 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();
      }
   }

另外,上面的案列使用显示锁Lock,同样可以用同步的方法来实现,将transfer方法用synchronized修饰,同样在transfer中调用的getTotalBalance也需要用synchronized来修饰。

/**
    * 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();
   }

/**
    * 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;
   }

 

二、实验内容和步骤

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

测试程序1:

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

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

程序:

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);//调用initialBalance生成锁对象属性
      bankLock = new ReentrantLock();
      sufficientFunds = bankLock.newCondition();
   }

   /**
        * 把钱从一个账户转到另一个账户。
    * @param 从该账户转账
    * @param 把钱转到账户上
    * @param 转帐金额
    */
   //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.signalAll();//调用signalAll()方法
      }
      finally
      {
         bankLock.unlock();
      }
   }

   /**
       * 获取所有帐户余额的总和。
    * @return the total balance
    */
   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;
   }
}

 

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

运行结果:

 

 

测试程序2

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

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

程序:

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 转帐金额
    */
   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();//唤醒所有等待的线程
   }

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

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

      return sum;
   }

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

 

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;//当类加载时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();//把变量customer1的值设置为分配给新的Customer对象的内部地址
   Customer customer2 = new Customer();
   customer1.start();
   customer2.start();
  }
}

运行结果:

修改后代码:

 

package Thread;

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方法执行代码。

 

 

 

结对编程者:吴丽丽

 

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

运行结果:

 

实验总结:

        这周继续学习了有关线程的知识,主要学习了有关线程同步的问题。线程同步主要是为了解决多线程并发运行不确定性问题,使得多个线程中在一个线程使用某种方法时候,另一线程要使用该方法,就只能等待。在实验中学会了在Java中解决多线程同步问题。掌握利用锁对象和条件对象实现的多线程同步技术。掌握synchronized在多线程同步中的应用。

 

 

 

 

Guess you like

Origin www.cnblogs.com/LZ-728672/p/12077103.html