前一篇讲了线程的两种创建方式以及常用的一些方法
但是上一篇都是单线程,本篇文章学习多线程
**
1.并发错误
**
多个线程共享同一组数据时可能会出现
线程体重连续的多行语句未必能够连续执行,很有可能只进行了一半,时间片就用完了,这时候另一个线程拿走了操作并不完整的“错误”数据
**
2.解决并发错误的方法
**
1)加锁
*修饰代码块
synchronized(临界资源){
需要连续执行的操作1;
需要连续执行的操作2;
}
多个线程共享的那个对象 = 临界资源
锁标记 = 互斥锁标记
*修饰整个方法
public synchronized void add(Object obj){
…;
}
等价于从方法的第一行到方法的最后一行统统加锁
对调用方法的那个对象加锁!
* synchronized特性不会被子类继承得到,想要该特性,需要方法覆盖
2)java.util.concurrent.locks.ReentrantLock JUC
两个方法:lock(); unlock();
public class TestConcurrentError{
public static void main(String[] args){
Student stu = new Student("张朝伟","先生");//对stu的名字和性别进行赋值
PrintThread pt = new PrintThread(stu);//创建打印线程
ChangeThread ct = new ChangeThread(stu);//创建修改线程
pt.start();
ct.start();
}
}
//1st.用于打印显示数据的线程
class PrintThread extends Thread{
Student stu;
public PrintThread(Student stu){
this.stu = stu;
}
@Override
public void run(){
while(true){ // 被pt.start(); 执行之后 如果不加锁 ,资源也许会被ct.start()抢走
//就会出现 梁朝伟女士 张曼玉先生的错误
synchronized(stu){//我们要对一组连续的操作加锁 不要对所有操作加锁
//我们去厕所 只是锁一次上厕所的过程 不要一辈子死在厕所里
System.out.println(stu);
}
}
}
}
class ChangeThread extends Thread{
Student stu;
public ChangeThread(Student stu){
this.stu = stu;
}
@Override
public void run(){ //被ct.start();
boolean isOkay = true;
while(true){
synchronized(stu){
if(isOkay){ //梁朝伟 先生
stu.name = "张曼玉";//张曼玉 先生
stu.gender = "女士";//张曼玉 女士
}else{ //张曼玉 女士
stu.name = "梁朝伟";//梁朝伟 女士
stu.gender = "先生";//梁朝伟 先生
}
isOkay = !isOkay;
}
}
}
}
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;
}
}
**
3.互斥锁标记使用过多 或者使用不当
**
就会造成多个线程 相互持有对方想要申请的资源
不释放的情况下 又去主动申请对方已经持有的资源
从而双双进入对方持有资源的锁池当中 产生永久的阻塞
就是 死锁 DeadLock: 马路堵车例子
如何解决死锁:
一块空间 和 三个方法:
一块空间: 对象的等待池
三个方法:
1) wait() : 让当前线程放弃已经持有的锁标记 并且进入调用方法的那个对象的等待池当中
east.wait(); 勋勋放弃已经持有的east的锁标记 并且进入east对象的等待池当中
2) notify() : 从调用方法的那个对象的等待池当中随机的唤醒一个线程
3) notifyAll() : 从调用方法的那个对象的等待池当中唤醒所有阻塞的线程
*: 这三个方法都是Object类的方法 所以 任何一个对象 都可以使用
因为任何一个对象都有等待池
*: 这三个方法都必须在已经持有锁标记的前提下才能使用 否则 不但是调用失败
还会触发运行时异常~
Object: equals() clone() finalize()
toString() hashCode()
wait() notify() notifyAll()
*: 我们可以使用wait() notify() 使两个线程相互配合 交替执行~
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{sleep(1000);}catch(Exception e){e.printStackTrace();}
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{sleep(1000);}catch(Exception e){e.printStackTrace();}
synchronized(east){
System.out.println("美国人和他的宝马又占领了马路东侧~");
east.notify();//用east唤醒
}
}
System.out.println("美国人顺利的通过了马路");
}
}
}
Java当中的锁池和等待池有什么区别?
锁池和等待池都是Java当中每个对象都有一份的 用来放置线程的空间
1st.进入的时候是否需要释放资源
锁池:不需要释放资源就能直接进入 (所以才会形成死锁)
等待池:必须要先释放资源 才能进入
2nd.离开的时候是否需要调用方法
锁池:不需要 一旦锁标记归还再度可用 锁池就可以解除
等待池:必须要另外的线程主动唤醒 notify() / notifyAll()
3rd.离开之后去到什么状态
离开锁池:前往就绪状态
离开等待池:前往锁池! [等着唤醒我的线程释放锁标记]