Java —— 多线程笔记 二、线程同步
来源:互联网 发布:网络教育质量监管系统 编辑:程序博客网 时间:2024/06/05 18:39
一、使用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());//打印1try{myLock.readLock().lock();System.out.println(myLock.getReadHoldCount());//打印2myLock.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());//打印1stampedLock.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,等待。
例子运行结果:
AoneA线程睡眠BoneB线程睡眠A线程醒来B线程醒来
要解决此问题,可从两方面入手:
一是将methodTwo 都改为非同步的;二是将去掉methodOne 中的sleep(),让其一步执行完此方法。
方法一结果:
AoneA线程睡眠BoneB线程睡眠A线程醒来BtwoB线程醒来Atwo
方法二结果:
AoneBtwoBoneAtwo
推荐方法一,因为方法二其实是不正确的,因为程序运行并不是以方法为单位,所以当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 )。当循环次数较小时,出现此问题可能性较小。
所以,不同线程的同步方法间互相调用时,务必多注意!!!
- Java —— 多线程笔记 二、线程同步
- Java线程学习笔记(二)---多线程同步方法
- Java中的多线程(二)之多线程同步
- java多线程:线程同步(二)
- java多线程(二)---线程的同步
- java多线程——线程同步问题
- Java多线程——线程同步
- Java多线程——线程同步(2)
- Java多线程编程— 线程同步问题
- java 多线程学习笔记之 线程同步
- Java多线程——同步(二)
- 多线程系列二——java线程间的互斥与同步
- java多线程(二)之线程安全和线程同步
- Java多线程编程总结笔记——六线程的同步与锁
- java多线程总结笔记4——线程互斥与同步
- java多线程学习笔记(四) ——线程安全及同步锁问题
- 多线程05:《疯狂Java讲义》学习笔记——线程同步
- java多线程-线程同步
- Codeforces 842D Vitya and Strange Lesson
- java调用存储过程
- 爬坑小王子——Android Studio集成漂亮Sweet Alert Dialog
- PAT甲级1001-害死人不偿命的(3n+1)猜想
- Android Studio手动配置Gradle的方法
- Java —— 多线程笔记 二、线程同步
- 如何实现全选与反选
- 通过plsql查询当前连接的数据库的配置情况,如查询oracle的安装地址,和oracle的tns配置
- Python 学习5
- 产品缺点变卖点
- go语言练习 : 编写一个非递归版本的comma函数,使用bytes.Buffer代替字符串链接操作。
- SpringBoot详解(三)-Spring Boot的web开发
- UPCOJ 4333
- 关于“运行在主线程的ContentProvider为什么不会影响主线程”的记录