关于java:线程同步

6次阅读

共计 6124 个字符,预计需要花费 16 分钟才能阅读完成。

线程同步

案例 卖票

public class SellTicket implements Runnable{
   private int tickets = 100;
   // 重写 run 办法 实现卖票
   @Override
   public void run(){while(true){if(tickets>0){// 票数大于 0 就卖票 并且告知是哪个窗口卖的 
              sout(Thread.currentThread().getName()+"正在发售第"+tickets+"张票");
              tickets --;
          }
       }
   
// 主程序 SellTicketDemo
// 创立 SellTicket 对象 
SellTicket st= new SellTicket ();
// 创立三个线程类的对象, 把 SellTicket 对象作为构造方法的参数,并且给出对应的窗口名称
Thread t1 = new Thread (st,name:"窗口 1");
Thread t2 = new Thread (st,name:"窗口 2");
Thread t3 = new Thread (st,name:"窗口 3");
// 启动线程
t1.start();
t2.start();
t3.start();

卖票案例的思考

public class SellTicket implements Runnable{
   private int tickets = 100;
   // 重写 run 办法 实现卖票
   @Override
   public void run(){while(true){if(tickets>0){// 票数大于 0 就卖票 并且告知是哪个窗口卖的 
              // 通过 sleep 办法来模拟出票工夫 
              Thread.sleep(millis:100);//alt enter 捕捉异样
              sout(Thread.currentThread().getName()+"正在发售第"+tickets+"张票");
              tickets --;
          }
       }
   
// 主程序 SellTicketDemo
// 创立 SellTicket 对象 
SellTicket st= new SellTicket ();
// 创立三个线程类的对象, 把 SellTicket 对象作为构造方法的参数,并且给出对应的窗口名称
Thread t1 = new Thread (st,name:"窗口 1");
Thread t2 = new Thread (st,name:"窗口 2");
Thread t3 = new Thread (st,name:"窗口 3");
// 启动线程
t1.start();
t2.start();
t3.start();

下面那个在卖票类外面加了 sleep 函数,会呈现三个窗口卖同一张票或者卖票是正数

  • 三个窗口卖同一张票

    tickets = 100;t1 t2 t3
    假如 t1 线程抢到 cpu 的执行权。而后 t1 线程劳动 100ms,在 t1 劳动的时候 t2 线程抢到了 cpu 的执行权,t2 线程就开始执行,也是从 tickets = 100 执行,100>0,走进循环,而后 t2 线程劳动 100ms,同理,在 t2 劳动的时候 t3 线程抢到了 cpu 的执行权,t3 线程就开始执行,也是从 tickets = 100 执行,100>0,走进循环,而后 t3 线程劳动 100ms
    而后走到输入,假如线程依照劳动的程序醒过来,t1 抢到 cpu 的执行权,在控制台输入窗口 1 正在发售第 100 张票,而后就应该执行 tickets --,然而如果在这会正好 t2 抢到 cpu 的执行权,在控制台输入窗口 2 正在发售第 100 张票,同理,如果在这会正好 t3 抢到 cpu 的执行权,在控制台输入窗口 3 正在发售第 100 张票,如果这三个线程还是依照程序来,那么这里前面就会执行三次 tickets --,最终票就会变成 97
  • 售票呈现正数

    tickets = 1;t1 t2 t3
    假如 t1 线程抢到 cpu 的执行权。而后 t1 线程劳动 100ms,在 t1 劳动的时候 t2 线程抢到了 cpu 的执行权,t2 线程就开始执行,也是从 tickets = 1 执行,100>0,走进循环,而后 t2 线程劳动 100ms,同理,在 t2 劳动的时候 t3 线程抢到了 cpu 的执行权,t3 线程就开始执行,也是从 tickets = 1 执行,1>0,走进循环,而后 t3 线程劳动 100ms
    而后走到输入,假如线程依照劳动的程序醒过来,t1 抢到 cpu 的执行权,在控制台输入窗口 1 正在发售第 1 张票,假如 t1 持续领有 cpu 的使用权,则就会执行 tickets --,tickets 就会变成 0。而后就进不去循环了,而后与此同时,正好 t2 抢到 cpu 的执行权,在控制台输入窗口 2 正在发售第 0 张票,假如 t2 持续领有 cpu 的使用权,则就会执行 tickets --,tickets 就会变成 -1,而后就进不去循环了,同理,正好 t3 抢到 cpu 的执行权,在控制台输入窗口 2 正在发售第 - 1 张票,假如 t3 持续领有 cpu 的使用权,则就会执行 tickets --,tickets 就会变成 -2,

卖票案例数据安全问题的解决

为什么呈现问题(也是判断多线程程序是否会有数据安全问题的规范)

  • 是否是多线程环境
  • 是否共享数据
  • 是否有多条语句操作共享数据
public class SellTicket implements Runnable{
   private int tickets = 100;
   // 重写 run 办法 实现卖票
   @Override
   public void run(){while(true){if(tickets>0){// 票数大于 0 就卖票 并且告知是哪个窗口卖的 
              // 通过 sleep 办法来模拟出票工夫 
              Thread.sleep(millis:100);//alt enter 捕捉异样
              sout(Thread.currentThread().getName()+"正在发售第"+tickets+"张票");
              tickets --;
          }
       }
   
// 主程序 SellTicketDemo
// 创立 SellTicket 对象 
SellTicket st= new SellTicket ();
// 创立三个线程类的对象, 把 SellTicket 对象作为构造方法的参数,并且给出对应的窗口名称
Thread t1 = new Thread (st,name:"窗口 1");
Thread t2 = new Thread (st,name:"窗口 2");
Thread t3 = new Thread (st,name:"窗口 3");
// 启动线程
t1.start();
t2.start();
t3.start();

如何解决多线程平安问题?

  • 根本思维:让程序没有平安问题的环境 个别解决下面第三个问题

怎么实现

  • 把多条语句操作共享数据的代码锁起来,然任意时刻只能有一个线程执行
  • java 提供了 同步代码块 的形式解决

同步代码块

public class SellTicket implements Runnable{
   private int tickets = 100;
   private Object obj = new Object();// 创立一个新的对象,不论是什么线程进来都是执行 obj 是同一把锁
   // 重写 run 办法 实现卖票
   @Override
   public void run(){while(true){synchronized(obj){if(tickets>0){// 票数大于 0 就卖票 并且告知是哪个窗口卖的 
                  // 通过 sleep 办法来模拟出票工夫   
                  Thread.sleep(millis:100);//alt enter 捕捉异样
                  sout(Thread.currentThread().getName()+"正在发售第"+tickets+"张票");
                  tickets --;
              }
          }
      }
   }
}
tickets = 100;t1 t2 t3
假如 t1 线程抢到 cpu 的执行权,t1 进来胡,就会执行 synchronized 把这段代码锁起来,锁完之后,就会执行 if 语句,而后 t1 线程劳动 100ms,假如在 t1 劳动的时候 t2 线程抢到了 cpu 的执行权,t2 线程而后 t2 线程发现被锁住了,就只能等着,而后等 t1 劳动好了,t1 持续向下执行,在控制台输入窗口 1 正在发售第 100 张票,而后执行 tickets --,tickets 就会变成 99,而后 t1 就进去了,t1 进去之后这段代码的锁就会被开释了
而后三个线程再持续抢 cpu 的使用权

同步办法

public class SellTicket implements Runnable{
   private int tickets = 100;
   private Object obj = new Object();// 创立一个新的对象,不论是什么线程进来都是执行 obj 是同一把锁
   private int x= 0;
   // 重写 run 办法 实现卖票
   @Override
   public void run(){while(true){if(x%2==0){synchronized(obj){if(tickets>0){// 票数大于 0 就卖票 并且告知是哪个窗口卖的 
                     // 通过 sleep 办法来模拟出票工夫
                     Thread.sleep(millis:100);//alt enter 捕捉异样
                     sout(Thread.currentThread().getName()+"正在发售第"+tickets+"张票");
                     tickets --;
                  }
              }
          }else{synchronized(obj){if(tickets>0){// 票数大于 0 就卖票 并且告知是哪个窗口卖的 
                     // 通过 sleep 办法来模拟出票工夫
                     Thread.sleep(millis:100);//alt enter 捕捉异样
                     sout(Thread.currentThread().getName()+"正在发售第"+tickets+"张票");
                     tickets --;
                  }
              }
          }
          x++;
     }
   }
}
   
// 主程序 SellTicketDemo
// 创立 SellTicket 对象 
SellTicket st= new SellTicket ();
// 创立三个线程类的对象, 把 SellTicket 对象作为构造方法的参数,并且给出对应的窗口名称
Thread t1 = new Thread (st,name:"窗口 1");
Thread t2 = new Thread (st,name:"窗口 2");
Thread t3 = new Thread (st,name:"窗口 3");
// 启动线程
t1.start();
t2.start();
t3.start();

下面的 if 和 else 外面管制的都是一样的也是同一把锁,能够写成办法
同步办法 就是把 synchronized 关键字加到办法上

  • 格局:修饰符 synchronized 返回值类型 办法名 (办法参数){}

    而后下面那个两种办法的对象应该是一样的 都是办法里的 this 对象
  • 同步办法的锁的对象是 this这个对象

    private synchronized void sellTickett(){if(tickets>0){// 票数大于 0 就卖票 并且告知是哪个窗口卖的   
            // 通过 sleep 办法来模拟出票工夫
            Thread.sleep(millis:100);//alt enter 捕捉异样
            sout(Thread.currentThread().getName()+"正在发售第"+tickets+"张票");
            tickets --;
        }
    }

同步静态方法 就是把 synchronized 关键字加到静态方法上

  • 格局:修饰符 static synchronized 返回值类型 办法名 (办法参数){}

    而后下面那个两种办法的对象应该是一样的 都是办法里的 类名.class 对象
  • 同步静态方法的锁的对象是 类名.class这个对象

线程平安的类

  • StringBuffer
    线程平安,可变的字符序列
    从 JDK5 开始,被 StringBuilder 代替,通常应该应用 StringBuilder 类,因为它反对所有雷同的操作,但它更快,因为它不执行同步
  • Vector
    从 java2 平台 v1.2 开始,该类改良了 List 接口,使其成为 JavaCollectionFramework 的成员。与新的汇合实现不同,Vector 被同步,如果不须要线程平安的实现,倡议应用 ArrayList 代替 Vector
  • Hashtable
    该类实现了一个哈希表,它将键映射到值,任何非 null 对象都能够用作键或者值
    从 java2 平台 v1.2 开始,该类进行了改良,实现了 Map 接口,使其成为 JavaCollectionFramework 的成员。与新的汇合实现不同,Hashtable 被同步。如果不须要线程平安的实现,倡议应用 HashMap 代替 Hashtable
StringBuffer sb = new StringBuffer();// 加了 synchronized 关键字,所以也是同步办法,是线程平安的
StringBuilder sb2 = new StringBuilder();// 办法没有加 synchronized 关键字,所以是线程不平安

Vector<String> v = new Vector<String>();// 加了 synchronized 关键字,所以也是同步办法,是线程平安的
ArrayList<String> array = new ArrayList<String>();// 办法没有加 synchronized 关键字,所以是线程不平安

Hashtable<String,String> ht = new Hashtable<String,String>();// 加了 synchronized 关键字,所以也是同步办法,是线程平安的
HashMap<String,String> hm = new HashMap<String,String>();// 办法没有加 synchronized 关键字,所以是线程不平安

// 将线程不平安变成线程平安 所有的都是相似的示意
List<String> list = Collections.synchronizedlist(new ArrayList<String>())

Lock 锁

public class SellTicket implements Runnable{
   private int tickets = 100;
   private Lock lock = new Reentrantlock();
   // 重写 run 办法 实现卖票
   @Override
   public void run(){while(true){lock.lock();
              if(tickets>0){// 票数大于 0 就卖票 并且告知是哪个窗口卖的 
                   // 通过 sleep 办法来模拟出票工夫 
                   Thread.sleep(millis:100);//alt enter 捕捉异样
                   sout(Thread.currentThread().getName()+"正在发售第"+tickets+"张票");
                   tickets --;  
              }
              lock.unlock();}
   
// 主程序 SellTicketDemo
// 创立 SellTicket 对象 
SellTicket st= new SellTicket ();
// 创立三个线程类的对象, 把 SellTicket 对象作为构造方法的参数,并且给出对应的窗口名称
Thread t1 = new Thread (st,name:"窗口 1");
Thread t2 = new Thread (st,name:"窗口 2");
Thread t3 = new Thread (st,name:"窗口 3");
// 启动线程
t1.start();
t2.start();
t3.start();

通过 lock 和 unlock 加锁和开释锁
个别会用如下表是,用 try catch

正文完
 0