Java多线程之线程同步和死锁
- 前言:大家在看此文章时若有时间请先看前面的俩篇文章:Java多线程之多线程概述和俩种创建方式与Java多线程之线程状态转换、控制线程和线程同步。这三篇Java多线程文章均为总结性文章,适合自己复习使用。
(一).线程同步
为什么要实现线程同步?当使用多个线程来访问同一个数据时,很容易“偶然”出现线程安全问题。我用通俗的语言来说明:我们知道Thread类实现了Runnable接口,新建的线程中每个线程都共享Thread类的成员变量(也就是共享资源)。那么很有可能当线程足够多时俩个线程同时取得成员变量,同时对成员变量进行操作,这时候数据紊乱,出现线程不安全现象。
【经典的银行取钱问题】:假如你的银行账户里面有2000块钱,有一天你去银行柜台取钱,取1500,正在你办理的时候,你老婆去了取款机,她也取钱,事先没商量好谁取,所以她也想取1500。如果两个人都取走了1500,合起来就3000了,银行咋办???
【个人理解】:所以我个人把线程同步理解为:多个线程同时访问同一共享资源,并且对共享资源进行操作,导致操作之后的资源混乱,不符合正确地流程所得结果。我们来看下面一个具体问题:
以买车票为例,不管多少售票点可以买火车票,同一列火车的票数一定,也就是说把每个售票点理解为线程的话,所有线程共享一份票数,票数就是线程共享资源。卖票就是线程对共享资源的操作。
package Thread1;
class MyThread implements Runnable{
private int ticket = 5 ; // 假设一共有5张票
public void run(){
for(int i=0;i<100;i++){
if(ticket>0){ // 还有票
try{
Thread.sleep(300) ; // 加入延迟
}catch(InterruptedException e){
e.printStackTrace() ;
}
System.out.println("卖票:ticket = " + ticket-- );
}
}
}
};
public class demo1{
public static void main(String args[]){
MyThread mt = new MyThread() ; // 定义线程对象
Thread t1 = new Thread(mt) ; // 定义Thread对象
Thread t2 = new Thread(mt) ; // 定义Thread对象
Thread t3 = new Thread(mt) ; // 定义Thread对象
t1.start() ;
t2.start() ;
t3.start() ;
}
};
运行结果:
卖票:ticket = 5
卖票:ticket = 3
卖票:ticket = 4
卖票:ticket = 2
卖票:ticket = 1
卖票:ticket = 0
卖票:ticket = -1
此时结果发现卖出的票数成负数,程序代码出现问题。
- 如上,程序出现问题有可能是线程2取出数据,但是还没有把ticket减一的数据送回去之前,线程n也取出了这个数据执行了减一操作,这样这俩个数据都执行了俩次减一操作导致数据为负。
【问题解决】
1.要解决这种问题必须使用同步,所谓同步是指多个操作在同一个时间段内只能有一个线程进行,其他线程要等这个线程完成后才能继续执行。
2.使用同步方法解决:要想解决共享资源的同步操作问题,需要使用同步代码块或是同步方法俩种方式来完成。
- 【同步代码块】,使用synchronized关键字声明的代码块叫做同步代码块。格式如下:
synchronized(同步对象){
需要同步的代码
}
- 上面synchronized后括号里的同步对象也叫做同步监视器!代码含义是:线程在执行需要同步的代码之前需要获得对同步监视器的锁定。【注意】任何时刻只有一个线程能获得对同步监视器的锁定,当同步代码块执行完成后,该线程会释放对该同步监视器的锁定。同步的时候必须指明:同步的对象,一般情况下,会将当前对象作为同步对象使用this表示,或是将要共享的成员变量。
package Thread1;
class MyThread implements Runnable{
private int ticket = 5 ; // 假设一共有5张票
public void run(){
for(int i=0;i<100;i++){
synchronized(this){ // 要对当前对象进行同步
if(ticket>0){ // 还有票
try{
Thread.sleep(300) ; // 加入延迟
}catch(InterruptedException e){
e.printStackTrace() ;
}
System.out.println("卖票:ticket = " + ticket-- );
}
}
}
}
};
public class demo1{
public static void main(String args[]){
MyThread mt = new MyThread() ; // 定义线程对象
Thread t1 = new Thread(mt) ; // 定义Thread对象
Thread t2 = new Thread(mt) ; // 定义Thread对象
Thread t3 = new Thread(mt) ; // 定义Thread对象
t1.start() ;
t2.start() ;
t3.start() ;
}
};
- 运行结果:
卖票:ticket = 5
卖票:ticket = 4
卖票:ticket = 3
卖票:ticket = 2
卖票:ticket = 1
从运行结果可以发现,程序加入了同步操作,所以不会产生负数的情况,但是程序的执行效率明显降低很多。
【同步方法】:使用了synchronized声明的方法为同步方法。同步方法定义格式:
synchronized 方法返回值类型 方法名称(参数列表)
{
}
package Thread1;
class MyThread implements Runnable{
private int ticket = 5 ; // 假设一共有5张票
public void run(){
for(int i=0;i<100;i++){
this.sale() ; // 调用同步方法
}
}
public synchronized void sale(){ // 声明同步方法
if(ticket>0){ // 还有票
try{
Thread.sleep(300) ; // 加入延迟
}catch(InterruptedException e){
e.printStackTrace() ;
}
System.out.println("卖票:ticket = " + ticket-- );
}
}
};
public class demo1{
public static void main(String args[]){
MyThread mt = new MyThread() ; // 定义线程对象
Thread t1 = new Thread(mt) ; // 定义Thread对象
Thread t2 = new Thread(mt) ; // 定义Thread对象
Thread t3 = new Thread(mt) ; // 定义Thread对象
t1.start() ;
t2.start() ;
t3.start() ;
}
};
- 运行结果:
卖票:ticket = 5
卖票:ticket = 4
卖票:ticket = 3
卖票:ticket = 2
卖票:ticket = 1
(二).死锁
- 当俩个线程在互相等待对方释放同步监视器时就会发生死锁,java虚拟机没有检测,也没有采取措施来处理死锁情况,所以多线程编程应该采取措施避免死锁出现。一旦出现死锁,整个程序既不会发生任何异常,也不会给出任何提示,只是所有线程处于阻塞状态,无法继续。
资源共享时候需要进行同步操作,程序中过多的同步会产生死锁。
死锁一般情况下表示互相等待,线程之间互相需要对方的资源程序才能执行下去。
package Thread1;
class Zhangsan{ // 定义张三类
public void say(){
System.out.println("张三对李四说:“你给我画,我就把书给你。”") ;
}
public void get(){
System.out.println("张三得到画了。") ;
}
};
class Lisi{ // 定义李四类
public void say(){
System.out.println("李四对张三说:“你给我书,我就把画给你”") ;
}
public void get(){
System.out.println("李四得到书了。") ;
}
};
public class demo1 implements Runnable{
private static Zhangsan zs = new Zhangsan() ; // 实例化static型对象
private static Lisi ls = new Lisi() ; // 实例化static型对象
private boolean flag = false ; // 声明标志位,判断那个先说话
public void run(){ // 覆写run()方法
if(flag){
synchronized(zs){ // 同步张三
zs.say() ;
try{
Thread.sleep(500) ;
}catch(InterruptedException e){
e.printStackTrace() ;
}
synchronized(ls){
zs.get() ;
}
}
}else{
synchronized(ls){
ls.say() ;
try{
Thread.sleep(500) ;
}catch(InterruptedException e){
e.printStackTrace() ;
}
synchronized(zs){
ls.get() ;
}
}
}
}
public static void main(String args[]){
demo1 t1 = new demo1() ; // 控制张三
demo1 t2 = new demo1() ; // 控制李四
t1.flag = true ;
t2.flag = false ;
Thread thA = new Thread(t1) ;
Thread thB = new Thread(t2) ;
thA.start() ;
thB.start() ;
}
};
张三对李四说:“你给我画,我就把书给你。”
李四对张三说:“你给我书,我就把画给你”
- 此时双方说完话都在等待对方回应,此时处于停滞状态,都在互相等待着对方回答。