synchronized & Lock 使用原理
synchronized
首先我们给出一个总体的代码出来,然后根据这个原始的代码一步一步地去修改验证来得出我们对这个锁的理解。
需求:首先我们写一个task类,类中只有num的属性;然后我们开启5个线程去,每个线程去跑一个for循环20,000次的代码,看看最后的num是不是为100,000。
class Task {
int num;
}
public class test {
public static void main(String[] args) {
test t = new test();
Task ta = new Task();
// 产生五个线程
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(new Runnable() { // 这是一种匿名内部类开启线程的新的方式
@Override
public void run() {
t.test(ta);
}
}
).start();
}
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ta.num);
}
// 创建一个同步锁 锁住的方法
public synchronized void test(Task obj) {
for (int j = 0; j < 20000; j++) {
obj.num++;
}
}
}
/*
上述代码运行结果:100000
*/
证明是没毛病的,的确锁住了。但是问题是,这里的线程安全是如何实现的,锁住了谁?是调用test方法的对象还是test方法里面的obj参数呢?
下面我们根据这个问题修改一下代码。。。。
假如我把那个 for循环 换到上面 调用test方法两万次 会不会有一样的结果?
// 这里我只修改一部分 可以对照上面代码可以知道修改了哪里
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 20000; j++) {
t.test(ta);
}
}
}
).start();
}
// 创建一个同步锁 锁住的方法
public synchronized void test(Task obj) {
obj.num++;
}
/*
运行结果还是100000
*/
那我们再创建一个类TestA,然后再写一个线程ThreadA,来实现TestA的test方法,还是处理原来的Task中的num属性。
class Task {
int num;
}
class TestA {
public synchronized void test(Task obj) {
obj.num++;
}
}
class ThreadA extends Thread {
TestA testA;
Task task;
// 初始化
public ThreadA(TestA testA, Task task) {
this.task = task;
this.testA = testA;
}
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 20000; i++) {
testA.test(task);
}
}
}
public class test {
public static void main(String[] args) {
test t = new test();
Task ta = new Task();
TestA testA = new TestA();
// 产生五个线程
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(new Runnable() {
@Override
public void run() {
t.test(ta);
}
}
).start();
new ThreadA(testA, ta).start(); // 开启ThreadA线程
}
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ta.num);// 输出num值
}
/*
输出结果为:184165
注:这里是输出随机数,但是会小于200,000
*/
那假如我们用sychronized 的代码块锁住obj会是什么样的结果呢?
public class test {
public void test(Task obj) {
synchronized (obj) {
obj.num++;
}
}
}
class TestA{
// 这里加了synchronized还是不会等于十个线程的num数
public void test(Task obj) {
synchronized (obj) {
obj.num++;
}
}
}
/*
运行结果为:200000
*/
综上所述,我们可以得出结论了:锁住的并不是这个形参,而是各自该类的产生的那五个线程对象。
所以说给这个方法加锁,就是给这个调用该方法的对象加锁!
那问题来了?锁住调用这个方法的对象有什么作用呢?
因为一个类中很多属性,锁住这个对象就把这个对象的所有属性全锁住了(例子中的类没有属性而已)。
class Task {
int num;
}
public class test {
public static void main(String[] args) {
// 这里新增加一个test类对象 t1
test t = new test();
test t1 = new test();
Task ta = new Task();
// 产生五个线程
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(new Runnable() { // 创建线程
@Override
public void run() {
for (int j = 0; j < 20000; j++) {
t.test(ta);
}
}
}
).start();
new Thread(new Runnable() { // 创建线程
@Override
public void run() {
for (int j = 0; j < 20000; j++) {
t1.test(ta);
}
}
}
).start();
}
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ta.num);
}
public synchronized void test(Task obj) {
obj.num++;
}
}
/*
运行结果为:174370
*/
这里为什么又锁不住了呢?因为前面说了sychronized 加在方法上就是锁住调用该方法的对象。但是这两个对象不是同一个对象呀!一个是t,一个是t1。所以还是会共享数据的,那如何解决这个问题呢?在test方法上加static使其成为静态方法。加入static之后,不管是类还是对象调用,都是被锁住了。
}
public class test {
public static synchronized void test(Task obj) {
obj.num++;
}
}
/*
运行结果为:200000
*/
Lock
既然我们知道了sychronized 的原理了,那lock原理也是如此的,只不过lock需要自己手动释放锁。
// 上述原始代码的复用,只不过是改了一下
class Task {
int num;
}
public class test {
// 这里加入了static就可以了锁定了调用该方法的类对象或者是类
static Lock lock = new ReentrantLock();
public void test(Task obj) {
lock.lock();
obj.num++;
lock.unlock();
}
}
/*
运行结果为:200000
*/