线程的同步机制

线程的同步机制
主要是同java中的synchronized关键字[类锁方式]的实现

关于synchronized关键字的讲解可以参考 synchronized 关键字解析

这里主要是通过售票案例来讲解下线程的同步机制:
使用继承Thread类的方式:
package org.xyz.java.thread.demo03;
/**
 * 使用继承Thread的方式来实现多个窗口同时售票的案例场景
 * @author kevin.chen
 *
 */
public class WindowThread {
        /**
         * 该案例说明:
         * 	> 1、该案例创建了3个子线程来同时售票,但是3个线程中各种都有一个ticket的成员变量,所以三个线程各种售各种的票,没有达到统一售票的目的(参考WindowT1的代码)
         *  > 2、要想达到统一售票的目的
         *  	必须让3个线程共享票数,所以ticket必须由成员变量需改为类变量(参考WindowT2的代码)
         *  > 3、统一售票的目的达到了,但是还存如下问题:
         *  	1、不同窗口卖出同号的票
         *  	2、有的窗口偶尔卖出不存的票号,如0号票或负数票
         *      > 解决方式:
         *  	a、使用同步代码块(参考WindowT3的代码)
         *  	b、使用同步方法(参考WindowT4的代码)
         */
        public static void main(String[] args) {
                /*
                 * 第一种情况
                 * 	结果
                 * 		每个窗口都是卖各自的票,各部相关
                 */
//		WindowT1 w1 = new WindowT1();
//		WindowT1 w2 = new WindowT1();
//		WindowT1 w3 = new WindowT1();
                
                /*
                 * 第二种情况 :存在重票、错票
                 * 	结果
                 		3号窗口: 19
                                2号窗口: 20
                                1号窗口: 20
                                1号窗口: 18
                                2号窗口: 17
                                3号窗口: 16
                                3号窗口: 15
                                1号窗口: 15
                                2号窗口: 15
                                3号窗口: 13
                                2号窗口: 14
                                1号窗口: 12
                                2号窗口: 11
                                3号窗口: 10
                                1号窗口: 11
                                3号窗口: 9
                                1号窗口: 7
                                2号窗口: 8
                                2号窗口: 5
                                3号窗口: 4
                                1号窗口: 6
                                3号窗口: 3
                                2号窗口: 2
                                1号窗口: 3
                                3号窗口: 1
                                2号窗口: 0
                                1号窗口: -1	
                 */
//		WindowT2 w1 = new WindowT2();
//		WindowT2 w2 = new WindowT2();
//		WindowT2 w3 = new WindowT2();
                
                /*
                 * 第三、四种情况
                 * 	结果 
                 * 		预期的结果,正常售票
                 */
//		WindowT3 w1 = new WindowT3();
//		WindowT3 w2 = new WindowT3();
//		WindowT3 w3 = new WindowT3();
                
                WindowT4 w1 = new WindowT4();
                WindowT4 w2 = new WindowT4();
                WindowT4 w3 = new WindowT4();
                                
                w1.setName("1号窗口");
                w2.setName("2号窗口");
                w3.setName("3号窗口");
                
                w1.start();
                w2.start();
                w3.start();
        }
}
// 使用成员变量,票数无法共享
class WindowT1 extends Thread {
        // 成员变量 总票数
        private int ticket = 20;
        
        @Override
        public void run() {
                while(true) {
                        if(ticket > 0) {
                                System.out.println(Thread.currentThread().getName() + ": " + ticket--);
                        }else {
                                break;
                        }
                }
        }
}
// 使用类变量,票数可以共享,但存在线程是不安全的
class WindowT2 extends Thread {
        // 类变量 总票数
        private static int ticket = 20;
        
        @Override
        public void run() {
                while(true) {
                        if(ticket > 0) {
                                try {
                                        // 使用睡眠的方式来让问题放大
                                        Thread.sleep(10);
                                } catch (InterruptedException e) {
                                        e.printStackTrace();
                                }
                                System.out.println(Thread.currentThread().getName() + ": " + ticket--);
                        }else {
                                break;
                        }
                }
        }
}
/*
 * 使用类变量,票数可以共享,保障线程的安全性
 * 方式一: 使用同步代码块
 * 	 注意: 使用同步代码块,线程使用继承的方式要注意,同步锁一定不能用this来充当对象锁,也不能用Object obj = new Object()的obj来充当,必须使用static关键字修饰为类变量才可,该方式必须使用非本对象锁(或类对象锁)
 */
class WindowT3 extends Thread {
        // 类变量 总票数
        private static int ticket = 20;
        
        @Override
        public void run() {
                while(true) {
                        // 使用this来做同步锁是达不到目的的,因为使用继承的方式,每个this都是不同的对象,所以每个线程竞争的都是各自的同步锁,而非同一把同步锁,所以使用this当同步锁是无法保障线程的安全性
//			synchronized(this) { 
                        // 使用类的对象可以保障线程的安全性   Class clazz = WindowT3.class;     这个类对象每个类只要一个,使用类对象等同于将方法同步
                        synchronized(WindowT3.class) { 
                                if(ticket > 0) {
                                        try {
                                                // 使用睡眠的方式来让问题放大
                                                Thread.sleep(500);
                                        } catch (InterruptedException e) {
                                                e.printStackTrace();
                                        }
                                        System.out.println(Thread.currentThread().getName() + ": " + ticket--);
                                }else {
                                        break;
                                }
                        }
                }
        }
}
/*
 * 使用类变量,票数可以共享,保障线程的安全性
 * 方式一: 使用同步方法
 * 	 注意: 使用同步方法,同步方法必须使用static修饰
 */
class WindowT4 extends Thread {
        // 类变量 总票数
        private static int ticket = 20;
        private static boolean f = true;
        
        @Override
        public void run() {
                while(f) {
                        show();
                }
        }
        
        // 使用继承的方式 同步方法不使用static修饰,相当于同步代码块中的this本对象锁,依旧无法保障线程的安全性
//	synchronized public void show() {
        // 使用继承的方式 同步方法必须使用static修饰,类似于同步代码块中的类对象锁
        synchronized public static void show() {
                if(ticket > 0) {
                        try {
                                // 使用睡眠的方式来让问题放大
                                Thread.sleep(500);
                        } catch (InterruptedException e) {
                                e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + ": " + ticket--);
                }else {
                        System.out.println("票已售罄!");
                        f = false;
                }
        }
}
使用实现Runnable接口的方式:
package org.xyz.java.thread.demo03;
/**
 * 使用实现Runnable接口的方式来实现多个窗口同时售票的案例场景
 * @author kevin.chen
 *
 */
public class WindowRunnable {
        /**
         * 该案例说明:
         * 	> 1、该案例创建了3个子线程来同时售票,售票的线程使用实现Runnable的方式,就具备了资源共享的作用,比使用继承的方式有天然的优势(参考WindowR1的代码),但存在线程安全隐患
         *  > 2、统一售票的目的达到了,但是还存如下问题:
         *  	1、不同窗口卖出同号的票
         *  	2、有的窗口偶尔卖出不存的票号,如0号票或负数票
         *      > 解决方式:
         *  	a、使用同步代码块(参考WindowR2的代码)
         *  	b、使用同步方法(参考WindowR3的代码)
         */
        public static void main(String[] args) {
                /*
                 *  第一种情况
                 *  结果
                  		1号窗口: 20
                                2号窗口: 19
                                3号窗口: 18
                                2号窗口: 17
                                1号窗口: 17
                                3号窗口: 16
                                2号窗口: 15
                                1号窗口: 14
                                3号窗口: 13
                                1号窗口: 12
                                2号窗口: 11
                                3号窗口: 10
                                2号窗口: 9
                                1号窗口: 8
                                3号窗口: 7
                                2号窗口: 6
                                3号窗口: 5
                                1号窗口: 4
                                2号窗口: 3
                                1号窗口: 2
                                3号窗口: 1
                                2号窗口: 0
                                1号窗口: -1
                 */
//		WindowR1 w = new WindowR1();
                
                /*
                 * 第二种情况
                 * 结果
                 * 	预期的结果,正常售票
                 */
//		WindowR2 w = new WindowR2();
                
                /*
                 * 第三种情况
                 * 结果
                 * 	预期的结果,正常售票
                 */
                WindowR3 w = new WindowR3();
                
                Thread t1 = new Thread(w);
                Thread t2 = new Thread(w);
                Thread t3 = new Thread(w);
                
                t1.setName("1号窗口");
                t2.setName("2号窗口");
                t3.setName("3号窗口");
                
                t1.start();
                t2.start();
                t3.start();
        }
}
//使用成员变量,票数就能直接共享,因为多个线程使用的是同一个对象,不存在多个对象的问题,但存在线程安全问题
class WindowR1 implements Runnable {
        // 成员变量 总票数
        private int ticket = 20;
        
        @Override
        public void run() {
                while(true) {
                        if(ticket > 0) {
                                try {
                                        // 使用睡眠的方式来让问题放大
                                        Thread.sleep(100);
                                } catch (InterruptedException e) {
                                        e.printStackTrace();
                                }
                                System.out.println(Thread.currentThread().getName() + ": " + ticket--);
                        }else {
                                break;
                        }
                }
        }
}
/*
 * 使用成员变量,票数可以共享,保障线程的安全性
 * 方式一: 使用同步代码块
 * 	 注意: 使用同步代码块,对比与使用继承的方式即可以使用this本对象锁,也可以使用WindowR2.class类对象锁
 */
class WindowR2 implements Runnable {
        // 成员变量 总票数
        private int ticket = 20;
        
        @Override
        public void run() {
                while(true) {
                        // 此时的this对多个线程来讲是同一个对象,竞争的是同一把对象锁,可以保证线程的安全性,也可以使用类对象锁
                        synchronized(this) { 
                                if(ticket > 0) {
                                        try {
                                                // 使用睡眠的方式来让问题放大
                                                Thread.sleep(100);
                                        } catch (InterruptedException e) {
                                                e.printStackTrace();
                                        }
                                        System.out.println(Thread.currentThread().getName() + ": " + ticket--);
                                }else {
                                        break;
                                }
                        }
                }
        }
}
/*
 * 使用成员变量,票数可以共享,保障线程的安全性
 * 方式一: 使用同步方法
 * 	 注意: 使用同步方法,同步方法对static修饰没有要求,可有可无
 */
class WindowR3 implements Runnable {
        // 成员变量 总票数
        private int ticket = 20;
        private boolean f = true;
                
        @Override
        public void run() {
                while(f) {
                        show();
                }
        }
        
        synchronized public void show() {
                if(ticket > 0) {
                        try {
                                // 使用睡眠的方式来让问题放大
                                Thread.sleep(500);
                        } catch (InterruptedException e) {
                                e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + ": " + ticket--);
                }else {
                        System.out.println("票已售罄!");
                        f = false;
                }
        }
}
两者方式的对比:
1、使用继承的时候在资源共享方面不如使用实现的方式,继承想要实现资源共享,必须使用static来将成员变量修饰为类变量
2、在使用同步的来保证线程的安全时,需要时刻注意线程在竞争同步锁的时候,多个线程竞争的锁是否为同一把锁,如果不是,无法保障线程的安全性
3、this关键字在不同的线程实现方式下,需要慎用,因此在使用多线的时候强烈推荐使用实现Runnable接口的方式,而不要继承Thread类的方式。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值