ThreadSynchronizationLock - juedaiyuer/researchNote GitHub Wiki
#java线程的同步与锁#
##线程交互##
###争用条件Race Condition###
当多个线程同时共享访问同一数据(内存区域)时,每个线程都尝试操作该数据,从而导致数据被破坏(corrupted),这种现象称为争用条件
线程的交互:互斥与同步
线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏
###同步问题代码###
package thread;
public class Foo {
private int x = 100;
public int getX() {
return x;
}
public int fix(int y) {
x = x - y;
return x;
}
}
package 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-A : 当前foo对象的x值= 40
Thread-B : 当前foo对象的x值= 40
Thread-A : 当前foo对象的x值= -20
Thread-B : 当前foo对象的x值= -50
Thread-A : 当前foo对象的x值= -80
Thread-B : 当前foo对象的x值= -80
保持结果的合理性,只需要达到一个目的,就是将对Foo的访问加以限制,每次只能有一个线程在访问。这样就能保证Foo对象中数据的合理性了
package thread;
public class Foo {
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;
}
}
package thread;
public class MyRunnable {
public static void main(String[] args) {
MyRunnable run = new MyRunnable();
Foo foo=new Foo();
MyThread t1 = run.new MyThread("线程A", foo, 10);
MyThread t2 = run.new MyThread("线程B", foo, -2);
MyThread t3 = run.new MyThread("线程C", foo, -3);
MyThread t4 = run.new MyThread("线程D", foo, 5);
t1.start();
t2.start();
t3.start();
t4.start();
}
class MyThread extends Thread {
private Foo foo;
/**当前值*/
private int y = 0;
MyThread(String name, Foo foo, int y) {
super(name);
this.foo = foo;
this.y = y;
}
public void run() {
foo.fix(y);
}
}
}
- java的每个对象都由一个内置锁
- 只能同步方法,不能同步变量和类
- 如果一个线程在对象上获得一个锁,就没有任何其他线程可以进入(该对象的)类中的任何一个同步方法
###静态方法同步###
#要同步静态方法,需要一个用于整个类对象的锁,这个对象就是这个类(XXX.class)
public static synchronized int setName(String name){
Xxx.name = name;
}
#等价于
public static int setName(String name){
synchronized(Xxx.class){
Xxx.name = name;
}
}
###如果线程不能获得锁会怎么样###
如果线程试图进入同步方法,而其锁已经被占用,则线程在该对象上被阻塞。实质上,线程进入该对象的的一种池中,必须在哪里等待,直到其锁被释放,该线程再次变为可运行或运行为止
当考虑阻塞时,一定要注意哪个对象正被用于锁定
- 调用同一个对象中非静态同步方法的线程将彼此阻塞。如果是不同对象,则每个线程有自己的对象的锁,线程间彼此互不干预。
- 调用同一个类中的静态同步方法的线程将彼此阻塞,它们都是锁定在相同的Class对象上。
- 静态同步方法和非静态同步方法将永远不会彼此阻塞,因为静态方法锁定在Class对象上,非静态方法锁定在该类的对象上。
- 对于同步代码块,要看清楚什么对象已经用于锁定(synchronized后面括号的内容)。在同一个对象上进行同步的线程将彼此阻塞,在不同对象上锁定的线程将永远不会彼此阻塞。
##线程同步##
###同步方法###
- 把竞争访问的资源标识为private;
- 同步哪些修改变量的代码,使用synchronized关键字同步方法或代码。
为了演示同步方法的使用,构建了一个信用卡账户,起初信用额为100w,然后模拟透支、存款等多个操作。显然银行账户User对象是个竞争资源,而多个并发操作的是账户方法oper(int x),当然应该在此方法上加上同步,并将账户的余额设为私有变量,禁止直接访问。
package thread;
public class ThreadSynchronizedMethod {
public static void main(String[] args) {
ThreadSynchronizedMethod t = new ThreadSynchronizedMethod();
User u = t.new User("张三", 100);
MyThread t1 = t.new MyThread("线程A", u, 20);
MyThread t2 = t.new MyThread("线程B", u, -60);
MyThread t3 = t.new MyThread("线程C", u, -80);
MyThread t4 = t.new MyThread("线程D", u, -30);
MyThread t5 = t.new MyThread("线程E", u, 32);
MyThread t6 = t.new MyThread("线程F", u, 21);
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
t6.start();
}
class MyThread extends Thread {
private User u;
/**存款金额*/
private int y = 0;
MyThread(String name, User u, int y) {
super(name);
this.u = u;
this.y = y;
}
public void run() {
u.oper(y);
}
}
class User {
/** 账号 */
private String code;
/** 余额 */
private int cash;
User(String code, int cash) {
this.code = code;
this.cash = cash;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
/**
* 存款
*
* @param x 欲存款金额
*
*/
public synchronized void oper(int x) {
try {
Thread.sleep(10L);
this.cash += x;
System.out.println("线程"+Thread.currentThread().getName() + "运行结束,增加“" + x
+ "”,当前用户账户余额为:" + cash);
Thread.sleep(10L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public String toString() {
return "User{" + "code='" + code + '\'' + ", cash=" + cash + '}';
}
}
}
###同步代码块###
##volatile关键字##
package thread;
public class ThreadVolatile extends Thread {
public static volatile int n = 0;
public void run() {
for (int i = 0; i < 10; i++)
try {
n = n + 1;
sleep(3); // 为了使运行结果更随机,延迟3毫秒
} catch (Exception e) {
}
}
public static void main(String[] args) throws Exception {
Thread threads[] = new Thread[100];
for (int i = 0; i < threads.length; i++)
// 建立100个线程
threads[i] = new ThreadVolatile();
for (int i = 0; i < threads.length; i++)
// 运行刚才建立的100个线程
threads[i].start();
for (int i = 0; i < threads.length; i++)
// 100个线程都执行完后继续
threads[i].join();
System.out.println("n=" + ThreadVolatile.n);
}
}
-
很多时侯输出的n都小于1000,这说明n=n+1不是原子级别的操作
-
n++,n=n+1表达式不是原子操作
package thread;
public class ThreadVolatile extends Thread { public static volatile int n = 0;
//使用synchronized关键字,让表达式原子操作 public static synchronized void inc(){ n++; } public void run() { for (int i = 0; i < 10; i++) try {
// n = n + 1; inc(); sleep(3); // 为了使运行结果更随机,延迟3毫秒
} catch (Exception e) { } } public static void main(String[] args) throws Exception { Thread threads[] = new Thread[100]; for (int i = 0; i < threads.length; i++) // 建立100个线程 threads[i] = new ThreadVolatile(); for (int i = 0; i < threads.length; i++) // 运行刚才建立的100个线程 threads[i].start(); for (int i = 0; i < threads.length; i++) // 100个线程都执行完后继续 threads[i].join(); System.out.println("n=" + ThreadVolatile.n); }
}
当变量的值由自身的上一个决定时,如n=n+1、n++等,volatile关键字将失效,只有当变量的值和自身上一个值无关时对该变量的操作才是原子级别的,如n = m + 1,这个就是原级别的。所以在使用volatile关键时一定要谨慎,如果自己没有把握,可以使用synchronized来代替volatile
##source##