java线程锁一
java锁相关一
1.什么是锁,锁的概念
java中锁的作用就是用来解决线程安全问题,避免线程之间共享的数据出现错误,可以说, 锁作为并发共享数据,保证一致性的工具2.如何使用锁
java中提供两种锁的方式,一种是通过synchronized关键字,一种是通过lock类(1)synchronized关键字
synchronized加锁有两种方式,一种是直接修饰要加锁的方法或静态方法,一种是锁代码块
锁代码块:
public class LockTest {
public void testLock1() {
//使用synchronized来锁一个代码块
//这里写this代表锁的是当前对象,如果在使用的时候,使用的是同一个对象
//则该同步代码块同时只能有一个线程来执行
//如果使用的时候,有多个对象,那么该代码块是每个对象都可以进来执行
//简单的理解就是,同一时刻同一个对象,只能有一个线程来执行代码块
synchronized (this) {
//do something
System.out.println("aaa");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//do other something
System.out.println(Thread.currentThread().getName() + "finish");
}
public static void main(String[] args) {
LockTest lt1 = new LockTest();
LockTest lt2 = new LockTest();
new Thread(() -> {
lt1.testLock1();
}).start();
new Thread(() -> {
lt2.testLock1();
}).start();
System.out.println("ok");
}
}
当锁代码块中参数为this,表示对该类的当前对象加锁,这个时候,同一时刻同一个对象,只能有一个线程来执行代码块,不同对象的线程则
互不干扰
private final Object obj = new Object();
//这种写法,锁的范围跟括号里写this是相同的
public void testLock() {
synchronized (obj){
System.out.println("aaa");
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "finish");
}
锁代码块参数可以为创建好的对象,这种写法和上面this参数的写法,锁的范围是一样的,即同一时刻同一个对象,只能有一个线程来执行代码块,不同对象的线程则
互不干扰。使用final定义的对象可以防止对象的地址改变导致锁失效的情况。
synchronized (LockTest3.class){
System.out.println("aaaa");
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
也可以以类名.class作为参数,锁代码块将会对该类的所有对象生效,同一时刻只能由一个该类对象的一个线程来执行该代码块
public class LockTest4 {
public void testLock4(){
//这样写,可以锁住所有使用该字符串作为该对象的代码块,不仅局限于同一个类
synchronized ("lock"){
System.out.println("aaaa");
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "finish");
}
}
public static void main(String[] args) {
LockTest4 lt1 = new LockTest4();
LockTest4 lt2 = new LockTest4();
LockTest0 lt0 = new LockTest0();
new Thread(()->{
lt1.testLock4();
}).start();
new Thread(()->{
lt2.testLock4();
}).start();
new Thread(()->{
lt0.testLock();
}).start();
System.out.println("ok");
}
}
class LockTest0{
public void testLock(){
synchronized ("lock"){
System.out.println("aaaa");
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "finish");
}
}
}
锁代码块也可以以变量或常量作为参数,比如以string对象作为参数,由于string存在于字符串常量池,有可以复用的特性,"lock"字符串指向的地址是相同的,
所以以"lock"为参数的代码块,即使别的类中的参数相同的锁代码块也只能同一时刻由一个线程访问,所有使用该字符串作为该对象的代码块都会被锁住
修饰方法、静态方法:
修饰普通成员方法,
public synchronized void testLock(){
System.out.println("aaaa");
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "finish");
}
直接加在方法的前面,即可以锁住该方法的代码,同一时刻只能有一个线程执行
修饰静态方法,
public class LockTest6 {
//修饰静态方法,会锁住整个类,该类的所有对象都会被锁住
//同一时刻,只有一个线程可以访问
public static synchronized void testLock(){
System.out.println("aaaa");
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "finish");
}
}
直接加在静态方法前面,可以锁住该类的所有对象,同一时刻只有一个线程可以访问,修饰静态变量同理
推荐使用代码块的形式来加锁,因为直接给方法加锁的话,会直接锁住整个方法,锁的粒度比较大,会导致程序的效率降低,方法被加锁之后,整个方法再也无法被多线程执行,
使用多了会导致整个程序变得很慢。
使用代码块的形式,锁的范围可以控制得更小,不会导致多余的代码被上锁,程序运行效率更高。
(2)使用Lock类
public class LockTest7 {
private Lock lock = new ReentrantLock(true);
public void testLock(){
//调用lock方法加锁
lock.lock();
System.out.println("随便做什么事");
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//如果不手动释放锁,其它线程永远进不来
lock.unlock();
}
}
public static void main(String[] args) {
LockTest7 lt1 = new LockTest7();
LockTest7 lt2 = new LockTest7();
new Thread(()->{
lt1.testLock();
}).start();
new Thread(()->{
lt1.testLock();
}).start();
System.out.println("ok");
}
}
通过创建Lock类的对象,调用lock方法可以对方法进行加锁,与synchronized 不同的是,lock方法加锁之后,必须手动进行释放,否则该方法将会被永远锁住,
别的线程将会永远无法访问这个方法