Java 多线程 - 线程的同步与锁的问题
这里有新鲜出炉的 Java 并发编程示例, 程序狗速度看过来!
Java 程序设计语言
java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言, 是由 Sun Microsystems 公司于 1995 年 5 月推出的 Java 程序设计语言和 Java 平台 (即 JavaEE(j2ee), JavaME(j2me), JavaSE(j2se)) 的总称
线程的同步是为了防止多个线程访问一个数据对象时, 对数据造成的破坏本篇文章主要介绍了 Java 多线程 - 线程的同步与锁的问题, 有兴趣的可以了解一下
一同步问题提出
线程的同步是为了防止多个线程访问一个数据对象时, 对数据造成的破坏
例如: 两个线程 ThreadAThreadB 都操作同一个对象 Foo 对象, 并修改 Foo 对象上的数据
- package cn.thread;
- public class Foo {
- private int x = 100;
- public int getX() {
- return x;
- }
- public int fix(int y) {
- x = x - y;
- return x;
- }
- }
- package cn.thread;
- public class MyRunnable implements Runnable {
- private Foo foo = new Foo();
- public static void main(String[] args) {
- MyRunnable run = new MyRunnable();
- Thread ta = new Thread(run, "Thread-A");
- Thread tb = new Thread(run, "Thread-B");
- ta.start();
- tb.start();
- }
- public void run() {
- for (int i = 0; i < 3; i++) {
- this.fix(30);
- try {
- Thread.sleep(1);
- } catch(InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName() + ": 当前 foo 对象的 x 值 =" + foo.getX());
- }
- }
- public int fix(int y) {
- return foo.fix(y);
- }
- }
运行结果:
Thread-B : 当前 foo 对象的 x 值 = 40
Thread-A : 当前 foo 对象的 x 值 = 40
Thread-B : 当前 foo 对象的 x 值 = -20
Thread-A : 当前 foo 对象的 x 值 = -20
Thread-B : 当前 foo 对象的 x 值 = -80
Thread-A : 当前 foo 对象的 x 值 = -80
从结果发现, 这样的输出值明显是不合理的原因是两个线程不加控制的访问 Foo 对象并修改其数据所致
如果要保持结果的合理性, 只需要达到一个目的, 就是将对 Foo 的访问加以限制, 每次只能有一个线程在访问这样就能保证 Foo 对象中数据的合理性了
在具体的 Java 代码中需要完成一下两个操作:
把竞争访问的资源类 Foo 变量 x 标识为 private;
同步哪些修改变量的代码, 使用 synchronized 关键字同步方法或代码
- package cn.thread;
- public class Foo2 {
- private int x = 100;
- public int getX() {
- return x;
- }
- // 同步方法
- public synchronized int fix(int y) {
- x = x - y;
- System.out.println("线程" + Thread.currentThread().getName() + "运行结束, 减少" + y + ", 当前值为:" + x);
- return x;
- }
- // // 同步代码块
- // public int fix(int y) {
- // synchronized (this) {
- // x = x - y;
- // System.out.println("线程"+Thread.currentThread().getName() + "运行结束, 减少" + y
- // + ", 当前值为:" + x);
- // }
- //
- // return x;
- // }
- }
- package cn.thread;
- public class MyRunnable2 {
- public static void main(String[] args) {
- MyRunnable2 run = new MyRunnable2();
- Foo2 foo2 = new Foo2();
- MyThread t1 = run.new MyThread("线程 A", foo2, 10);
- MyThread t2 = run.new MyThread("线程 B", foo2, -2);
- MyThread t3 = run.new MyThread("线程 C", foo2, -3);
- MyThread t4 = run.new MyThread("线程 D", foo2, 5);
- t1.start();
- t2.start();
- t3.start();
- t4.start();
- }
- class MyThread extends Thread {
- private Foo2 foo2;
- /** 当前值 */
- private int y = 0;
- MyThread(String name, Foo2 foo2, int y) {
- super(name);
- this.foo2 = foo2;
- this.y = y;
- }
- public void run() {
- foo2.fix(y);
- }
- }
- }
线程线程 A 运行结束, 减少 10, 当前值为: 90
线程线程 C 运行结束, 减少 - 3, 当前值为: 93
线程线程 B 运行结束, 减少 - 2, 当前值为: 95
线程线程 D 运行结束, 减少 5, 当前值为: 90
二同步和锁定
1 锁的原理
Java 中每个对象都有一个内置锁
当程序运行到非静态的 synchronized 同步方法上时, 自动获得与正在执行代码类的当前实例 (this 实例) 有关的锁获得一个对象的锁也称为获取锁锁定对象在对象上锁定或在对象上同步
当程序运行到 synchronized 同步方法或代码块时该对象锁才起作用
一个对象只有一个锁所以, 如果一个线程获得该锁, 就没有其他线程可以获得锁, 直到第一个线程释放 (或返回) 锁这也意味着任何其他线程都不能进入该对象上的 synchronized 方法或代码块, 直到该锁被释放
释放锁是指持锁线程退出了 synchronized 同步方法或代码块
关于锁和同步, 有一下几个要点:
1)只能同步方法, 而不能同步变量和类;
2)每个对象只有一个锁; 当提到同步时, 应该清楚在什么上同步? 也就是说, 在哪个对象上同步?
3)不必同步类中所有的方法, 类可以同时拥有同步和非同步方法
4)如果两个线程要执行一个类中的 synchronized 方法, 并且两个线程使用相同的实例来调用方法, 那么一次只能有一个线程能够执行方法, 另一个需要等待, 直到锁被释放也就是说: 如果一个线程在对象上获得一个锁, 就没有任何其他线程可以进入 (该对象的) 类中的任何一个同步方法
5)如果线程拥有同步和非同步方法, 则非同步方法可以被多个线程自由访问而不受锁的限制
6)线程睡眠时, 它所持的任何锁都不会释放
7)线程可以获得多个锁比如, 在一个对象的同步方法里面调用另外一个对象的同步方法, 则获取了两个对象的同步锁
8)同步损害并发性, 应该尽可能缩小同步范围同步不但可以同步整个方法, 还可以同步方法中一部分代码块
9)在使用同步代码块时候, 应该指定在哪个对象上同步, 也就是说要获取哪个对象的锁例如:
- public int fix(int y) {
- synchronized(this) {
- x = x - y;
- }
- return x;
- }
当然, 同步方法也可以改写为非同步方法, 但功能完全一样的, 例如:
- public synchronized int getX() {
- return x++;
- }
与
- public int getX() {
- synchronized(this) {
- return x++;
- }
- }
效果是完全一样的
三静态方法同步
要同步静态方法, 需要一个用于整个类对象的锁, 这个对象就是这个类(XXX.class)
例如:
- public static synchronized int setName(String name) {
- Xxx.name = name;
- }
等价于
- public static int setName(String name) {
- synchronized(Xxx.class) {
- Xxx.name = name;
- }
- }
四如果线程不能获得锁会怎么样
如果线程试图进入同步方法, 而其锁已经被占用, 则线程在该对象上被阻塞实质上, 线程进入该对象的的一种池中, 必须在哪里等待, 直到其锁被释放, 该线程再次变为可运行或运行为止
当考虑阻塞时, 一定要注意哪个对象正被用于锁定:
1 调用同一个对象中非静态同步方法的线程将彼此阻塞如果是不同对象, 则每个线程有自己的对象的锁, 线程间彼此互不干预
2 调用同一个类中的静态同步方法的线程将彼此阻塞, 它们都是锁定在相同的 Class 对象上
3 静态同步方法和非静态同步方法将永远不会彼此阻塞, 因为静态方法锁定在 Class 对象上, 非静态方法锁定在该类的对象上
4 对于同步代码块, 要看清楚什么对象已经用于锁定 (synchronized 后面括号的内容) 在同一个对象上进行同步的线程将彼此阻塞, 在不同对象上锁定的线程将永远不会彼此阻塞
五何时需要同步
在多个线程同时访问互斥 (可交换) 数据时, 应该同步以保护数据, 确保两个线程不会同时修改更改它
对于非静态字段中可更改的数据, 通常使用非静态方法访问
对于静态字段中可更改的数据, 通常使用静态方法访问
如果需要在非静态方法中使用静态字段, 或者在静态字段中调用非静态方法, 问题将变得非常复杂已经超出 SJCP 考试范围了
六线程安全类
当一个类已经很好的同步以保护它的数据时, 这个类就称为线程安全的
即使是线程安全类, 也应该特别小心, 因为操作的线程是间仍然不一定安全
七线程同步小结
1 线程同步的目的是为了保护多个线程访问一个资源时对资源的破坏
2 线程同步方法是通过锁来实现, 每个对象都有切仅有一个锁, 这个锁与一个特定的对象关联, 线程一旦获取了对象锁, 其他访问该对象的线程就无法再访问该对象的其他同步方法
3 对于静态同步方法, 锁是针对这个类的, 锁对象是该类的 Class 对象静态和非静态方法的锁互不干预一个线程获得锁, 当在一个同步方法中访问另外对象上的同步方法时, 会获取这两个对象锁
4 对于同步, 要时刻清醒在哪个对象上同步, 这是关键
5 编写线程安全的类, 需要时刻注意对多个线程竞争访问资源的逻辑和安全做出正确的判断, 对原子操作做出分析, 并保证原子操作期间别的线程无法访问竞争资源
6 当多个线程等待一个对象锁时, 没有获取到锁的线程将发生阻塞
7 死锁是线程间相互等待锁锁造成的, 在实际中发生的概率非常的小真让你写个死锁程序, 不一定好使, 呵呵但是, 一旦程序发生死锁, 程序将死掉
来源: http://www.phperz.com/article/18/0210/359385.html