A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

在使用java多线程的时候如果着多个线程访问的是同一个共享数据;

例如售票, 多个售票点线程各自售票, 但都是从一个地方取票, 这就会造成操作系统让多个线程能够"同时"访问这个共享数据, 所谓同时并不是真正意义上的同时,而是很短间隔;

只是这个线程不能一次性执行完它的线程体, 比如说它现在只拿到了共享变量的值, 但是还没有做修改,

但是这时很可能会由于cpu分配给自己的时间片用完了而进入阻塞状态, 这时候另一个线程开始, 并且成功的拿到了并且修改了这个共享变量,的值;

很显然他们俩拿到的是同一个值,这就是问题所在.
于是线程同步就提出来了;
而线程同步又是基于对象锁的;
一般用对象锁更灵活;
未加线程代码:

package MultiThread;

public class TicketSellTest{
        public static void main(String[] args) {
                System.out.println("main线程开始");
                TicketOffice tick = new TicketOffice();
                Thread s1 = new Thread(tick);
                Thread s2 = new Thread(tick);
                Thread s3 = new Thread(tick);
                s1.setName("售票点1 ");
                s2.setName("售票点2 ");
                s3.setName("售票点3 ");
                s1.start();
                s2.start();
                s3.start();
                System.out.println("main线程结束");
        }
}

class TicketOffice implements Runnable{
        // 售出的票计数器
        private int tickets = 0;
        // 不管用这个类创立多少个线程, 每个线程执行start()的时候只执行run()方法
        public void run() {
                boolean flag = true;
                while(flag) {
                        flag = sell();
                }
        }
        // 售票方法
        private boolean sell() {
                boolean flag = true;
                if (tickets < 10) {
                        tickets = tickets + 1;
                        // 获取正在执行这里的线程对象的引用的名字, 即是获取正在执行这里的线程名字
                        System.out.println(Thread.currentThread().getName() + "卖出第 "+tickets +" 张票");
                }else {
                        System.out.println("无票可卖!!");
                        flag = false;
                }
                // 为了增大出错几率, 让线程停留一会儿
                try {
                        Thread.sleep(300);
                }catch(InterruptedException e) {
                        System.out.println("售卖过程中出现InterruptedException");
                        e.printStackTrace();
                }
                // 返回True表示有票可卖
                return flag;
        }
}
// 执行结果
// 可以看到这里就是由于不加限制访问共享变量造成的后果, 就是一个线程没有完成一个完整的操作, 就被另一个线程介入了
main线程开始
售票点1 卖出第 2 张票
main线程结束
售票点2 卖出第 2 张票
售票点3 卖出第 3 张票
售票点1 卖出第 4 张票
售票点2 卖出第 6 张票
售票点3 卖出第 6 张票
售票点1 卖出第 7 张票
售票点2 卖出第 8 张票
售票点3 卖出第 9 张票
售票点1 卖出第 10 张票
无票可卖!!
无票可卖!!
无票可卖!!

然后解决办法是进行线程同步,此处有两个方法:
第一个

//将sell()方法变成一个同步方法,即在方法定义的时候加入synchronized修饰符
//如下
private synchronized boolean sell(){}
//这会使得此方法为同步方法,作用是每次线程执行此方法不会被打断,一定要执行完

还有一个方法是将操作共享变量的那一块代码变成同步代码块,即用synchronized(this){}将操作共享变量的那段代码包围起来;
其中括号里的参数表示获取制定对象的对象锁;
如下:


        // 售票方法
        private  boolean sell() {
                boolean flag = true;
                // 加入同步代码块,这个this表示调用此方法的对象的引用;获取此对象的对象锁
                synchronized(this) {
                        if (tickets < 10) {
                                tickets = tickets + 1;
                                // 获取正在执行这里的线程对象的引用的名字, 即是获取正在执行这里的线程名字
                                System.out.println(Thread.currentThread().getName() + "卖出第 "+tickets +" 张票");
                        }else {
                                System.out.println("无票可卖!!");
                                flag = false;
                                }
                }
                // 为了增大出错几率, 让线程停留一会儿
                try {
                        Thread.sleep(300);
                }catch(InterruptedException e) {
                        System.out.println("售卖过程中出现InterruptedException");
                        e.printStackTrace();
                }
                // 返回True表示有票可卖
                return flag;
        }
//这样的运行结果就不会有冲突,如下
main线程开始
售票点1 卖出第 1 张票
售票点2 卖出第 2 张票
main线程结束
售票点3 卖出第 3 张票
售票点1 卖出第 4 张票
售票点2 卖出第 5 张票
售票点3 卖出第 6 张票
售票点1 卖出第 7 张票
售票点2 卖出第 8 张票
售票点3 卖出第 9 张票
售票点1 卖出第 10 张票
无票可卖!!
无票可卖!!
无票可卖!!

对象锁

同步机制的实现就是利用了对象锁;

JVM对每一个对象都有一个对象锁; 这个对象锁代表任何时候只允许一个线程能够有访问权限;

即是如果线程获取了这个对象的对象锁, 那么在它释放之前这段时间里面其他线程都不能访问;

其作用和synchronized同步机制是一样的,但是这个对象锁用起来更为灵活;

java提供了一种显示加锁机制, 使用java.util.concurrent.locks.Lock接口提供的lock()方法来获取锁;

用unlock()方法解锁,通常使用ReentrantLock这个类来实现, 上面更改之处的示例如下:

// 对象锁的实现所依赖的包
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

        // 售票方法
        // 创建Lock锁实例
        private Lock lock = new ReentrantLock();
        private boolean sell() {
                boolean flag = true;
                // 获取对象锁
                lock.lock();
                if (tickets < 10) {
                        tickets = tickets + 1;
                        // 获取正在执行这里的线程对象的引用的名字, 即是获取正在执行这里的线程名字
                        System.out.println(Thread.currentThread().getName() + "卖出第 "+tickets +" 张票");
                }else {
                        System.out.println("无票可卖!!");
                        flag = false;
                        }
                // 释放锁
                lock.unlock();


142 个回复

倒序浏览
孙丽 来自手机 中级黑马 2020-3-3 11:45:22
沙发
6666666666666666
回复 使用道具 举报
孙丽 来自手机 中级黑马 2020-3-3 11:45:55
藤椅
6666666666666666
回复 使用道具 举报
孙丽 来自手机 中级黑马 2020-3-3 11:47:44
板凳
6666666666666666
回复 使用道具 举报
厉害厉害
回复 使用道具 举报
改变是痛苦的,但成长的过程更是喜悦的,一定要加油!
回复 使用道具 举报
duanshaobo 来自手机 中级黑马 2020-3-3 12:09:52
7#
╭( ′• o •′ )╭☞就是这个人!
回复 使用道具 举报
棒棒哒!
回复 使用道具 举报
改变是痛苦的,但成长的过程更是喜悦的,一定要加油!
回复 使用道具 举报
改变是痛苦的,但成长的过程更是喜悦的,一定要加油!
回复 使用道具 举报
感谢分享  学到了   很厉害              
回复 使用道具 举报
改变是痛苦的,但成长的过程更是喜悦的,一定要加油!
回复 使用道具 举报
感谢分享  加油哦
回复 使用道具 举报
加油呢,感谢分享,撒花
回复 使用道具 举报
66666666666666666666666
回复 使用道具 举报
回复 使用道具 举报
改变是痛苦的,但成长的过程更是喜悦的,一定要加油!
回复 使用道具 举报
棒棒哒,感谢分享
回复 使用道具 举报
6666666666666666666666666666666
回复 使用道具 举报
键盘敲烂,月薪过万^_^^_^
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马