synchronized 定义
在并发编程中存在线程安全问题,主要原因有:1.存在共享数据 2.多线程共同操作共享数据。关键字synchronized可以保证在同一时刻,只有一个线程可以执行某个方法或某个代码块,同时synchronized可以保证一个线程的变化可见(可见性),即可以代替volatile。
synchronzied 是 悲观锁
synchronized 关键字 对某个对象加锁
public void m() {
synchronized(o) {
count--;
System.out.println(Thread.currentThread().getName() + "count = " + count);
}
}
synchronized 锁定的是对象
public class T {
private int count = 0;
public void m() {
/**
* synchronized 锁定的是对象
* 如果三个人去上厕所,但是只有一个厕所坑。 厕所门口有一把锁。 第一个人进去之后,其他两个人需要等待
* 第一个人把锁打开才能够进去。
*/
synchronized (this) { // 任何线程要执行下面的代码,必须要拿到this的锁
count++;
System.out.println(Thread.currentThread().getName() + "count:" + count);
}
}
}
synchronized 关键字对某个对象加锁。
private int count = 0;
public synchronized void m() { // 等同于在方法代码执行开始时候,就执行synchronized(this) 这里直接锁定的就是this对象
count ++;
System.out.println(Thread.currentThread().getName()+ "count:" + count);
}
synchronized 关键字在静态方法上使用
public class T {
private static int i = 10;
public synchronized static void m() { // 这里等同于synchronized(T.class)
i--;
System.out.println(Thread.currentThread().getName() + "count-:" + i);
}
public static void n() {
synchronized(T.class) { // 考虑一下 写this 是否可以 : 不可以 因为静态方法,是不需要创建对象的就可以使用的。!
i--;
System.out.println(Thread.currentThread().getName() + "count-:" + i);
}
}
}
synchronized 是原子操作 不可以再分的
public class T implements Runnable{
private int count = 10;
// synchronized 是原子操作 不可以再分的
@Override
public /*synchronized*/ void run() {
count --;
System.out.println(Thread.currentThread().getName() + "count --: " + count);
}
public static void main(String[] args) {
T t = new T();
for (int i = 0; i < 10; i++) {
new Thread(t).start();
}
}
}
非同步方法和同步方法是否可以一起调用
public class T {
public synchronized void m1(){
System.out.println(Thread.currentThread().getName() + "m1.start");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "m1.end");
}
public void m2() {
System.out.println(Thread.currentThread().getName() + "m2.start");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "m12.end");
}
public static void main(String[] args) {
// 结论是可以一起调用
T t = new T();
new Thread(()->t.m1()).start();
new Thread(()->t.m2()).start();
}
}
解决脏读问题
/**
* 对业务写方法加锁
* 对业务读方法不加锁
* 容易产生脏读问题
* @author gssznb
*
*/
public class Account {
String name;
double balance;
/**
* 设置姓名和金钱余额的时候。 给金钱赋值在线程沉睡十秒时候赋值。
* 使用多线程在沉睡过程中模拟人为操作对这个金钱余额的读取。
* 在这个时候就会产生脏读现象。
* @param name
* @param balance
*/
public synchronized void set(String name,double balance) {
this.name = name;
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.balance = balance;
}
public double get(String name) {
return this.balance;
}
public static void main(String[] args) {
Account a = new Account();
new Thread(()->a.set("gss", 100.0)).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(a.get("gss"));
try {
TimeUnit.SECONDS.sleep(11);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(a.get("gss"));
}
}
synchronized是可以重入的
一个同步方法可以调用另外一个同步方法,一个线程已经拥有其对象的锁。再次申请的时候仍然会得到该对象的锁。 也就是说synchronized是可以重入的
public class T {
synchronized void m1() {
System.out.println("m1.start");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("m1.end");
m2();
}
synchronized void m2() {
System.out.println("m2.start");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("m2.end");
}
public static void main(String[] args) {
T t = new T();
new Thread(()->t.m1()).start();
}
}
一个同步方法可以调用另一个同步方法,一个线程已经拥有某个对象的锁。再次申请的时候仍然会得到该对象的锁,也就是说synchronized获得的锁是可重入的。这里是继承中可能发生的事情。子类调用父类的同步方法。 子类的重入锁调用父类的重入锁
public class T {
synchronized void m() {
System.out.println("m.start");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("m.end");
}
public static void main(String[] args) {
TT tt = new TT();
tt.m2();
}
}
class TT extends T{
synchronized void m2() {
System.out.println("m2.start");
super.m();
System.out.println("m2.end");
}
}
程序执行过程中。synchronized同步方法如果出现异常,默认情况下就会被释放。 所以,在并发处理的过程中。有异常要多加小心,不然可能出现不一致的情况。 比如在一个Web App处理过程中。多个servlet线程处理同一个资源。如果异常如理不合适。 在第一个线程抛出异常,其他线程就会进入同步代码区。有可能会访问到异常产生时的数据。 因此要非常小心处理同步业务逻辑中的异常
public class T {
private int count = 0;
synchronized void m() {
System.out.println(Thread.currentThread().getName() + "start");
while (true) {
System.out.println(Thread.currentThread().getName()+ "count = " + count);
count++;
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 5) {
int c = 1/0; // 这里抛出异常。如果想要锁释放就不要进行try()catch{}.然后让循环继续。
}
}
}
public static void main(String[] args) {
T t = new T();
Runnable r = new Runnable() {
@Override
public void run() {
t.m();
}
};
new Thread(r,"t1").start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
new Thread(r,"t2").start();
}
}
锁定某对象o , 如果 o 的属性发生变化。不影响锁的使用,但是如果 o 变成另外一个对象。则锁定的对象发生改变。 应该避免将锁定的对象引用变成另外的对象。
public class T {
Object o = new Object();
void m() {
synchronized (o) {
while (true) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}
}
public static void main(String[] args) {
T t = new T();
// 启动第一个线程
new Thread(t::m,"t1").start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 修改 o 对象的引用 你就会发现,第二个线程在第一个线程没有释放锁的情况下运行了
// 证明了synchronized锁定的是堆内存中new出的对象,锁的内存是记录在堆内存中的
t.o = new Object();
// 创建第二个线程
new Thread(t::m,"t2").start();
}
}
不要以字符串常量作为锁定对象
在下面的例子中,m1和m2其实锁定的是同一个对象。
== 这种情况还会发生比较诡异的现象。 比如你用到了一个类库,该类库中代码锁定了字符串hello==
但是你读不到源码,所以你在自己的代码中也锁定了“hello”,这时候就有可能发生非常诡异的死锁阻塞。
== 因为你得程序和你用到的类库不经意间使用到了同一把锁。==
public class T {
String s1 = "hello";
String s2 = "hello";
void m1() {
synchronized (s1) {
}
}
void m2() {
synchronized (s2) {
}
}
}