第一周学习总结(20190819)
-
同步
所谓的同步问题出现在多线程的环境中,由于多个线程同时操作同一个资源而造成的问题。举个简单的栗子:计算机在加减一个数时会先将数从内存取到寄存器中然后利用加法器进行计算,再放回寄存器送回内存。如果是有两个线程同时在进行这样的工作,就会出现这样一个问题,当第一个线程将数计算完成后放回寄存器,而这时第二个线程占用CPU进行工作,第一个线程的工作就要进行入栈保存,以便后面恢复。第二个线程获取了CPU工作权限以后就会进行数的加减,然后第一个线程获得CPU运行权限后恢复之前的运行结果继续运算,当第一个线程将计算结果写入内存后就会覆盖第二个线程到计算结果,从而出现问题。
这里要解决这个问题就要引入一个关键字synchronized。这个关键字有两种用法,其一是同步代码块,其二是同步方法。下面依次举例:例1(同步代码块)以网上售票站点为例,main函数中的每个线程都代表一个网上售票站点:
package testsynchronized;
public class TicketSale {
public static void main(String args[])
{
Synchro syn = new Synchro();
new Thread(syn,"线程1").start();
new Thread(syn,"线程2").start();
new Thread(syn,"线程3").start();
new Thread(syn,"线程4").start();
new Thread(syn,"线程5").start();
}
}
package testsynchronized;
public class Synchro implements Runnable{
private int tickets = 5;
@Override
public void run() {
// TODO Auto-generated method stub
if (this.tickets > 1)
{
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.tickets = --this.tickets;
System.out.println(Thread.currentThread().getName()+"余票"+this.tickets);
}
else
{
System.out.println("tickets sale out!!!");
}
}
}
运行结果:
线程1 余票4
线程2 余票2
线程4 余票3
线程3 余票3
线程5 余票1
从上面的结果就能看出之前描述的问题,下面再看看同步代码块的处理,只需要改变Synchro 类:
package testsynchronized;
public class Synchro implements Runnable{
private int tickets = 5;
@Override
public void run() {
// TODO Auto-generated method stub
synchronized(this) {//看这里,只加了这一个地方,整个花括号括起来的就是同步代码块
if (this.tickets > 1)
{
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
this.tickets = --this.tickets;
System.out.println(Thread.currentThread().getName()+"余票"+this.tickets);
}
else
{
System.out.println("tickets sale out!!!");
}
}
}
}
运行结果:
线程1余票4
线程5余票3
线程4余票2
线程3余票1
tickets sale out!!!
再下面是同步方法(就是用synchronized这个关键字去描述方法):
package testsynchronized;
public class Synchro implements Runnable{
private int tickets = 5;
@Override
public void run() {
// TODO Auto-generated method stub
saletickets();
}
//出售方法
private synchronized void saletickets() throws InterruptedException
{
if (this.tickets > 1)
{
this.tickets -= 1;
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()+"余票"+(this.tickets));
}
else
{
System.out.println("tickets sale out!!!");
}
}
}
运行结果:
线程1余票4
线程5余票3
线程4余票2
线程3余票1
tickets sale out!!!
但在进行同步方法试验的时候出现了这么一种情况
程序代码如下(将this.tickets作为参数传入出售方法中,在方法末尾返回一个修改后的值给this.tickets)
package testsynchronized;
public class Synchro implements Runnable{
private int tickets = 5;
@Override
public void run() {
// TODO Auto-generated method stub
try {
this.tickets = saletickets(this.tickets);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"赋值后的tickets"+(this.tickets ));
}
private synchronized int saletickets(int ticket) throws InterruptedException
{
System.out.println(Thread.currentThread().getName()+"this.tickets "+(this.tickets));
System.out.println(Thread.currentThread().getName()+"ticket "+(ticket));
if (ticket > 1)
{
ticket -= 1;
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()+"余票"+(ticket ));
return ticket;
}
else
{
System.out.println("tickets sale out!!!");
return 0;
}
}
}
运行结果:
线程2this.tickets 5
线程2ticket 5
线程2余票4
线程4this.tickets 5
线程4ticket 5
线程2赋值后的tickets4
线程4余票4
线程4赋值后的tickets4
线程3this.tickets 4
线程3ticket 5
线程3余票4
线程3赋值后的tickets4
线程5this.tickets 4
线程5ticket 5
线程5余票4
线程5赋值后的tickets4
线程1this.tickets 4
线程1ticket 5
线程1余票4
线程1赋值后的tickets4
从上面加黑的结果来看Synchro的属性tickets在返回值进行赋值以后已经改变,但是传入
saletickets(int ticket)方法的值依然没有更新,也就是说同步锁是锁在了为方法传入参数值之后,方法体执行之前。(具体锁在了哪一步只有留待后面的深入学习进行考证,如果有了解的朋友请在下面留言,感激不尽)
- 死锁
死锁其实理解起来也是比较简单的,就像小明和小王,小明拥有一本《分布式服务构架》,小王拥有一本《java第一行代码》,小明想要小王的书,小王想要小明的书,小明对小王说你不给我《java第一行代码》,我就不给你《分布式服务构架》,同样小王对小明说你不给我《分布式服务构架》,我就不给你《java第一行代码》,俩人就此僵持。这个例子其实已经足够说明死锁的形成了,来源于进程对资源的相互抢占和资源的不释放。产生死锁严格来说有四个条件(条件内容引自线程互斥量及死锁的形成):
1)互斥条件:在一段时间内,资源只能被一个进程占用。
2)请求和保持条件:进程在拥有一个资源的条件下提出新的资源请求,但请求资源被占用,此时的请求被阻塞,而自己占用的资源不释放。
3)不可抢占条件:进程占用的资源在未完成前不能被抢占
4)循环等待条件:进程P1等待被P2占用的资源,P2等待被P1占用的资源
其上的四个条件破坏任何一个条件都能避免死锁的发生。
以下为死锁预防方法:
1)所有的进程在运行之前一次性申请在整个运行过程中所需要的全部资源
2)进程占用的资源在运行中逐步释放已用完的资源
3)当一个已经保持了某些不可被抢占资源的进程,提出新的资源请求而不能得到满足时,它必须释放已经保持的所有资源,待以后需要时再重新申请
4)对资源进行线性排序,并赋予不同的序号,规定每个进程必须按序号递增的顺序请求资源