多线程-锁

方法锁,对象锁,类锁

方法锁:

每个类的对象对应一个锁,当对象中的某个方法被synchronized修饰后,调用该方法的时候必须获得该对象的“锁”
该方法一旦执行就会占有该锁,别的线程使用该对象调用这个方法的时候就会被阻塞直到这个方法执行完后释放锁,被阻塞的线程才能获得锁,从而进入执行状态。
这种机制确保了在同一时刻,对于每一个对象的实例,其所有声明为synchronized方法中最多只有一个处于可执行状态。从而避免了类成员变量的访问冲突。

1,当使用同一个对象调用加了synchronized修饰的方法,如下所示。

public class Demo4 {
	public static void main(String[] args) {
		 Demo4 d = new Demo4();

		 new Thread(){
			 public void run() {
				d.method1();
			 };
		 }.start();
		 
		 
		 new Thread(){
			 public void run() {
				 d.method2();
			 };
		 }.start();
	}
	
	public synchronized void method1(){	
			System.out.println("method1------开始");
			try {
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("method1------结束");
	}
	
	public synchronized void method2(){	
		System.out.println("method2------开始");
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("method2------结束");
   }
}

结果如下:

method1------开始
method1------结束
method2------开始
method2------结束

由此可得,当使用第一个线程调用method1的时候,method1就会获得当前对象的锁,然后进入方法,在这个时候线程二执行method2方法的时候是进不去的,因为当前对象d的锁已经被method1方法获取到。所以线程二执行method2方法的时候获取不到锁,只能阻塞,然后等待method1方法释放锁后method2才得以执行。

当使用不同的对象调用加了synchronized修饰的方法,如下所示。

ublic class Demo4 {
	public static void main(String[] args) {

		Demo4 d1 = new Demo4();
		Demo4 d2 = new Demo4();
		 new Thread(){
			 public void run() {
				
				d1.method1();
			 };
		 }.start();
		 
		 
		 new Thread(){
			 public void run() {
				 
				 d2.method2();
			 };
		 }.start();
	}
	
	public synchronized void method1(){	
			System.out.println("method1------开始");
			try {
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("method1------结束");
	}
	
	public synchronized void method2(){	
		System.out.println("method2------开始");
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("method2------结束");
   }
}

结果入下:

method1------开始
method2------开始
method2------结束
method1------结束
有结果可以得出:使用不同的对象调用这两个方法,可以看到这两个方法是同时执行的。因为调用方法的对象不同,所以两个方法获取到的锁也不同,结果就是这两个方法分别获取了这两个对象的锁。然后两个方法会同时执行。

对象锁:

当一个对象中有synchronized方法或者sychronized代码块的时候。调用此对象方法的时就必须获取对象锁,如果此对象的锁已被别的线程获取,那么就必须等待别的线程释放锁后才可以 执行该方法(方法锁也是对象锁)。

方法锁和对象锁都差不多,方法锁针对的是一个方法。对象锁则是一个代码块,针对的是一部分代码。

对象锁和方法锁的两种形式。

1,方法锁:

public synchronized void method1(){	
			System.out.println("method1------开始");
			try {
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("method1------结束");
	
	}
2,代码块:

public  void method2(){	
		synchronized(this){
			System.out.println("method2------开始");
			try {
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("method2------结束");
		}
   }

类锁:

synchronized修饰的代码块或者synchronized修饰的静态方法。

由于一个类不论被实例化多少次,这个类中的静态方法和变量只会被加载和初始化一份,一旦某个静态方法被修饰为synchronized,此类的所有实例化对象共用一把锁,称之为类锁。

类锁只是一个概念上的东西,并不是真实存在的,它只是用来帮助我们理解锁定实例方法和静态方法的区别的

如下两种形式:

静态synchronizedpublic static synchronized void method1(){	
			System.out.println("method1------开始");
			try {
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("method1------结束");
	
	}
代码块类锁:
public  void method2(){	
		synchronized(Demo4.this){
			System.out.println("method2------开始");
			try {
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("method2------结束");
		}
   }

读写锁

ReadWriteLock读写锁
ReadWriteLock也是一个接口,在它里面只定义了两个方法:

public interface ReadWriteLock(){

Lock readLock();

Lock writeLock();

}

一个用来获取读锁,一个用户来获取写锁,也就是说将文件的读写操作分开,分成2个锁来分配线程,从而使得多个线程可以同时进行读操作。下面的ReentranReadWriteLock实现了ReadWriteLock接口。

如果一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写锁的线程会一直等待释放读锁,而其他申请线程如果要申请读锁则可以正常执行。

如果一个线程已经占用了写锁,则此时其他线程如果申请写锁或读锁,则申请的线程会一直等待释放写锁。

ReadWriteLock读写锁和synchronized区别:

使用读写锁,可以实现读写分离锁定,读操作并发进行,写操作锁定单个进程。而使用synchronized则要读写都完成后才能释放锁。

public class MyReentranReadWriteLock {
    private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    public static void main(String[] args) {
        final MyReentranReadWriteLock test = new MyReentranReadWriteLock();
        new Thread(){
            public void run(){
                test.get(Thread.currentThread());
                test.write(Thread.currentThread());
            }
        }.start();
        new Thread(){
            public void run(){
                test.get(Thread.currentThread());
                test.write(Thread.currentThread());
            }
        }.start();
    }
    //读操作,用读锁来锁定
    public void get(Thread thread){
        rwl.readLock().lock();
        try {
            long start = System.currentTimeMillis();
            while (System.currentTimeMillis() -start <= 1){
                System.out.println(thread.getName()+"正在进行读操作");
            }
            System.out.println(thread.getName()+"读操作完毕");
        }finally {
            rwl.readLock().unlock();
        }
    }
    //写操作用写锁来锁定
    public void write(Thread thread){
        rwl.writeLock().lock();
        try {
            long start = System.currentTimeMillis();
            while (System.currentTimeMillis() - start <=1){
                System.out.println(thread.getName()+"正在进行写操作");
            }
            System.out.println(thread.getName()+"写操作完毕");
        }finally {
            rwl.writeLock().unlock();
        }
    }
}

synchronized 线程同步锁

synchronized 线程同步锁
synchronized是java中的一个关键字,也就是说是java语言内置的特性。

如果一个代码块被synchronized 修饰了,当一个线程获取了对应的锁(同一把锁,synchronized中的参数相同就是同一把锁),并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况:

1.获取锁的线程执行完了该代码块,然后线程释放对锁的占有。
2.线程执行发生异常,此时JVM会让线程自动释放锁。


final MySynchronized mySynchronized = new MySynchronized();
 
final MySynchronized mySynchronized2 = new MySynchronized();
 
new Thread(){
 
public void run() {
 
synchronized (mySynchronized) { //可以传任意对象,相同的就是同一把锁
 
System.out.println(this.getName() + "---start---");
 
try {
 
Thread.sleep(5000);
 
} catch (InterruptedException e) {
 
e.printStackTrace();
 
}
 
System.out.println(this.getName() + "---end---");
 
}
 
}
 
}.start();

Lock锁

Lock和 synchronized的区别:

1)、Lock不是java语言内置的, synchronized是java语言的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问。

2)、Lock和 synchronized一个非常大的不同就是, synchronized不需要用户去手动释放锁,当 synchronized方法或者 synchronized代码块执行完毕后或者在发生异常时候,系统会自动让线程释放对锁的占用;而lock必须要用户去手动释放锁,就有可能导致出现死锁现象,因此使用Lock时需要在finally块中释放锁。(使用Lock一定要在try{}catch{}块中进行,在finally去释放锁unlock,否则在发生异常的时候,会造成死锁)。

3)、Lock可以让等待的线程相应中断,而synchronized去不行,使用synchronized时,等待的线程会一直等待下去,不能都响应中断;

4)、Lock可以知道有没有成功获取锁,而synchronized却无法办到。

5)、Lock可以提高多个线程进行读操作的效率。

在性能上来说,如果竞争资源不激烈,两者的性能是差不多,而当竞争资源非常激烈时(即有大量的线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。

Lock是java.util.concurrent.locks包下的类

Lock是一个接口

public interface Lock{

void lock(); //获取锁

void lockInterruptibly throws interruptedException; //获取可中断的锁(这里是指等待的线程可以不在等待)

boolean tryLock(); //试着获取锁,返回true或false

boolean tryLock(long time,TimeUnit unit) throws InterruptedException; //如果没有拿到锁,会等一定时间

void unlock(); //释放锁

}

悲观锁和乐观锁

悲观锁和乐观锁
悲观锁和乐观锁是一种广义的概念,体现的是看待线程同步的不同的角度

悲观锁认为自己在使用数据的时候,一定有别的线程来修改数据,在获取数据的时候会先加锁,确保数据不会被别的线程修改。

锁实现:关键字synchronized、接口Lock的实现类

使用的场景:写操作较多,先加锁可以保证写操作是数据正确

乐观锁认为自己在使用数据的时候不会有其他的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据
锁实现:CAS算法
使用场景:读操作较多,不加锁的特点能够使其读操作的性能大幅提升

死锁

死锁: 可以认为是两个线程或进程在请求对方占有的资源。

以下四种情况会产生死锁:

1,相互排斥。一个线程或进程永远占有共享资源,比如,独占该资源。
  2,循环等待。例如,进程A在等待进程B,进程B在等待进程C,而进程C又在等待进程A。
  3,部分分配。资源被部分分配,例如,进程A和B都需要访问一个文件,同时需要用到打印机,进程A得到了这个文件资源,进程B得到了打印机资源,但两个进程都不能获得全部的资源了。
  4,缺少优先权。一个进程获得了该资源但是一直不释放该资源,即使该进程处于阻塞状态。

按照前面的分析,情况1和情况4是线程或进程无休止的等待另外的几个线程或进程所占据的资源。

针对上面可能出现死锁的几种情况,可以给出预防措施,比如控制线程或进程如果判断不能够获取到所需的全部的资源,那么就释放已经占有的资源等等。

什么是活锁

活锁和死锁很像似。 只是活锁的状态可以发生改变。不过虽然状态可以改变,却没有实质的进展。

比如两个人在一个很宅的胡同里。 一次只能并排过两个人。 两人比较礼貌,都要给对方让路。 结果一起要么让到左边,要么让到右边,结果仍然是谁也过不去。 类似于原地踏步或者震荡状态。

活锁一般是由于对死锁的不正确处理引起的。由于处于死锁中的多个线程同时采取了行动。 而避免的方法也是只让一个线程释放资源。

什么是饿死

饿死(starvation) 是一个线程长时间得不到需要的资源而不能执行的现象。 有人饿死并不代表着出现了死锁。

很有可能系统还能很好的进行。

所以,没有出现死锁并不能就认为系统是完好的。还要保证没有出现饿死的现象。

避免饿死就应该是采用队列的方式,保证每个人都有机会获得请求的资源。 当然实现方式可以很多个变化,比如优先级,时间片,等,都是“队列”的特殊形式

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值