总结:传统线程互斥技术,即为synchronzied操作,锁的概念。至于为什么要加锁?新手可以好考虑一下,老鸟们无视。
重点记录几点概念:
A. synchronized 锁的是对象。且要为同一个对象,而不是对象的引用。
示例代码:
package com.victor.thread;
/**
*
* @author Victor
*
*/
public class TraditionalSynchronizedThread_004 {
Object o = new Object();
void m() {
synchronized (o) {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}
}
public static void main(String[] args) {
final TraditionalSynchronizedThread_004 a = new TraditionalSynchronizedThread_004();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
a.m();
}
}, "t1");
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
a.m();
}
}, "t2");
// 在这里对象发生了改变,这里的m方法锁起不到作用了
a.o = new Object();
t2.start();
}
}
B. 尽量避免将字符串常量当做锁的对象,可能出现死锁的现象。
例子1:String a=“aaa”;
String b=“aaa”;
这里的 a和b是同一个对象;这里 “aaa” 存放在同一堆内存中。
例子2:String key =null;
public String getResult(String key1,String key2){
key = key1+key2;
return key;
}
key = getResult(”a”,””);
key = getResult(“a”,””);
这里的key就不是同一个对象了。
如果是 key=“a”+””;
key=”a”+””;
这才是同一对象
编译器会自动优化key=“a”;
C. 一个类中当一个同步方法执行中,另一个非同步方法也是可以运行的。S
也就说同步方法和非同步方法是可以同时调用的
示例代码:
package com.victor.thread;
/**
* 一个类中同步方法和非同步方法可以同时运行
*
* @author Victor
*
*/
public class TraditionalSynchronizedThread_005 {
public static void main(String[] args) {
final TraditionalSynchronizedThread_005 o = new TraditionalSynchronizedThread_005();
new Thread(new Runnable() {
@Override
public void run() {
o.m1();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
o.m2();
}
}).start();
}
private synchronized void m1() {
while (true) {
try {
System.out.println("m1 start");
Thread.sleep(5000);
System.out.println("m1 end");
} catch (Exception e) {
e.printStackTrace();
}
}
}
private void m2() {
while (true) {
try {
Thread.sleep(3000);
System.out.println("m2");
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
D. 一个类的一个同步方法可以调用另外一个同步方法,同一个线程已经拥有某个对象的锁,再次申请的时候仍然会得到该对象的锁,也就是说synchronized获得的锁是可以重(新进)入的。
示例代码:
package com.victor.thread;
/**
* 一个类的一个同步方法可以调用另外一个同步方法,同一个线程已经拥有某个对象的锁, 再次申请的时候仍然会得到该对象的锁,
* 也就是说synchronized获得的锁是可以重入的。
*
* @author Victor
*
*/
public class TraditionalSynchronizedThread_006 {
private synchronized void m1() {
try {
System.out.println("m1");
Thread.sleep(1000);
m2();
} catch (Exception e) {
e.printStackTrace();
}
}
private synchronized void m2() {
try {
Thread.sleep(2000);
System.out.println("m2");
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
final TraditionalSynchronizedThread_006 o = new TraditionalSynchronizedThread_006();
new Thread(new Runnable() {
@Override
public void run() {
o.m1();
}
}).start();
}
}
E. 对业务写方法加锁,对读方法不加锁,容易产生脏读问题(dirtyRead)
解决办法,对读也加锁
示例代码:
package com.victor.thread;
/**
* 对业务写方法加锁,
* 对读方法不加锁,
* 容易产生脏读问题(dirtyRead)
* @author Victor
*
*/
public class TraditionalSynchronizedThread_007 {
private String name;
private double account;
public synchronized void setAccount(String name,double account) {
this.name = name;
try {
Thread.sleep(10000);
} catch (Exception e) {
e.printStackTrace();
}
this.account = account;
}
public /*synchronized */ double getAccount(String name) {
return account;
}
public static void main(String[] args) {
final TraditionalSynchronizedThread_007 o = new TraditionalSynchronizedThread_007();
new Thread(new Runnable() {
@Override
public void run() {
o.setAccount("张三", 100);
}
}).start();
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(o.getAccount("张三"));
try {
Thread.sleep(10000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(o.getAccount("张三"));
}
}
F. 程序在执行过程中,如果出现异常,默认情况锁会被释放。
示例代码:
package com.victor.thread;
/**
* 程序在执行过程中,如果出现异常,默认情况锁会被释放
* 所以,在并发处理的过程中,有异常要多加小心,不然可能会发生不一致的情况。
* 比如,在一个web app处理过程中,多个servlet线程共同访问同一个资源,这时如果异常处理不合适,
* 在第一个线程中抛出异常,其他线程就会进入同步代码区,有可能会访问到异常产生时的数据。
* 因此要非常小心的处理同步业务逻辑中的异常
* @author Victor
*/
public class TraditionalSynchronizedThread_008 implements Runnable{
int count =0;
@Override
public void run() {
m();
}
private synchronized void m(){
System.out.println(Thread.currentThread().getName()+" start");
while (true) {
count++;
try {
System.out.println(Thread.currentThread().getName()+" count:"+count);
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
if(count == 5) {
int i = 1/0; //此处抛出异常,锁将被释放,要想不被释放,可以在这里进行catch,然后让循环继续
}
}
}
public static void main(String[] args) {
TraditionalSynchronizedThread_008 o = new TraditionalSynchronizedThread_008();
new Thread(o).start();
try {
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
}
new Thread(o).start();
}
}
G. synchronized优化: 同步代码块中的语句越少越好也就说细粒度锁比粗粒度锁效率更高。 采用细粒度的锁,可以使线程争用时间变短,从而提高效率
示例代码:
package com.victor.thread;
/**
* 比较下面两个方法,哪个效率更快
* 抽出时间来测
* @author Victor
*/
public class TraditionalSynchronizedThread_009 {
int count =0;
private synchronized void m1(){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
try {
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
}
private void m2() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (this) {
count++;
}
try {
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
}
}