synchronized 锁住当前对象的理解
public class TT implementsRunnable {int b = 100;public synchronized void m1() throwsException{//Thread.sleep(2000);
b = 1000;
Thread.sleep(5000);
System.out.println("b = " +b);
}public void m2() throwsException {
Thread.sleep(2000);
b= 2000;
}public voidrun() {try{
m1();
}catch(Exception e) {
e.printStackTrace();
}
}public static void main(String[] args) throwsException {
TT tt= newTT();
Thread t= newThread(tt);
t.start();
tt.m2();
}
}
m1()方法和m2()方法都访问了成员变量b,m1有加锁(对调用m1方法的对象加锁) m2没有加锁
打印结果:b=2000 tt.m2()方法并没有等t线程执行结束就访问了m1() t.start()和tt.m2()并没有同步
结论:
类的这个对象,是一个资源
这个资源能不能好好的被访问,就像账户里的钱 能不能保证前后访问一致
你必须把访问这个资源的所有的方法,都要考虑到是否设置成同步的(加锁)
锁住当前对象只是针对加了synchronized 关键字m1()这段方法,另外一个线程绝对不可能执行这段代码,但是有可能执行其他的代码m2()
模拟生产者与消费者
生产者和消费者例子中,蕴含了多线程很多个知识点
启动线程、 synchronized关键字sleep 、 wait 和notify的用法 以及数据结构中的栈
packagejavaee.net.cn.thread;public classProducerConsumer {//这里用是三个生产者线程和一个消费者线程来测试//如果想要把生产者生产的全部消费完 消费者的run方法执行的次数就需要时生产者的三倍 (i<6)
public static voidmain(String[] args) {
SyncStack ss= newSyncStack();
Producer p= newProducer(ss);
Consumer c= newConsumer(ss);//启动线程是调用start()方法,如果直接调用run方法就是普通的方法调用,不是异步的。
newThread(p).start();newThread(p).start();newThread(p).start();newThread(c).start();
}
}//这是声明一个对象 用来放进栈里面
classWoTou {intid;
WoTou(intid) {this.id =id;
}publicString toString() {return "WoTou : " +id;
}
}/*** 数据结构中 栈
* 有push和pop方法*/
classSyncStack {int index = 0;
WoTou[] arrWT= new WoTou[6];//因为arrWT 和index是成员变量,所以需要对这两个方法加锁。 加锁是锁住当前对象,他里面的成员变量自然也就锁定了
public synchronized voidpush(WoTou wt) {//须用while 而不是if 因为如果栈满了 需要一直等待
while(index ==arrWT.length) {try{//栈满了时 需要停止住
this.wait();
}catch(InterruptedException e) {
e.printStackTrace();
}
}//wait不同于sleep 不需要指定睡眠的时间, 但需要手动唤醒. 当index不等arrWT.length 表示栈现在不是满的可以进行push 于是手动唤醒线程
this.notifyAll();//如果不加锁 arrWT和index因为多线程同步 导致新加的窝头和指针不匹配//先把当前指针 执行新加的窝头
arrWT[index] =wt;//然后把指针加1
index ++;
}//pop操作和push操作刚好相反
public synchronizedWoTou pop() {while(index == 0) {try{this.wait();
}catch(InterruptedException e) {
e.printStackTrace();
}
}this.notifyAll();
index--;returnarrWT[index];
}
}/*** 定义一个生产者 每生产一个对象 睡眠200ms*/
class Producer implementsRunnable {
SyncStack ss= null;
Producer(SyncStack ss) {this.ss =ss;
}
@Overridepublic voidrun() {for(int i=0; i<2; i++) {
WoTou wt= newWoTou(i);
ss.push(wt);
System.out.println("生产了:" +wt);try{
Thread.sleep(200);
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}
}/*** 定义一个消费者*/
class Consumer implementsRunnable {
SyncStack ss= null;
Consumer(SyncStack ss) {this.ss =ss;
}
@Overridepublic voidrun() {for(int i=0; i<6; i++) {
WoTou wt=ss.pop();
System.out.println("消费了: " +wt);try{//每消费一个对象 睡眠200ms 可以在console控制台 慢慢的看到线程的调度执行
Thread.sleep(200);
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}
}
View Code
生产了:WoTou : 0
生产了:WoTou : 0
生产了:WoTou : 0
生产了:WoTou : 1
消费了: WoTou : 1
消费了: WoTou : 1
消费了: WoTou : 1
生产了:WoTou : 1
生产了:WoTou : 1
消费了: WoTou : 0
消费了: WoTou : 0
消费了: WoTou : 0
这是三个生产者一个消费者 最后的打印结果,可见 生产的窝头全部都被消费了
补充 volatile关键字和syschronized
synchronized声明不会被继承,如果一个用synchronized修饰的方法被子类覆盖,那么子类中的这个方法不再保持同步,除非也用synchronized修饰(锁重入)
synchonized 最能用来锁定字符串常量(因为常量池里面只有一个对象 如果允许,各种第三方jar包都锁定了同一个对象),也不能锁定基础的数据类型
补充:程序之中如果出现了异常,锁会被释放,会被其他程序冲进来(程序乱入,导致数据不一致 访问到异常产生中间的数据)
解决办法:Catch住异常
线程的常用方法和关键字
wait()和join()方法都会释放锁(join方法的底层也是使用了wait方法)
wait:的目的是为了多个线程抢资源的时候,当前线程把资源让出来
如果没有锁,就不存在两个线程抢资源的情况,也就没有必要把资源让出来,两个线程早就一起跑了 这也很好解释了为什么wait方法要和synchronized关键字连用。
join():合并某个线程,他调用此方法的线程(别的线程)合并到当前线程上来。等待调用此方法的线程执行完了,当前线程才开始继续执行(经常用于等待另外一个线程的结束,有点像方法调用)。
interrupt():中断阻塞会释放当前锁(如执行System.in.read() 也会进入阻塞状态等待用户的输入、调用了其他线程的join()方法、等待获取某个对象的锁 如死锁、wait())等都会进入阻塞状态。
注意,当一个线程获取了锁之后,是不会被interrupt()方法中断的
sleep()和yield()方法,当前线程都会放弃CPU,并且都不会释放锁
sleep():的过程之中 如果对当前对象加锁了(可以不加),不会释放当前对象的锁。
yield(): 当前线程会做出让出CPU使用的暗示,但是线程调度器可能会忽略这个暗示
----------------------------------------------------------------------------------------------
如果对象s的锁池中没有任何线程,那么notify()方法什么也不做。
等待池:假如线程A调用了某个对下的wait()方法,线程A就会释放该对象的锁,同时线程A就进入到了该线程的等待池中,进入到等待池中的线程不会竞争到该对象的锁。
锁池: 假如线程A已经拥有了某个对下的锁,而其他线程B,C想要调用这个对下的某个Synchronized方法,
此时B,C线程就会被阻塞,进入一个地方去等待锁的释放,这个地方便是该对象的锁池。
JAVA多线程死锁
产生死锁的原因:
不同的线程等待不可能被释放的锁 互相等待对方释放锁
看一个死锁的例子,
/*** 当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要额外的同步
* 或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那么这个对象是线程安全的*/
public class TestDeadLock implementsRunnable {public int flag = 1;private static Object o1 = newObject();private static Object o2 = newObject();public voidrun() {
System.out.println("flag=" +flag);if(flag == 1) {synchronized(o1) {try{//睡眠500ms,让t2线程执行先锁住o2
Thread.sleep(500);
}catch(Exception e) {
e.printStackTrace();
}//因为t1线程睡眠了500ms t1执行到这里的时,o2已经被 t2锁住了
synchronized(o2) {
System.out.println("死锁住了1");
}
}
}if(flag == 2) {synchronized(o2) {synchronized(o1) {
System.out.println("死锁住了2");
}
}
}
}public static voidmain(String[] args) {
TestDeadLock td1= newTestDeadLock();
TestDeadLock td2= newTestDeadLock();
td1.flag= 1;
td2.flag= 2;
Thread t1= newThread(td1);
Thread t2= newThread(td2);
t1.start();
t2.start();
}
}
上面代码执行的流程是,线程t1和线程t2异步执行。 执行线程t1的时 先锁住o1 此时线程t1 睡眠500ms。t2线程开始执行,t2线程锁住o2
等t2线程执行的时候 需要拿到o1的锁才能执行下去,此时1线程往下执行的时候 需要拿到o2的锁才能继续执行
t1线程和t2线程互相持有对方锁(t1锁住了o1 拿到o2的锁就可以继续执行了,t2锁住了o2,拿到o1的锁就可以继续执行了) 陷入了死锁的状态。
死锁解决办法
死锁是程序设计的Bug 在设计程序时就要避免双方互相持有对方锁的情况,只要互相等待对方释放锁就有可能出现死锁
当几个线程都需要访问共享资源A B和C时,保证每个线程都以相同的顺序执行他们,比如都先访问A,在访问B和C。
concurrent并发包
Lock
支持异步的Callable接口和Future接口
1)Callable接口:它和Runnable接口有点类似,都指定了线程所需要执行的操作。区别在于,Callable接口是在call()方法中指定线程所要执行的操作的,并且该方法有泛型的返回值。
此外 Callable实例不能像Runnable实例那样,直接作为Thread类的构造方法的参数。
2)Future接口:能够保存异步运算的结果。
get():方法返回异步运算的结果。如果运算结果还没处理,当前线程就会被阻塞,直到获得运算结果,才结束阻塞。
下面程序演示两个线程之间异步运算的过程。
public class Machine implements Callable{
@Overridepublic Integer call() throwsException {int sum=0;for(int a=0;a<100;a++){//计算从1加到100
sum=sum+a;
Thread.sleep(20);
}returnsum;
}public static void main(String[] args) throwsException {
FutureTask task=new FutureTask(newMachine());
Thread threadMachine= newThread(task);
threadMachine.start();//执行Machine的call()方法
System.out.println("等待计算结果...");//主线程调用task.get()方法 获得运算结果
System.out.println("从1加到100的和:"+task.get());
System.out.println("计算完毕");
}
}
在以上程序中,Machine类实现了Callable接口,threadMachine线程负责执行Machine类的call()方法,该方法会计算从1加到100的和,并且返回运算的结果。
主线程调用task.get()方法,当ThreadMachine线程还没运算完毕时,主线程就会阻塞,直到threadMachine线程执行完call()方法,主线程才会获得运算结果,并从task.get()方法中退出。