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 个回复

正序浏览
逆风TO 发表于 2020-3-9 09:46
感谢支持  加油加油!

感谢支持  加油加油!
回复 使用道具 举报
逆风TO 发表于 2020-3-9 09:39
感谢支持  加油加油!

感谢支持  加油加油!
回复 使用道具 举报
逆风TO 发表于 2020-3-9 09:45
感谢支持  加油加油!

感谢支持  加油加油!
回复 使用道具 举报
逆风TO 发表于 2020-3-9 09:36
感谢支持  加油加油!

感谢支持  加油加油!
回复 使用道具 举报
lzq123 发表于 2020-3-5 18:55
6666666666666666666666666666666

感谢支持  加油加油!
回复 使用道具 举报

感谢支持  加油加油!
回复 使用道具 举报
daoqin 发表于 2020-3-6 10:22
就业篇,面试时我们应该保持怎样的一个心态

感谢支持  加油加油!
回复 使用道具 举报
我是小圆圆 发表于 2020-3-5 16:52
键盘敲烂,月薪过万^_^^_^

感谢支持  加油加油!
回复 使用道具 举报

感谢支持  加油加油!
回复 使用道具 举报
hongping 发表于 2020-3-6 10:06
感谢分享  加油哦~

感谢支持  加油加油!
回复 使用道具 举报
daoqin 发表于 2020-3-6 10:22
就业篇,面试时我们应该保持怎样的一个心态

感谢支持  加油加油!
回复 使用道具 举报
1467584 发表于 2020-3-5 14:39
666666666666666666666666666666666666

感谢支持  加油加油!
回复 使用道具 举报
lzq123 发表于 2020-3-5 18:55
6666666666666666666666666666666

感谢支持  加油加油!
回复 使用道具 举报
逆风TO 发表于 2020-3-9 09:36
感谢支持  加油加油!

感谢支持  加油加油!
回复 使用道具 举报

感谢支持  加油加油!
回复 使用道具 举报

感谢支持  加油加油!
回复 使用道具 举报
逆风TO 发表于 2020-3-9 09:36
感谢支持  加油加油!

感谢支持  加油加油!
回复 使用道具 举报
1467584 发表于 2020-3-5 14:39
666666666666666666666666666666666666

感谢支持  加油加油!
回复 使用道具 举报

感谢支持  加油加油!
回复 使用道具 举报

感谢支持  加油加油!
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马