什么是并发错误
多个线程共享操作同一份数据的时候 线程体当中连续的多行操作未必能够连续执行 很可能操作只完成了一部分 时间片突然耗尽 而此时另一个线程刚好抢到时间片 直接访问了操作不完整的数据
根本原因:多个线程共享访问同一份数据
直接原因:线程体当中连续多行语句 未必能够连续执行
导火线:时间片突然耗尽
//并发修改异常(CME)和 并发错误是什么关系?
并发修改异常时Sun公司官方程序员为了避免程序运行时出现并发错误而主动抛出的运行时异常
并发错误 则编译不报错 运行没异常 只是数据全是逻辑错误的
如何解决并发错误
1.互斥锁
*:什么是临界资源
多个线程共享的那个对象 称为 临界资源
*:什么是锁标记
Java当中每个对象都有一份的标识
锁标记 = 互斥锁 = 互斥锁标记 = 锁旗标 = 监视器 = Moniter
synchronized 修饰符 形容词(同步的)
1.修饰代码块
synchronized(临界资源){
需要连续执行的操作1;
需要连续执行的操作2;
...
}
2.修饰整个方法
public synchronizied void add(Object obj){
需要连续执行的操作1;
需要连续执行的操作2;
...
}
这等价于 从方法的第一行到最后一行统统加锁 对调用方法的当前对象枷锁
等价于
public void add(Object obj){
synchronized(this){
}
}
//已知:Vector类的 add() 和 remove() 都是synchronized修饰的
现在有一个Vector对象 名叫v
有两个线程对象 名叫 t1 和 t2
当t1线程调用v对象 add() 方法已经开始执行了
但是还没执行结束呢 此时时间片突然耗尽
而t2线程抢到了时间片
问:
t2能不能调用v对象的add()? false
t2能不能调用v对象的remove()? false
已知:Hashtable的put() 和 remove() 都是synchronized修饰的
现在有两个Hashtable对象 名叫 h1 和 h2
有两个线程对象 名叫 t1 和 t2
当t1线程调用h1的put() 方法已经开始执行了
但是还没执行结束呢 此时时间片突然耗尽
而t2线程抢到了时间片
问:
t2线程能不能调用h1对象的put()? false
t2线程能不能调用h1对象的remove()? false
t2线程能不能调用h2对象的put()? true
t2线程能不能调用h2对象的remove()? true
综上所述 哪怕synchronized修饰符加在方法上
也不是对方法进行加锁 而是对调用方法的对象进行加锁
*:Java当中只有每个对象才有锁标记 一定是对对象进行加锁
*:Vector HashTable StringBuffer 之所以线程安全 是因为底层大量方法使用了synchronized修饰
*:单例模式的懒汉式 需要synchronized
*:synchronized隔代丢失... 父类的synchronized方法 能够被子类继承得到 但是方法的synchronized就没了
2.可重入锁(ReentrantLock)
java.util.concurrent.locks.ReentrantLock
java包的工具包的并发包的锁包的可重入锁
since JDK5.0 by Doug Lea
lock() unlock()
加锁 释放锁
ReentrantLock构造方法可以传参指定公平锁或非公平锁
new ReentrantLock(true);
import java.util.concurrent.locks.*;
public class TestConcurrentErrorWithLock{
public static void main(String[] args){
Lock lock = new ReentrantLock();
Student stu = new Student("张曼玉","女士");
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;
}
}
DeadLock 死锁
互斥锁标记食用过多或者使用不当 就会出现多个线程相互持有对方想要申请的资源 不是放的情况下 又去申请对方已经持有的资源 从而双双进入对方已经持有 资源的锁池当中 产生永久的阻塞
public class TestDeadLock{
public static void main(String[] args){
QCRoad r = new QCRoad();
QCRoad.Benz s900 = r.new Benz();
QCRoad.Bmw x9 = r.new Bmw();
s900.start();
x9.start();
}
}
class QCRoad{
Object east = new Object();//路东资源
Object west = new Object();
class Benz extends Thread{
@Override
public void run(){
System.out.println("钟总驾驶奔驰驶出家门去上课");
synchronized(east){
System.out.println("钟总驾驶奔驰占领了泉城路东侧");
try{Thread.sleep(10);}catch(Exception e){e.printStackTrace();}
//当前线程Benz放弃east对象的锁标记 进入east对象的等待池
try{east.wait();}catch(Exception e){e.printStackTrace();}
synchronized(west){
System.out.println("钟总和他的奔驰又占领了泉城路西侧");
}
}
System.out.println("钟总驾驶奔驰驶过泉城路");
}
}
class Bmw extends Thread{
@Override
public void run(){
System.out.println("李总驾驶宝马驶出家门去上课");
synchronized(west){
System.out.println("李总驾驶宝马占领了泉城路西侧");
try{Thread.sleep(10);}catch(Exception e){e.printStackTrace();}
synchronized(east){
System.out.println("李总和他的宝马又占领了泉城路东侧");
east.notify();
}
}
System.out.println("李总驾驶宝马驶过泉城路");
}
}
}
如何解决死锁
一块空间:对象的等待池
三个方法:wait() / notify() /notifyAll()
wait() : 让当前线程放弃已经持有的锁标记 并且进入调用方法的那个对象的等待池
notify():当前线程从调用方法的那个对象的等待池当中 随机唤醒的一个线程
notifyAll : 唤醒所有阻塞的线程
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("美国人就餐完毕");
}
}
}
*:这三个方法都不是Thread类的方法 而是Object类的方法 因为Java 当中每个对象都有等待池 都需要操作等待池 所以这三个方法被定义在Object类当中
*: 这三个方法都必须在已经持有锁标记的前提下才能使用 也就是说 他们都一定出现在synchronizedz的大括号当中 abc.wait()就一定在synchronized(abc){这里写} 如果没有拿到对象的锁标记就尝试操作对象的等待池 不但会操作失败 还会出发运行时异常 IllegalMonitorStateException
借助wait() / notify() 实现两个线程交替运行
//synchronized 等待池 wait() notify() notifyAll()
ReentranLock Condition await() signal() signalAll()
锁池和等待池 都是Java当中每个对象都有一份的空间
而且都是用于存放线程任务的空间
锁池:存放的是想要拿到对象的锁标记 但是还没成功的线程
等待池:存放的是原本已经拿到对象的锁标记
为了避免跟另外的线程永久阻塞
又主动释放锁标记的线程...
它们的区别主要在如下三个层面
1.进入的时候是否需要释放资源:
锁池不需要
等待池需要先释放资源
2.离开的时候是否需要调用方法
锁池不需要
等待池需要notify()/notifyAll()
3.离开之后去到什么状态
离开锁池返回就绪
离开等待池返回锁池
//Object类的一个对象 有属性 有方法 有互斥锁Monitor 有锁池 有等待队列
public class TestSwitchThread{
public static void main(String[] args){
RightThread rt = new RightThread();
LeftThread lt = new LeftThread(rt);
lt.start();
//
}
}
class X{
static Object obj = new Object(); //X.obj
}
class LeftThread extends Thread{
RightThread rt;
public LeftThread(RightThread rt){
this.rt = rt;
}
@Override
public void run(){
synchronized(X.obj){
rt.start();
for(int i = 0;i<666;i++){
System.out.println("左jio");//1
try{X.obj.wait();}catch(Exception e){e.printStackTrace();}//2
X.obj.notify();//6
}
}
}
}
class RightThread extends Thread{
@Override
public void run(){
synchronized(X.obj){
for(int i = 0;i<666;i++){
System.out.println(" 右jio");//3
X.obj.notify();
try{X.obj.wait();}catch(Exception e){e.printStackTrace();}
}
}
}
}
import java.util.concurrent.locks.*;
public class TestSwitchThreadWithLock{
public static void main(String[] args){
RightThread rt = new RightThread();
LeftThread lt = new LeftThread(rt);
lt.start();
//
}
}
class X{
//static Object obj = new Object(); //X.obj
static Lock lock = new ReentrantLock();
static Condition c = lock.newCondition();//新建一个阻塞条件
}
class LeftThread extends Thread{
RightThread rt;
public LeftThread(RightThread rt){
this.rt = rt;
}
@Override
public void run(){
//synchronized(X.obj){
X.lock.lock();
rt.start();
for(int i = 0;i<666;i++){
System.out.println("左jio");//1
try{X.c.await();}catch(Exception e){e.printStackTrace();}//try{X.obj.wait();}catch(Exception e){e.printStackTrace();}//2
X.c.signal();//X.obj.notify();//6
}
X.lock.unlock();
//}
}
}
class RightThread extends Thread{
@Override
public void run(){
//synchronized(X.obj){
X.lock.lock();
for(int i = 0;i<666;i++){
System.out.println(" 右jio");//3
X.c.signal();//X.obj.notify();
try{X.c.await();}catch(Exception e){e.printStackTrace();}//try{X.obj.wait();}catch(Exception e){e.printStackTrace();}
}
X.lock.unlock();
//}
}
}