- 什么是并发错误?
- 根本原因:多个线程共享操作同一份数据
- 直接原因:线程当中连续的多行语句未必能够连续执行,很可能操作只完成了一部分,时间片突然耗尽,而此时另一个线程抢到了时间片,直接访问了操作不完整的数据...
- 导火索:时间片突然耗尽
- 多个线程共享操作同一份数据,线程体当中连续的多行语句,未必能够连续执行,很可能操作只完成了一部分,时间片耗尽,而另外一个线程抢到时间片之后,直接访问了操作不完整的错误数据
- 并发错误和并发修改异常哪个更严重,它们什么关系?并发错误更严重,它编译不报错,运行没异常, 数据全是错误的。而并发修改异常是官方程序员为了避免程序出现并发错误,而主动校验主动抛出的一个运行时异常,它的目的是防止程序出现并发错误
- 如何解决并发错误?可以使用synchronized结合对象的互斥锁进行加锁,也可以使用ReentrantLock(它是面向对象思想实现的加锁方式)可重入锁进行加锁
- synchronized能够修饰什么?互斥锁=互斥锁标记=锁标记=锁旗标=监视器=Monitor
/**
1.修饰代码块
synchronized(临界资源){
连续执行的操作1;
连续执行的操作2;
....
}
2.修饰整个方法
public synchronized void add(Object obj){
连续执行的操作1;
连续执行的操作2;
....
}
*:Vector、Hashtable、StringBuffer 它们之所以线程安全,是因为底层大量的方法都使用了 synchronized修饰
*:单例模式的懒汉式需要synchronized修饰那个getter方法
*:synchronized有一个所有修饰符都不具备的特性,它隔代丢失
*:即便synchronized修饰符加在方法上 也依然是对 对象 进行加锁
Java当中只有每个对象才有锁标记 只能对 对象 进行加锁!
*/
- synchronized的特性:隔代丢失 -- 父类的synchronized方法能够被继承,但是synchronized修饰符就消失了
- java.util.concurrent.locks.ReentrantLock=java包的工具包的并发包的锁包的可重入锁,
- 他提供了更好的可扩展性!!!
- since jdk5.0;
- lock() - 加锁;unlock() - 释放锁;
- 它可还可以指定公平锁或非公平锁;new ReentrantLock(true);
- 什么是公平锁、非公平锁?如何创建公平锁?
- 公平:按照对锁资源申请的先后顺序,依次获得资源;new ReentrantLock(true)
- 非公平:一但锁标记可用,就开始抢; new ReentrantLock() ;默认是false
- CountDownLatch是什么?它的核心方法叫什么?有什么作用?
- CountDownLatch = 倒计时门栓
- countDown():打开一个门闩
- await():主动制造阻塞,等待门闩都被打开
- 什么是死锁? 互斥锁标记使用过多,或者使用不当,就会造成多个线程 互相持有对方想要申请的资源,不释放的情况下,又去申请对方已经持有的资源,从而双双进入对方已经持有资源的锁池当中,产生永久的阻塞
- 如何解决死锁:一块空间:等待池 + 三个方法:wait()、notify()、notifyAll()
- wait():让当前线程放弃已经持有的锁标记,并且进入调用方法的那个对象的等待池当中。需要异常处理:try catch
- notify():从调用方法的那个对象的等待池当中,随机唤醒一个线程
- notifyAll():从调用方法的那个对象的等待池当中,唤醒所有阻塞的线程
- 使用上面三个方法的时候需要注意:它们必须出现在synchronized的作用范围内,否则不但操作失败,还会触发运行时异常,IllegalMonitorStateException
- *:这三个方法都是Object类的方法,而不是线程类的方法。因为Java当中每个对象都有等待池,每个对象都可能要操作等待池,所以这三个方法被定义在Object类当中
- *:这三个方法都必须在已经持有锁标记的前提下才能使用,否则不但操作失败还会触发IllegalMonitorStateException
- IllegalMonitorStateException是什么异常?怎么触发的?非法锁标记异常;在没有拿到对象锁标记的情况下,直接使用了操作等待池的方法。wait()、notify()没有出现在synchronized的作用范围当中
- 锁池、等待池的概念和区别
- 都是java当中每个对象都有一份的空间,都是存放线程任务的
- 锁池:存放的是想要获得对象的锁标记,但是还没有成功的线程(鱼塘、备胎池)
- 等待池:存放的是原本已经获得对象的锁标记,但是怕跟其他线程永久阻塞,而是主动释放掉资源的线程
- 区别:
- 进入的时候是否需要释放资源:锁池不需要,等待池需要
- 离开的时候是否需要调用方法:锁池不需要,等待池需要notify()或notifyAll()
- 离开之后去往什么状态:离开锁池,返回就绪;离开等待池,直奔锁池
- 线程交替 左右两部分
1、张师傅开车工作 | |
2、张师傅休息wait | |
| 3、王师傅开车工作 |
| 4、王师傅 叫醒 张师傅 notify |
| 5、王师傅休息 wait |
6、张师傅 叫醒 王师傅 notify | |
- synchronized 等待池 wait()、notify()、notify()
- ReentrantLock Condition await()、signal()、signalAll()
- Java当中每个对象都有一份的 都是什么?属性、方法、锁、锁池、等待池
- 补充:强化理解synchronized
- 已知Vector类的add()和remove()都是synchronized修饰的,我们现在有一个Vector对象,名叫v(wc的天字一号房),有两个线程对象,名叫t1和t2。当t1线程调用v对象的add()方法已经开始执行了,但是还没有执行结束呢,此时时间片突然耗尽了,而t2线程抢到了时间片。问:t2能不能调用v对象的add()?t2能不能调用v对象的remove()?
- 答 false;false。因为add()方法被synchronized修饰,t1调用add()方法相当于t1进入了天字一号房后上锁了,中断后锁上门拿着钥匙走了,t2当然就不能调用add()方法进入天字一号房
- 已知:Vector类的add() 和 remove() 都是synchronized修饰的,我们现在有两个Vector对象 名叫 v1(wc的天字一号房) 和 v2(wc的天字二号房),有两个线程对象 名叫 t1 和 t2。当t1线程调用v1对象的add() 方法已经开始自行了,但是还没执行结束呢 此时时间片突然耗尽,而t2线程抢到了时间片。问:t2线程能不能调用v1对象的add()?t2线程能不能调用v1对象的remove()?t2线程能不能调用v2对象的add()?t2线程能不能调用v2对象的remove()?
- 答 false;false;true;true
- 综上所述,几遍synchronized修饰符加在方法上,也依然是对对象进行加锁。java当中只有每个对象才有锁标记,只能对对象进行加锁!
- 完善懒汉式
class Sun{
private Sun(){}
private static Sun only;
public static synchronized Sun getOnly(){
if(only == null)
only = new Sun();
return only;
}
}
- 总结:Object类的方法
- clone() :创建并返回此对象的副本
- finalize():当垃圾收集确定不再有该对象的引用时,垃圾收集器在对象上调用该对象
- equals():知识一些其他对象是否等于此
- getClass():返回此Object的运行时类
- hashCode():返回对象的哈希码值
- toString():返回对象的字符串表示形式
- wait():导致当线程等待,直到另一个线程调用notify()方法或该对象的notifyAll()方法
- notify():唤醒正在等待对象监视器的单个线程
- notifyAll():唤醒正在等待对象监视器的所有线程
public class TestConcurrentError{
public static void main(String[] args){
Student stu = new Student("张曼玉","女士");
printThread p = new printThread(stu);
changeThread c = new changeThread(stu);
p.start();
c.start();
}
}
class Student{
String name;
String gender;
public Student(String name,String gender){
this.name = name;
this.gender = gender;
}
public String toString(){
return name + ":" + gender;
}
}
class changeThread extends Thread{
Student stu;
public changeThread(Student stu){
this.stu = stu;
}
@Override
public void run(){
boolean isOkay = true;
while(true){
synchronized(stu){//不加锁的话就会出现数据错误,可能为姓名和性别不匹配
if(isOkay){
stu.name = "梁朝伟";
stu.gender = "先生";
}else{
stu.name = "张曼玉";
stu.gender = "女士";
}
isOkay = !isOkay;
}
}
}
}
class printThread extends Thread{
Student stu;
public printThread(Student stu){
this.stu = stu;
}
@Override
public void run(){
while(true){
synchronized(stu){//不加锁的话就会出现数据错误,可能为姓名和性别不匹配
System.out.println(stu);
}
}
}
}
import java.util.concurrent.locks.*;
public class TestConcurrentErrorWithLock{
public static void main(String[] args){
Student stu = new Student("张曼玉","女士");
Lock lock = new ReentrantLock();
PrintThread pt = new PrintThread(stu,lock);
ChangeThread ct = new ChangeThread(stu,lock);
pt.start();
ct.start();
}
}
class ChangeThread extends Thread{
Student stu;
Lock lock;
public ChangeThread(Student stu,Lock lock){
this.stu = stu;
this.lock = lock;
}
@Override
public void run(){
boolean isOkay = true;
while(true){
//synchronized(stu){
lock.lock();
try{
if(isOkay){
stu.name = "梁朝伟";//"梁朝伟","女士"
stu.gender = "先生";//"梁朝伟","先生"
}else{
stu.name = "张曼玉";//"张曼玉","先生"
stu.gender = "女士";//"张曼玉","女士"
}
isOkay = !isOkay;
}finally{
lock.unlock();
}
//}
}
}
}
class PrintThread extends Thread{
Student stu;
Lock lock;
public PrintThread(Student stu,Lock lock){
this.stu = stu;
this.lock = lock;
}
@Override
public void run(){
while(true){
//synchronized(stu){
lock.lock();
try{
System.out.println(stu);
}finally{
lock.unlock();
}
//}
}
}
}
class Student{
String name;
String gender;
public Student(String name,String gender){
this.name = name;
this.gender = gender;
}
@Override
public String toString(){
return name + " : " + gender;
}
}
public class ExecDeadLock{
public static void main(String[] args){
Restaurant kfc = new Restaurant();
Restaurant.Chinese xdd = kfc.new Chinese();
Restaurant.American bd = kfc.new American();
xdd.start();
bd.start();
}
}
class Restaurant{
Object forks = new Object();
Object chops = new Object();
class Chinese extends Thread{
@Override
public void run(){
System.out.println("中国人走进餐厅");
synchronized(forks){
System.out.println("中国人拿到刀叉");
synchronized(chops){
System.out.println("中国人又拿到筷子");
chops.notify();
}
}
System.out.println("中国人就餐完毕");
}
}
class American extends Thread{
@Override
public void run(){
System.out.println("美国人走进餐厅");
synchronized(chops){
System.out.println("美国人拿到筷子");
try{chops.wait();}catch(Exception e){e.printStackTrace();}
synchronized(forks){
System.out.println("美国人又拿到刀叉");
}
}
System.out.println("美国人就餐完毕");
}
}
}
import java.util.concurrent.locks.*;
public class TestSwitchThreadWithLock{
public static void main(String[] args){
右脚 r = new 右脚();
左脚 l = new 左脚(r);
l.start();
//r.start();
}
}
class X{
static Object obj = new Object();
static Lock lock = new ReentrantLock();
static Condition c = lock.newCondition();//!!!
}
class 左脚 extends Thread{
右脚 r;
public 左脚(右脚 r){
this.r = r;
}
@Override
public void run(){
X.lock.lock();
//synchronized(X.obj){
r.start();//start()的作用是使线程从新生到就绪,但是此时左脚已经拿到锁标记了,右脚肯定抢不过
for(int x = 0; x < 8888;x++){
//try{Thread.sleep(5);}catch(Exception e){}
System.out.println("左脚");//1.
try{X.c.await();}catch(Exception e){}
//try{X.obj.wait();}catch(Exception e){}//2.
X.c.signal();
//X.obj.notify();//6.
}
//}
X.lock.unlock();
}
}
class 右脚 extends Thread{
@Override
public void run(){
X.lock.lock();
//synchronized(X.obj){
for(int x = 0; x < 8888;x++){
//try{Thread.sleep(5);}catch(Exception e){}
System.out.println(" 右脚");//3.
X.c.signal();
//X.obj.notify();//4.
try{X.c.await();}catch(Exception e){}
//try{X.obj.wait();}catch(Exception e){}//5.
}
//}
X.lock.unlock();
}
}