Java 多线程详解(三)------线程的同步

Java 多线程详解(一)------概念的引入:https://blog.csdn.net/weixin_39816740/article/details/80089790

Java 多线程详解(二)------如何创建进程和线程:https://blog.csdn.net/weixin_39816740/article/details/80089834

介绍完如何创建进程以及线程了,那么我们接着来看一个实例:

  利用多线程模拟 3 个窗口卖票

第一种方法:继承 Thread 类

 创建窗口类 TicketSell 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.ys.thread;
 
public class TicketSell extends Thread{
     //定义一共有 50 张票,注意声明为 static,表示几个窗口共享
     private static int num = 50 ;
     
     //调用父类构造方法,给线程命名
     public TicketSell(String string) {
         super (string);
     }
     @Override
     public void run() {
         //票分 50 次卖完
         for ( int i = 0 ; i < 50 ;i ++){
             if (num > 0 ){
                 try {
                     sleep( 10 ); //模拟卖票需要一定的时间
                 } catch (InterruptedException e) {
                     // 由于父类的 run()方法没有抛出任何异常,根据继承的原则,子类抛出的异常不能大于父类, 故我们这里也不能抛出异常
                     e.printStackTrace();
                 }
                 System.out.println( this .currentThread().getName()+ "卖出一张票,剩余" +(--num)+ "张" );
             }
         }
     }
     
 
}

  创建主线程测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.ys.thread;
 
public class TestTicket {
 
     public static void main(String[] args) {
         //创建 3 个窗口
         TicketSell t1 = new TicketSell( "A窗口" );
         TicketSell t2 = new TicketSell( "B窗口" );
         TicketSell t3 = new TicketSell( "C窗口" );
         
         //启动 3 个窗口进行买票
         t1.start();
         t2.start();
         t3.start();
     }
}

  结果:这里我们省略了一些,根据电脑配置,结果会随机出现不同

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
B窗口卖出一张票,剩余 48
A窗口卖出一张票,剩余 47
C窗口卖出一张票,剩余 49
C窗口卖出一张票,剩余 46
B窗口卖出一张票,剩余 44
A窗口卖出一张票,剩余 45
A窗口卖出一张票,剩余 43
...
C窗口卖出一张票,剩余 5
A窗口卖出一张票,剩余 4
B窗口卖出一张票,剩余 3
A窗口卖出一张票,剩余 2
C窗口卖出一张票,剩余 3
B窗口卖出一张票,剩余 1
C窗口卖出一张票,剩余 0
A窗口卖出一张票,剩余- 1

  

第二种方法:实现 Runnable 接口

  创建窗口类 TicketSellRunnable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.ys.thread;
 
public class TicketSellRunnable implements Runnable{
 
     //定义一共有 50 张票,继承机制开启线程,资源是共享的,所以不用加 static
     private int num = 50 ;
     
     @Override
     public void run() {
         //票分 50 次卖完
         for ( int i = 0 ; i < 50 ;i ++){
             if (num > 0 ){
                 try {
                     //模拟卖一次票所需时间
                     Thread.sleep( 10 );
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
                 System.out.println(Thread.currentThread().getName()+ "卖出一张票,剩余" +(--num)+ "张" );
             }
         }
     }
 
}

  创建主线程测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.ys.thread;
 
public class TicketSellRunnableTest {
     public static void main(String[] args) {
         TicketSellRunnable t = new TicketSellRunnable();
         
         Thread t1 = new Thread(t, "A窗口" );
         Thread t2 = new Thread(t, "B窗口" );
         Thread t3 = new Thread(t, "C窗口" );
         
         t1.start();
         t2.start();
         t3.start();
     }
 
}

  结果:同理为了篇幅我们也省略了中间的一些结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
B窗口卖出一张票,剩余 49
C窗口卖出一张票,剩余 48
A窗口卖出一张票,剩余 49
B窗口卖出一张票,剩余 47
A窗口卖出一张票,剩余 45
......
A窗口卖出一张票,剩余 4
C窗口卖出一张票,剩余 5
A窗口卖出一张票,剩余 3
B窗口卖出一张票,剩余 2
C窗口卖出一张票,剩余 1
B窗口卖出一张票,剩余 0
A窗口卖出一张票,剩余- 2
C窗口卖出一张票,剩余- 1

  

结果分析:这里出现了票数为 负数的情况,这在现实生活中肯定是不存在的,那么为什么会出现这样的情况呢?

  

解决办法分析:即我们不能同时让超过两个以上的线程进入到 if(num>0)的代码块中,不然就会出现上述的错误。我们可以通过以下三个办法来解决:

1、使用 同步代码块

2、使用 同步方法

3、使用 锁机制

①、使用同步代码块

1
2
3
4
5
6
7
8
9
语法:
synchronized (同步锁) {
     //需要同步操作的代码         
}
 
同步锁:为了保证每个线程都能正常的执行原子操作,Java 线程引进了同步机制;同步锁也叫同步监听对象、同步监听器、互斥锁;
Java程序运行使用的任何对象都可以作为同步监听对象,但是一般我们把当前并发访问的共同资源作为同步监听对象
 
注意:同步锁一定要保证是确定的,不能相对于线程是变化的对象;任何时候,最多允许一个线程拿到同步锁,谁拿到锁谁进入代码块,而其他的线程只能在外面等着

  实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void run() {
         //票分 50 次卖完
         for ( int i = 0 ; i < 50 ;i ++){
             //这里我们使用当前对象的字节码对象作为同步锁
             synchronized ( this .getClass()) {
                 if (num > 0 ){
                     try {
                         //模拟卖一次票所需时间
                         Thread.sleep( 10 );
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                     System.out.println(Thread.currentThread().getName()+ "卖出一张票,剩余" +(--num)+ "张" );
                 }
             }
             
         }
     }

  

2、使用 同步方法

语法:即用  synchronized  关键字修饰方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
     public void run() {
         //票分 50 次卖完
         for ( int i = 0 ; i < 50 ;i ++){
             sell();
             
         }
     }
     private synchronized void sell(){
         if (num > 0 ){
             try {
                 //模拟卖一次票所需时间
                 Thread.sleep( 10 );
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             System.out.println(Thread.currentThread().getName()+ "卖出一张票,剩余" +(--num)+ "张" );
         }
     }

  注意:不能直接用 synchronized 来修饰 run() 方法,因为如果这样做,那么就会总是第一个线程进入其中,而这个线程执行完所有操作,即卖完所有票了才会出来。

3、使用 锁机制

  

1
public interface Lock

  主要方法:

  常用实现类:

1
2
3
public class ReentrantLock
extends Object
implements Lock, Serializable<br> //一个可重入互斥Lock具有与使用synchronized方法和语句访问的隐式监视锁相同的基本行为和语义,但具有扩展功能。

  例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.ys.thread;
 
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
 
public class TicketSellRunnable implements Runnable{
 
     //定义一共有 50 张票,继承机制开启线程,资源是共享的,所以不用加 static
     private int num = 50 ;
     //创建一个锁对象
     Lock l = new ReentrantLock();
     
     @Override
     public void run() {
         //票分 50 次卖完
         for ( int i = 0 ; i < 50 ;i ++){
             //获取锁
             l.lock();
             try {
                 if (num > 0 ){
                 //模拟卖一次票所需时间
                 Thread.sleep( 10 );
                 System.out.println(Thread.currentThread().getName()+ "卖出一张票,剩余" +(--num)+ "张" );
                 }
             } catch (Exception e) {
                 e.printStackTrace();
             } finally {
                 //释放锁
                 l.unlock();
             }
             
             
         }
     }
     private void sell(){
         
     }
 
}

  

  

猜你喜欢

转载自blog.csdn.net/weixin_39816740/article/details/80089890