Java —— 多线程笔记 二、线程同步

news/2024/5/17 19:34:14 标签: 多线程, 线程同步, Lock, synchronized

一、使用synchronized 关键字

1、同步代码块

//aVar 变量作为同步监视器,任何线程执行此代码块时必须获得对aVar 的锁定,即同时只有一个线程执行此代码块
//此方法逻辑:加锁——修改——释放锁
synchronized(aVar){
//业务
}

2、同步方法

此时以当前对象作为同步机监视器。

public synchronized void myMethod(){
//业务
}

释放同步监视器的锁定(重点)

1、以下情况不会释放同步监视器

(1)、程序执行同步方法或同步代码块时,调用了Thread.sleep()、Thread.yield(),则当前线程不会释放同步锁;

(2)、程序执行同步方法或同步代码块时,其它线程调用了该线程的suspend()方法将该线程挂起,该线程同样不会释放同步锁(当然,应尽量避免使用suspend()与resume()方法控制线程)。

说明:第(2)条应该不太会出现,因为同步方法或同步代码块中一般不会调用其它线程A,且调用的线程A的run或call方法中又调用了当前线程的suspend方法。

2、会释放同步监视器的几种情况

(1)、同步方法或同步块正常执行完;

(2)、同步方法或同步块中执行了break 或return 时(其实同样属于第(1)种);

(3)、同步方法或同步块中出现了未处理的异常(Exception)或错误(Error);

(4)、同步方法或同步块中执行了当前线程的wait()方法。

记忆方法:

不会释放同步监视器的3个方法:sleep、yield、suspend。



二、使用同步锁(Lock

简介:

Lock 同步锁除了提供synchronized 关键字相同的功能外,还扩展了一些其它功能,使同步访问更加灵活。同步锁从java5开始引入,提供了Lock 与 ReadWriteLock 两个接口,并为Lock 接口提供了ReentrantLock 实现类,为ReadWriteLock 接口提供了ReentrantReadWriteLock 实现类。java8 中有加入了StampedLock 类。

1、Lock 接口

接口方法:

//获得锁(即获得执行权)
void lock();
//获得锁,除非当前线程被阻塞
void lockInterruptibly() throws InterruptedException;
//仅仅在锁是空闲时可获得锁(返回结果表示是否已获得)
boolean tryLock();
//在tryLock基础上指定多长时间内一直尝试获取
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
//释放锁
void unlock();
说明:tryLock([...]) 已经尝试获得锁了,所以不能再根据其返回结果调用lock()或unLock()。

实现类:

(1)、ReentrantLock 类。

可重入锁,即再已经锁定的代码块内,可以嵌套锁定或调用此锁锁定的其它方法(道理一样)。示例:

import java.util.concurrent.locks.ReentrantLock;

public class TestLock {

	public static void main(String[] args) {
		
		new MyThread().start();
	}
}

class MyThread extends Thread{
	
	ReentrantLock myLock = new ReentrantLock();
	
	public void run(){
		
		myLock.lock();
		try{
			for(int i=0;i<10;i++){
				printInt(i);
			}
		}finally{
			myLock.unlock();
		}
	}
	
	private void printInt(int i){
		
		myLock.lock();
		System.out.println("被锁住的方法:"+i);
		myLock.unlock();
	}
}

2、ReadWriteLock 接口

接口方法:

//返回用于读的锁
Lock readLock();
//返回用于写的锁
Lock writeLock();
实现类:

ReentrantReadWriteLock 

可重入读写锁,作用和普通锁差不多,是一个工具类,里面包装了两个Lock 实现类(以内部类形式),即ReadLock 与WriteLock 类。此外,该类提供了获取锁定数目的方法getReadHoldCount()、getWriteHoldCount() 等。示例:

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class TestLock {

	public static void main(String[] args) {
		
		new MyThread().start();
	}
}

class MyThread extends Thread{
	
	ReentrantReadWriteLock myLock = new ReentrantReadWriteLock();
	
	public void run(){
		
		myLock.readLock().lock();
		System.out.println(myLock.getReadHoldCount());//打印1
		try{
			myLock.readLock().lock();
			System.out.println(myLock.getReadHoldCount());//打印2
			myLock.readLock().unlock();
			System.out.println(myLock.getReadHoldCount());//打印1
		}finally{
			myLock.readLock().unlock();
		}
	}
}

3、StampedLock

方法:

提供了与ReentrantReadWriteLock  类相似的方法,同样以内部类形式包装了两个Lock 实现类(ReadLock 与WriteLock )可用于获得该对象当前持有锁的数量,只是所持有的锁都是按long 类型数值来标志的,所以,StampedLock 类可用于替换ReentrantLock 类。示例:

class MyThread extends Thread{
	
	StampedLock stampedLock = new StampedLock();
	
	public void run(){
		
		long rl = stampedLock.readLock();//返回一个邮戳用于标志该锁
		System.out.println(rl);
		System.out.println(stampedLock.getReadLockCount());//打印1
		stampedLock.unlock(rl);//通过邮戳释放该锁
		System.out.println("释放后"+stampedLock.getReadLockCount());//打印0
	}
}
说明:锁必须被释放,对于可重入锁,其释放顺序为锁定的相反顺序,即较外层的较后释放。


注:文件读写涉及到专门的文件锁,另一篇单独学习:url



三、死锁

当两个线程互相等待对方释放同步监视器时,即出现了死锁,程序无法继续向下执行,也不会报错。

思路示例:

class A{

    public synchronized void methodOne(){
        //同步方法Aone
    }
    public synchronized void methodTwo(){
        //同步方法Atwo
    }
}
class B{

    public synchronized void methodOne(){
        //同步方法Bone
    }
    public synchronized void methodTwo(){
        //同步方法Btwo
    }
}
死锁出现情形分析:

首先,类A与类B中两个方法都为同步方法,即监视器为类的实例,所以同一时刻,只有一个实例执行同步方法,且该实例执行此方法时,不能再任何地方执行其它方法(包括被其他类调用执行),因为该实例作为监视器被正在执行的同步方法持有。死锁出现的关键在于,若一个同步方法被阻塞,则执行该方法的实例将不能再执行其它同步方法。

具体例子:

public class TestSynchronized {

	public static void main(String[] args) {
		
		A a = new A();
		B b = new B();
		a.setB(b);
		b.setA(a);
		ThreadA threadA = new ThreadA(a);
		ThreadB threadB = new ThreadB(b);
		threadA.start();
		try {
			//保证threadA先执行,加个50ms延时
			Thread.sleep(50);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		threadB.start();
	}
}

class A{
	
	private B b;
	public void setB(B b){
		this.b = b;
	}
	public synchronized void methodOne(){
		
		System.out.println("Aone");
		try {
			System.out.println("A线程睡眠");
			Thread.sleep(200);
			System.out.println("A线程醒来");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		//此时监视器b因为其methodOne方法中的sleep而阻塞,methodTwo无法获得监视器b,将等待
		b.methodTwo();
	}
	public synchronized void methodTwo(){
		
		System.out.println("Atwo");
	}
}
class B{
	
	private A a;
	public void setA(A a){
		this.a = a;
	}
	public synchronized void methodOne(){
		
		System.out.println("Bone");
		try {
			System.out.println("B线程睡眠");
			Thread.sleep(200);
			System.out.println("B线程醒来");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		//此线程醒来,此时监视器a在线程A中正在请求b实例的methodTwo方法,所以下面调用将等待
		a.methodTwo();
	}
	public synchronized void methodTwo(){
		
		System.out.println("Btwo");
	}
}
class ThreadA extends Thread{
	
	private A a;
	public ThreadA(A a){
		this.a = a;
	}
	public void run(){
		a.methodOne();
	}
}
class ThreadB extends Thread{
	
	private B b;
	public ThreadB(B b){
		this.b = b;
	}
	public void run(){
		b.methodOne();
	}
}

例子说明:

例子中,A的实例a对应线程A,B的实例b对应线程B,死锁范围是指任意两个线程间,而非同一个线程类的两个实例简。例子中死锁出现过程:线程A执行a的methodOne同步方法——>线程A沉睡200ms,监视器a被methodOne持有——>线程A沉睡时线程B执行b的methodOne同步方法——>线程B沉睡200ms,监视器b被methodOne持有——>线程A醒来,执行b的methodTwo同步方法,但无法获得监视器b,等待——>线程B醒来,执行a的methodTwo同步方法,但无法获得监视器a,等待。

例子运行结果:

Aone
A线程睡眠
Bone
B线程睡眠
A线程醒来
B线程醒来

要解决此问题,可从两方面入手:

一是将methodTwo 都改为非同步的;二是将去掉methodOne 中的sleep(),让其一步执行完此方法。

方法一结果:

Aone
A线程睡眠
Bone
B线程睡眠
A线程醒来
Btwo
B线程醒来
Atwo

方法二结果:

Aone
Btwo
Bone
Atwo


推荐方法一,因为方法二其实是不正确的,因为程序运行并不是以方法为单位,所以当methodOne 方法体较大时,可能还来不及执行完便失去了cpu资源,让给线程B执行,这时还是会出现死锁。如将:

try {
	System.out.println("B线程睡眠");
	Thread.sleep(200);
	System.out.println("B线程醒来");
} catch (InterruptedException e) {
	e.printStackTrace();
}
改为:

for(int i=0;i<5000;i++){
	System.out.println("B(或A):"+i);
}

则其中一次执行结果为:

Aone
A:0
A:1
A:2

...

A:3052

Bone

B:0

A:3053

B:1

A:3054

...

A:4999

B:2

B:3

...

B:4999


还是出现了死锁(没有打印Atwo 与Btwo )。当循环次数较小时,出现此问题可能性较小。


所以,不同线程的同步方法间互相调用时,务必多注意!!!








http://www.niftyadmin.cn/n/701306.html

相关文章

JS —— 鼠标位置 与 相关属性 及 js undefined 与 null 区别

一直理不清鼠标位置相关属性&#xff0c;在此整理下&#xff0c;彻底理解&#xff01; 一、鼠标位置帮助实例 运行此实例&#xff0c;关于鼠标位置的相关问题将不再是问题&#xff01; <!DOCTYPE html> <html> <head><title>鼠标位置</title>&…

Java —— 多线程笔记 三、线程通信 与 线程组、线程异常

一、线程通信 1、传统线程通信&#xff08;wait()、notify()、notifyAll() 配合 synchronized&#xff09; 基础介绍&#xff1a; wait()、notify()、notifyAll()三个方法属于Object 类&#xff0c;而非Thread 类。这三个方法必须由同步监视器对象调用&#xff0c;其作用是使…

Hibernate JPA —— Unable to find ... with id 0 错误

之前使用Hibernate JPA 的实体映射时并没有出现此错误&#xff0c;今天再次启动之前的项目时&#xff0c;报此错。此错误特殊之处在于&#xff0c;当使用JPA 获取实体时&#xff0c;其参考的实体若不存在&#xff0c;则判断null 为false &#xff0c;查看里面属性可见全部为nul…

综合 —— CORS 方式跨域访问

一、基础概念 同源策略&#xff1a; 为安全考虑&#xff0c;浏览器会拒绝指向非同源网站的请求。同源指协议、域名、端口相同都相同。举例&#xff1a; http://www.a.com&#xff0c;协议为http&#xff0c;域名www.a.com&#xff0c;端口默认80&#xff0c;则下面的同源与非…

JS —— 笔记,$(document).ready() 中ajax 与 $.ajax() 及同步异步优先级问题

jQuery 所有方法&#xff08;包括 $.load()等&#xff09;默认都是异步执行的&#xff0c;所以&#xff0c;它们具有相同的执行顺序优先级。此时谁先执行取决于位置前后顺序。 关键想记录的一点是&#xff0c;若将某个这类AJAX 方法改为同步的&#xff0c;即async: true&#x…

代码片段 —— c3p0 更新数据模板

表结构改变或含义变化时&#xff0c;假如里面存了大量有用数据&#xff0c;而但单纯的用SQL 更新无法实现时&#xff0c;就需要通过写代码来更新。 今天使用Hibernate JPA 时&#xff0c;发现JPA 的createQuery(sql).executeUpdate() 无效&#xff0c;也有许多网友遇到了该问题…

JS —— Uncaught TypeError: Cannot read property 'slice' of undefined 错误

JS Array 对象的slice 方法&#xff1a; arrayObject.slice(start,end) 参数说明&#xff1a; start&#xff1a; 必需。规定从何处开始选取&#xff08;为索引&#xff0c;从0开始&#xff09;。如果是负数&#xff0c;那么它规定从数组尾部开始算起的位置。也就是说&#…

JS —— typeof 判断与直接判断是否为undefined 的区别

几个示例说明一切&#xff1a; 直接判断是否为undefined&#xff1a; var name1"name1"; alert(name1 undefined);//false var name2; alert(name2 undefined);//true alert(name3 undefined);//出错typeof 判断是否未定义&#xff1a; var name1 "name1&q…