线程锁(synchronized与volatile)
简单地对synchronized和volatile两种锁进行总结,以便以后查找方便,希望大家提出宝贵建议,以便日后进一步完善。
1.synchronized的用法:
1)修饰方法的三种用法:
a)public class Test1{
private Object o =new Object();
public void invoke(){
synchronized(o){
//需要执行的代码
}
}
}
b)public class Test2{
public void invoke(){
synchronized(this){
//需要执行的代码
}
}
}
c)public class Test3{
public synchronized void invoke(){
//需要执行的代码
}
}
综述:三种方法效果一样,由此可以发现synchronized锁的不是方法,而是锁的对象。
2)synchronized用static修饰时
public class Test4 {
private intcount= 10;
public synchronized static void invoke(){
//需要执行的代码
}
public static void invoke2(){
synchronized(Test4.class){
//需要执行的代码
}
}
}
注意:两个静态方法效果一样,synchronized修饰静态方法时,锁的时Test类,而不是Test new出的对象。与synchronized修饰的非静态方法相比,静态的是锁的一个类,而非静态的锁的是一个具体的对象。
问题总结:
synchronized修饰静态方法,下面简为静态的,修饰非静态方法简称为非静态的。
情形一:当创建两个非静态方法对象,同时有两个线程去跑这两个对象,如果两个对象公用一个资源就会出现线程安全问题。而静态的就不会出现线程安全问题;这是由于静态的是锁的类,而非静态的锁的是该类创建出来的对象,不同的对象就是不同的锁,所以非静态方法只能锁住自己的对象。而静态的能锁住该类创建的所有对象。代码如下:
package com.example.girls.testThread; public class TestThread implements Runnable { private static int count = 10; @Override public void run() { invokeTest(); } public synchronized static void invokeTest(){ count--; System.out.println(Thread.currentThread().getName() +":count ="+ count); } public static void main(String[] args) { TestThread t = new TestThread(); TestThread t2 = new TestThread(); for (int i = 0; i<5; i++){ new Thread(t,"THREAD = "+i ).start(); new Thread(t2,"THREAD2 = "+i ).start(); } } }
3)同一个类中synchronized的修饰的同步方法和非同步方法,能否同时被调用
set方法被synchronized修饰被调用时,需要获得锁,而get方法则不需要锁;因此,在set被调用时,而get方法不需要获得锁,所以在set调用时,同时也可以调用get方法。这样在set复制的同时,调用get方法,此时有可能set还没有赋值完成,get获得值为0,就会出现脏读的情况,
package com.example.girls.testThread; public class TestThread2 { private int count; private String name; public synchronized void set(int count, String name){ this.name = name; try { /* 加上睡眠时间为了模拟实际中的高并发, 为了证明获取和写入的不同步, 从而说明在一个类中加锁的和不加锁的方法能够同时被调用 */ Thread.sleep(2000); }catch (InterruptedException e){ e.printStackTrace(); } this.count = count; } public int get(String name){ return this.count; } public static void main(String[] args) { TestThread2 t = new TestThread2(); new Thread(()->t.set(100000,"zhangsan")).start(); try { Thread.sleep(1000); }catch (InterruptedException e){ e.printStackTrace(); } //此处为了证明同步方法和非同步方法能同时调用 System.out.println("1秒后:"+t.get("zhangsan")); try { Thread.sleep(3000); }catch (InterruptedException e){ e.printStackTrace(); } System.out.println("3秒后:"+t.get("zhangsan")); } }
4)重入锁
在同一个类中两个同步方法,其中一个方法调用另一个同步方法,两个方法都能执行;调用的同步方法会再去找对象的锁,在原来锁的基础上加一。
子类的synchronized方法可以调用父类的synchronized方法。
package com.example.girls.testThread; import javax.persistence.criteria.CriteriaBuilder; public class TestDoubleLock { public synchronized void invoke(){ System.out.println("invoke执行"); try { Thread.sleep(1000); }catch (InterruptedException e){ e.printStackTrace(); } invoke2(); } public synchronized void invoke2(){ try { Thread.sleep(1000); }catch (InterruptedException e){ e.printStackTrace(); } System.out.println("invoke2执行"); } public static void main(String[] args) { TestDoubleLock t = new TestDoubleLock(); new Thread(()->t.invoke()).start(); } }
4)synchronized的锁抛异常就会释放锁
解决方案:加上try...catch
ReentrantReentrantLock的lock()抛异常时不会释放锁,
因此需要在try...catch..finally中释放锁unLock(),从而避免的锁泄露(由于程序的某些原因锁无法释放,导致其他线程无法获取该锁);
2 volatile的用法
volatile修饰变量,使得在所有线程中该变量可见,修改变量可以同步到正在运行中的线程中。
volatile对变量读操作进行了同步,对写操作没有惊醒控制,因此,在多线程进行写操作时会出现线程安全问题。
package com.example.girls.testThread; import java.util.ArrayList; import java.util.List; public class TestVolatile { private volatile int count = 18; public void getCount(){ for (int i = 0; i<1000;i++){ try { Thread.sleep(1); }catch (InterruptedException e){ e.printStackTrace(); } this.count ++; } } public void setCount(){ try { Thread.sleep(1); }catch (InterruptedException e){ e.printStackTrace(); } this.count ++; } public static void main(String[] args) { TestVolatile t = new TestVolatile(); List<Thread> threads = new ArrayList<>(); for (int i=0; i<2;i++){ threads.add(new Thread(()->t.getCount())); } /** * 创建两个线程对count,同时进行修改 * volatile只保证同步读,即变量的可见性; * 卫队其写的操作进行控制,会出现两个线程同时对count的同一值进行操作, * 造成最后结果出现小于2018 */ threads.forEach((o)->o.start()); threads.forEach((o)->{ try { //等待线程结束后再执行main方法 o.join(); }catch (InterruptedException e){ e.printStackTrace(); } }); System.out.println("count = "+t.count); } }