线程的同步

来源:互联网 发布:qq微信头像知乎 编辑:程序博客网 时间:2024/06/05 06:11

在 单 线 程 程 序 中 , 每 次 只 能 做 一 件 事 情 , 后 面 的 事 情 需 要 等 待 前 面 的 事 情 完 成 后 才 可 以 进 行 : 但 是 如 果 使 用 多 线 程 程 序 , 就 会 发 生 两 个 线 程 抢 占 资 源 的 问 题 , 例 如 两 个 人 同 时 说 话 、 两 个 人 同 时 过 同 一 个 独 木 桥 等 。 所 以 在 多 线 程 编 程 中 , 需 要 防 止 这 些 资 访 问 的 冲 突 。 Java 提 供 线 程 同 步 的 机 制 来 防 止 资 源 访 问 的 冲 突 。


线 程 安 全

实 际 开 发 中 , 使 用 多 线 程 程 序 的 情 况 很 多 , 如 银 行 排 号 系 统 、 火 车 站 售 票 系 统 等 。 这 种 多 线 程 的 程 序 通 常 会 发 生 问 题 , 以 火 车 站 售 票 系 统 为 例 , 在 代 码 中 判 断 当 前 票 数 是 否 大 于 0 , 如 果 大 于 0 则 执 行 将 该 票 出 售 给 乘 客 功 能 , 但 当 两 个 线 程 同 时 访 问 这 段 代 码 时 ( 假 如 这 时 只 剩 下 一 张 票 ) , 第 一 个 线 程 将 票 售 出 , 与 此 同 时 第 二 个 线 程 也 已 经 执 行 完 成 判 断 是 否 有 票 的 操 作 , 并 得 出 结 论 票 数 大 于 0 , 于 是 它 也 执 行 售 出 操 作 , 这 样 就 会 产 生 负 数 。 所 以 在 编 写 多 线 程 程 序 时 , 应 该 考 虑 到 线 程 安 全 问 题 。 实 质 上 线 程 安 全 问 题 来 源 于 两 个 线 程 同 时 存 取 单 一 对 象 的 数 据 。

〖 例 〗 在 项 目 中 创 建 ThreadSafeTest 类 , 该 类 继 承 了 Runnable 类 , 主 要 实 现 模 拟 火 车 站 售 票 系 统 。

public class ThreadSafeTest implements Runnable{    int sum=10;   //设置当前总票数    @Override    public void run() {        // TODO Auto-generated method stub        while(true){            if(sum>0){                try {                    Thread.sleep(100);                } catch (InterruptedException e) {                    // TODO Auto-generated catch block                    e.printStackTrace();                }                System.out.println("车票还有"+(sum--)+"张车票");            }        }    }    public static void main(String[] args) {        //实例化对象        ThreadSafeTest tst=new ThreadSafeTest();        /*         * 创建4个线程         */        Thread t1=new Thread(tst);        Thread t2=new Thread(tst);        Thread t3=new Thread(tst);        Thread t4=new Thread(tst);        /*         * 启动线程         */        t1.start();        t2.start();        t3.start();        t4.start();    }}

结果:

车票还有10张车票
车票还有8张车票
车票还有9张车票
车票还有10张车票
车票还有7张车票
车票还有6张车票
车票还有5张车票
车票还有4张车票
车票还有3张车票
车票还有2张车票
车票还有1张车票
车票还有0张车票
车票还有-1张车票
车票还有-2张车票

结果分析: 我们发现最后售出的票为负值,这样说明这个程序出了问题。这是由于同时创建了4个线程,这四个线程在最后票数为1时,进入run方法,都判定sum变量大于0,但此时,4个线程都对它进行递减操作,因此产生了负值。

线 程 同 步 机 制

如 何 解 决 资 源 共 享 的 问 题 ? 基 本 上 所 有 解 决 多 线 程 资 源 冲 突 问 题 都 会 采 用 给 定 时 间 只 允 许 一 个 线 程 访 问 共 享 资 源 , 这 时 就 需 要 给 共 享 资 源 上 一 道 锁 。 这 就 好 比 一 个 人 上 洗 手 间 , 这 个 人 进 入 洗 手 间 后 将 门 锁 上 , 当 他 出 来 时 再 将 锁 打 开 , 然 后 其 他 人 才 可 以 进 入 。

同 步 块

在 Java 中 提 供 了 同 步 机 制 , 可 以 有 效 地 防 止 资 冲 突 。 同 步 机 制 使 用synchronized 关 键 字 。

〖 例 〗 创 建 CopyOfThreadSafeTest 类 , 使 对 num 操 作 的 代 码 设 置 在 同 步 块 中 。

public class CopyOfThreadSafeTest implements Runnable{    int num=10;    @Override    public void run() {        while(true){            synchronized(""){                if(num>0){                    try {                        Thread.sleep(1000);                    } catch (InterruptedException e) {                        // TODO Auto-generated catch block                        e.printStackTrace();                    }                    System.out.println("tickets"+--num);                }            }        }    }    public static void main(String[] args) {        CopyOfThreadSafeTest t=new CopyOfThreadSafeTest();        Thread t1=new Thread(t);        Thread t2=new Thread(t);        Thread t3=new Thread(t);        Thread t4=new Thread(t);        t1.start();        t2.start();        t3.start();        t4.start();    }}

结果:

tickets9
tickets8
tickets7
tickets6
tickets5
tickets4
tickets3
tickets2
tickets1
tickets0

结果分析:

打 印 到 最 后 票 数 没 有 出 现 负 数 , 这 是 因 为 将 资 源 放 置 在 了 同 步 块 中 ·。这 个 同 步 块 也 被 称 为 临 界 区 , 它 使 用 synchronized 关 键 字 建 立 。 其 语 法 格 式 如 下 :

synchronized(Object){}

通 常 是 将 共 享 资 源 的 操 作 放 置 在 synchronized 定 义 的 区 域 内 , 这 样 当 其 他 线 程 也 获 取 到 这 个 锁 时 , 必 须 等 待 锁 被 释 放 时 才 能 进 入 该 区 域 。 Object 为 任 意 一 个 对 象 , 每 个 对 象 都 存 在 一 个 标 志 位 , 并 具 有 两 个 值 , 分 别 为 0 和 1。 一 个 线 程 运 行 到 同 步 块 时 首 先 检 查 该 对 象 的 标 志 位 , 如 果 为 0 状 态 , 表 明 此 同 步 块 中 存 在 其 他 线 程 在 运 行 。 这 时 该 线 程 处 于 就 绪 状 态 , 直 到 处 于 同 步 块 中 的 线 程 执 行 完 同 步 块 中 的 代 码 为 止 。 这 时 该 对 象 的 标 识 位 被 设 置 为 1 , 该 线 程 才 能 执 行 同 步 块 中 的 代 码 , 并 将 Object 对 象 的 标 识 位 设 置 为 0 , 防 止 其 他 线 程 执 行 同 步 块 中 的 代 码 。

同步方法

同 步 方 法 就 是 在 方 法 前 面 修 饰 synchromzed 关 键 字 的 方 法 , 其 语 法 格 式 如 下 :

synchromzed void f(){}

当 某 个 对 象 调 用 了 同 步 方 法 时 , 该 对 象 上 的 其 他 同 步 方 法 必 须 等 待 该 同 步 方 法 执 行 完 毕 才 能 被 执 行 。 必 须 将 每 个 能 访 问 共 享 资 源 的 方 法 修 饰 为 synchronized , 否 则 就 会 出 错 。

〖 例 〗在 项 目 中 创 建 一 个 类 文 件 , 在 该 类 中 定 义 同 步 方 法 。

public class SynchronizedTest implements Runnable{    int num=10;    public synchronized void doit(){   //定义同步方法        if(num>0){            try {                Thread.sleep(1000);            } catch (InterruptedException e) {                // TODO Auto-generated catch block                e.printStackTrace();            }            System.out.println("tickets"+--num);        }    }    @Override    public void run() {   //在run()方法中调用该同步方法        while(true){            doit();        }       }    public static void main(String[] args) {        SynchronizedTest st=new SynchronizedTest();        Thread t1=new Thread(st);        Thread t2=new Thread(st);        Thread t3=new Thread(st);        Thread t4=new Thread(st);        t1.start();        t2.start();        t3.start();        t4.start();    }}

结果:

tickets9
tickets8
tickets7
tickets6
tickets5
tickets4
tickets3
tickets2
tickets1
tickets0

分析:

将 共 享 的 资 源 放 置 在 同 步 方 法 中 , 运 行 结 果 与 使 用 同 步 块 的 结 果 一 致 。

原创粉丝点击