内容学习于:edu.aliyun.com


内容概括:

  在多线程的处理之中,可以利用Runnable描述多个线程操作的资源,而Thread描述每一个线程对象,于是当多个线程访问统一资源的时候如果处理不当就会产生数据的错误操作。

1. 同步问题的提出

卖票操作代码:

class MyThread implements Runnable {
    private int ticket = 10;//总票数为10
    @Override
    public void run() {
        while (true){
            if(this.ticket>0){
                System.out.println(Thread.currentThread().getName()+",卖 票、当前票数:"+this.ticket--);
            }else {
                System.out.println("***票已经卖光***");
                break;
            }
        }
    }
}

public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        MyThread mt = new MyThread();
        //三个线程卖5张票
        new Thread(mt,"票贩子A").start();
        new Thread(mt,"票贩子B").start();
        new Thread(mt,"票贩子B").start();
    }
}

结果:

票贩子B,卖票、当前票数:8
票贩子B,卖票、当前票数:9
票贩子A,卖票、当前票数:10
票贩子B,卖票、当前票数:6
票贩子B,卖票、当前票数:7
票贩子B,卖票、当前票数:4
票贩子A,卖票、当前票数:5
票贩子B,卖票、当前票数:2
***票已经卖光***
票贩子B,卖票、当前票数:3
***票已经卖光***
票贩子A,卖票、当前票数:1
***票已经卖光***

  此时的程序将创建三个线程对象,并且这三个线程对象将进行5张票的出售。此时的程序在进行卖票处理的时候并没有任何的问题(假象),下面可以模拟一下卖票中的延迟操作。

代码:

class MyThread implements Runnable {
    private int ticket = 10;//总票数为10

    @Override
    public void run() {
        while (true) {
            if (this.ticket > 0) {
                try {
                    Thread.sleep(100);//模拟网路延迟
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + ",卖票、当前票数:" + this.ticket--);
            } else {
                System.out.println("***票已经卖光***");
                break;
            }
        }
    }
}

public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        MyThread mt = new MyThread();
        //三个线程卖5张票
        new Thread(mt, "票贩子A").start();
        new Thread(mt, "票贩子B").start();
        new Thread(mt, "票贩子B").start();
    }
}

结果:

票贩子B,卖票、当前票数:9
票贩子B,卖票、当前票数:10
票贩子A,卖票、当前票数:9
票贩子B,卖票、当前票数:8
票贩子B,卖票、当前票数:8
票贩子A,卖票、当前票数:7
票贩子B,卖票、当前票数:6
票贩子B,卖票、当前票数:6
票贩子A,卖票、当前票数:5
票贩子A,卖票、当前票数:4
票贩子B,卖票、当前票数:3
票贩子B,卖票、当前票数:3
票贩子B,卖票、当前票数:2
票贩子A,卖票、当前票数:2
票贩子B,卖票、当前票数:2
票贩子B,卖票、当前票数:1
票已经卖光
票贩子B,卖票、当前票数:-1
票已经卖光
票贩子A,卖票、当前票数:0
票已经卖光

  这个时候追加了延迟问题就暴露出来了,而实际上这个问题一直都在。
  如下图所示:

2. 线程同步

  经过分析之后已经可以确认同步问题所产生的主要原因了,那么下面就需要进行同步问题的解决,但是解决同步问题的关键是锁,指的是当某一个线程执行操作的时候,其它线程外面等待;
  如果要想在程序之中实现这把锁的功能,就可以使用synchronized关键字来实现,利用此关键也可收定义同步方法或同步代码块,在同步代码块的操作里面的代码只允许一个线程执行。

synchronized(同步对象){
	同步代码操作;
}

  <mark>一般要进行同步对象处理的时候可以采用当前对象this进行同步。</mark>

同步代码块解决代码:

class MyThread implements Runnable {
    private int ticket = 10;//总票数为10

    @Override
    public void run() {
        while (true) {
            synchronized (this) {//每次只允许一个线程访问
                if (this.ticket > 0) {
                    try {
                        Thread.sleep(1);//模拟网路延迟
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ",卖票、当前票数:" + this.ticket--);
                } else {
                    System.out.println("***票已经卖光***");
                    break;
                }
            }
        }
    }
}

public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        MyThread mt = new MyThread();
        //三个线程卖5张票
        new Thread(mt, "票贩子A").start();
        new Thread(mt, "票贩子B").start();
        new Thread(mt, "票贩子B").start();
    }
}

结果:

票贩子A,卖票、当前票数:10
票贩子A,卖票、当前票数:9
票贩子A,卖票、当前票数:8
票贩子A,卖票、当前票数:7
票贩子A,卖票、当前票数:6
票贩子A,卖票、当前票数:5
票贩子A,卖票、当前票数:4
票贩子A,卖票、当前票数:3
票贩子A,卖票、当前票数:2
票贩子A,卖票、当前票数:1
***票已经卖光***
***票已经卖光***
***票已经卖光***

  (将ticket的数量变多后,就不会一直出现只有一个票贩子卖票的情况了)
  <mark>加入同步处理之后,程序的整体的性能下降了。同步实际上会造成性能的降低。</mark>

同步方法代码:

class MyThread implements Runnable {
    private int ticket = 10;//总票数为10

    public synchronized boolean sale(){
        if (this.ticket > 0) {
            try {
                Thread.sleep(100);//模拟网路延迟
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ",卖票、当前票数:" + this.ticket--);
            return true;
        } else {
            System.out.println("***票已经卖光***");
            return false;
        }
    }
    @Override
    public void run() {
        while (this.sale()) {}
        }
    }
    
public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        MyThread mt = new MyThread();
        //三个线程卖5张票
        new Thread(mt, "票贩子A").start();
        new Thread(mt, "票贩子B").start();
        new Thread(mt, "票贩子B").start();
    }
}

  在日后学习Java类库的时候会发现,系统中许多的类上使用的同步处理采用的都是同步方法。
  <mark>一定要记住:同步会造成性能下降。</mark>

3. 死锁

  死锁是在进行多线程同步的处理之中有可能产生的一种问题,所谓的死锁指的是若干个线程彼此互相等待的状态。下面通过一个简单的代码来观察一下死锁的表现形式,但是对于此代码不作为重点。

代码:

public class DeadLock implements Runnable {
    private Jian jj = new Jian();
    private XiaoMing xm = new XiaoMing();

    @Override
    public void run() {
        xm.say(jj);

    }

    public DeadLock() {
        new Thread(this).start();
        jj.say(xm);
    }

    public static void main(String[] args) {
        new DeadLock();
    }
}


class Jian {
    public synchronized void say(XiaoMing xm) {
        System.out.println("阿健说:此路是我开,要想从这过,留下买路财10块");
        xm.get();
    }

    public synchronized void get() {
        System.out.println("阿健说:给钱了,我可以让路了");
    }
}

class XiaoMing {
    public synchronized void say(Jian jj) {
        System.out.println("小明说:先让我走,我再给钱");
        jj.get();
    }

    public synchronized void get() {
        System.out.println("小明说:我走了,钱先赖着");
    }
}

结果:

  如下图所示:

  现在死锁造成的主要原因是因为彼此都在互相等待着,等待着对方先让出资源。死锁实际上是一种开发中出现的不确定的状态,有的时候代码如果处理不当则会不定期出现死锁,这是属于正常开发中的调试问题。
<mark>若干个线程访问同一资源时一定要进行同步处理,而过多的同步会造成死锁。</mark>