java学习笔记 多线程(二)线程同步、死锁、交互
线程同步问题:
假设有一个数x=1000,有两个线程,一个给x加一操作、另一个给x减一操作,
那么在执行n次之后,讲道理x应该还是等于1000的,但是因为线程同步的问题,最后会出现x!=1000的情况。
线程同步问题产生的原因:
1. 假设增加线程先进入,得到的x是1000
2. 进行增加运算
3. 正在做增加运算的时候,还没有来得及修改hp的值,减少线程来了
4. 减少线程得到的hp的值也是1000
5. 减少线程进行减少运算
6. 增加线程运算结束,得到值1001,并把这个值赋予x
7. 减少线程也运算结束,得到值999,并把这个值赋予x
这样x的值最后就是999
这种情况的x的值999就是一个错误的值,在业务上称之为脏数据
同步问题解决的思路:
假如先进行增加操作,增加线程获得x的值并进行增加操作,在完成整个增加操作之前,如果有
减少线程试图对x进行操作,但是不被允许那么增加操作完成之后,减少操作才能访问x的值,这
样就能避免上面的脏数据问题。
synchronized同步对象概念:
只要被我synchronized修饰的对象someObject,那有其他线程试图来占有someObject的时候,就会
等待,等我释放占有之后,其他线程才能有资格操作someObject。
someObject又叫做同步对象,任何对象都能作为同步对象,为了实现同步操作,必须使用同一个对象,
所以这里的someObject是一个(Object)类型的,啥都能使用它。
final Object someObject = new Object();
释放同步对象的方式:synchronized代码块运行结束,或者有异常抛出。
Thread t1 = new Thread(){
public void run(){
try {
System.out.println( now()+" t1 线程已经运行");
System.out.println( now()+this.getName()+ " 试图占有对象:someObject");
synchronized (someObject) {
System.out.println( now()+this.getName()+ " 占有对象:someObject");
Thread.sleep(5000);
System.out.println( now()+this.getName()+ " 释放对象:someObject");
}
System.out.println(now()+" t1 线程结束");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
如果所有线程都访问的同一个对象的话,就可以用该对象来代替someObject。因为所有对象都可以作为同步对象。
也可以在方法前直接加上synchronized,那么在外部访问此方法的时候,就不需要格外使用synchronized了,
同样也达到了同步的效果。
public void hurt(){
//使用this作为同步对象
synchronized (this) {
hp=hp-1;
}
}
线程安全的类:
HashMap和Hashtable都实现了Map接口,都是键值对保存数据的方式:
区别1、HashMap可以存放null,Hashtable不能存放null。
区别2、HashMap不是线程安全的类,Hashtable是线程安全的类。
Stringbuffer是线程安全的,StringBuilder不是线程安全的。
(线程安全的stringbuffer会更慢一点点,因为不需要同步,大家都抢着来,就很快)
ArrayList是非线程安全的,Vector不是线程安全的。
除此之外,Collections工具类里面有把上述非线程安全的类转换为线程安全的,比如以ArrayList为例:
package multiplethread;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class TestThread {
public static void main(String[] args) {
List<Integer> list1 = new ArrayList<>();
List<Integer> list2 = Collections.synchronizedList(list1);
}
}
线程死锁:
死锁是在上面讲的线程占有的基础上的,
假设有a,b,c三个对象,t1,t2,t3三个线程,
当t1占有a时,接着想去占有b
同时当t2占有b时,接着想去占有c
同时当t3占有c时,接着想去占有a
但是呢,他们都是想去占有的状态,而自身占有的并没有解除,那这三线程就等,疯狂的等。
这就是线程死锁。
package Multiplethread;
import charactor.Hero;
public class Deadlock {
/*
* 3个同步对象a, b, c
3个线程 t1,t2,t3
故意设计场景,使这3个线程彼此死锁
*/
public static void main(String[] args) {
final Hero a=new Hero();
final Hero b=new Hero();
final Hero c=new Hero();
a.name="a";
b.name="b";
c.name="c";
Thread t1 = new Thread() {
public void run() {
synchronized (a) {
System.out.println("我是线程t1,我已经占有了a");
try {
Thread.sleep(1000);
}catch(InterruptedException e)
{
e.printStackTrace();
}
System.out.println("我想要占有b");
System.out.println("t1等待中");
synchronized (b) {
System.out.println("占有成功!");
}
}
}
};
t1.start();
Thread t2 = new Thread() {
public void run() {
synchronized (b) {
System.out.println("我是线程t2,我已经占有了b");
try {
Thread.sleep(1000);
}catch(InterruptedException e)
{
e.printStackTrace();
}
System.out.println("我想要占有c");
System.out.println("t2等待中");
synchronized (c) {
System.out.println("占有成功!");
}
}
}
};
t2.start();
Thread t3 = new Thread() {
public void run() {
synchronized (c) {
System.out.println("我是线程t3,我已经占有了c");
try {
Thread.sleep(1000);
}catch(InterruptedException e)
{
e.printStackTrace();
}
System.out.println("我想要占有a");
System.out.println("t3等待中");
synchronized (a) {
System.out.println("占有成功!");
}
}
}
};
t3.start();
}
}
线程交互:
在上述的过程中,线程和线程之间都是自己干自己的,要么一起争活干,要么就等别人做完才能干。
那么线程交互作用就是,让线程之间能实现配合一起工作。
两个方法:
this.wate()让占用this的线程等待,并临时释放占用,别的线程能可以先来处理this。调用wait一定
要在synchronized块里面,否则会出错。
this.notify() 通知那些在等待this的线程可以重新开始工作了,不用等待了。
还有notifyAll() 意思是所有等待的可以开始了。
比如How2j上站长给出的列子:
上一篇: Drools学习笔记(四)---执行控制
下一篇: java多线程(二)多线程同步与死锁