java线程sys_JAVA多线程

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的用法 以及数据结构中的栈

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

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():方法返回异步运算的结果。如果运算结果还没处理,当前线程就会被阻塞,直到获得运算结果,才结束阻塞。

c96a83c80b7a31a14b9510efed45cb3d.png

下面程序演示两个线程之间异步运算的过程。

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()方法中退出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值