Fork me on GitHub

第22章 java线程(2)-线程同步

java线程(2)-线程同步

本节主要是在前面吃苹果的基础上发现问题,然后提出三种解决方式

1.线程不安全问题

什么叫线程不安全呢
即当多线程并发访问同一个资源对象的时候,可能出现不安全的问题

对于前一章例子中,使用接口实现方式时会有重复现象,使用接口方式时我们还没有发现明显的现象,但是这并不代表原来的代码没有问题
我们发现没有问题,必须要有这个意识:看不到问题,有可能是我们经验太少,或者问题出现的不够明显。
如果问题不够明显,我们可以使用Thread.sleep()方法,正在运行的线程暂停,此时会执行另外的进程,如此可以更方便的看到问题

注意:
在线程的run方法上不能使用throws来声明抛出异常,只能在方法中使用try-catch来处理异常
原因是:子类覆盖父类的原则,子类不能抛出新的异常。
在Runnable接口中的run方法,都没有抛出异常,所以子类中也不能对这个方法抛出异常
父类中:public abstract void run();

解决方法的原理
回到我们用继承方法的问题上来,如何才能让一个线程没有执行完时另一个线程不会操作一些动作呢。
解决方案是:保证某一些的动作的同步完成。即保证打印苹果和苹果总数-1操作,必须同步,作为一个单元来执行
具体的解决方法有3个:
方式1:同步代码块
方式2:同步方法
方式3:锁机制(lock)
下面分别实现三种方法

2.线程同步

思想是把不能中断或者分开执行的代码绑定在一起执行,使其不能在执行中中断

2.1.同步代码块

语法:

synchronized(同步锁)
{
	需要同步操作的代码
}

同步锁:
为了保证没一个线程都能正常执行原子操作,java引入了线程同步机制
同步锁又被称为:同步监听对象/同步监听器/互斥锁
其实就是相当与一把只有一个坑位的卫生间门上的锁,当里面有人的时候,门必须要被锁上,等人出来了再把锁打开
对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁
java程序运行使用任何对象作为同步锁,但是一般的,我们是用当前并发访问的共同资源作为同步监听对象
注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他线程,只能在外面等着
示例代码:

//接口实现方式
class Apple1 implements Runnable{
    private int num = 50;

    public void run(){
        for (int i = 0; i < 50; i++) {
            synchronized (this){
                if (num > 0){
                    System.out.println(Thread.currentThread().getName()+"吃了编号为"+ num +"的苹果");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    num--;
                }
            }

        }
    }
}


public class appleImplements {
    public static void main(String[] args) {
        //创建桑个线程
        Apple1 a = new Apple1();
        new Thread(a,"小A").start();
        new Thread(a,"小B").start();
        new Thread(a,"小C").start();
    }
}

注意:因为继承方法没有不能实现资源的共享,所以这里的例子都是以接口方式实现的。

2.2.同步方法

同步方法:使用synchronized修饰的方法,就叫做同步方法。保证A线程执行该方法的时候,其他线程只能在外面等着
语法:

synchronized public void doWork(){
	//TODO 要执行的动作
}

同步锁是谁:
对于非static方法,同步锁就是this
对于static方法,我们使用当前方法所在的类的字节码对象(Apple2.class)

注意:不要使用synchronized修饰run方法,修饰之后,某一个线程就执行完了所有的功能,好比是多个线程出现串行
代码实现:


//接口实现方式
class Apple2 implements Runnable{
    private int num = 500;

    public void run(){
        for (int i = 0; i < 500; i++) {
            eat();

        }
    }

    synchronized private void eat(){
        if (num > 0){
            System.out.println(Thread.currentThread().getName()+"吃了编号为"+ num +"的苹果");
            try {
                Thread.sleep(10);//模拟网络延迟
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            num--;
        }
    }
}

public class appleImplements {
    public static void main(String[] args) {
        //创建桑个线程
        Apple2 a = new Apple2();
        new Thread(a,"小A").start();
        new Thread(a,"小B").start();
        new Thread(a,"小C").start();
    }
}

2.3.同步锁(Lock)

Lock机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象
语法:

Lock.png
思想就是,在执行要绑定的代码之前,上一把锁,执行完之后呢,就把锁打开,这样别的线程就能继续了。
在使用这个锁之前呢,要先new一个锁出来
代码示例;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;


//接口实现方式
class Apple4 implements Runnable{
    private int num = 50;
    private final Lock lock = new ReentrantLock();//创建一个锁对象
    public void run(){
        for (int i = 0; i < 50; i++) {
            eat();

        }
    }

     private void eat(){
        //进入方法:立马加锁
        lock.lock();//获取锁
        if (num > 0){
            try {
                System.out.println(Thread.currentThread().getName()+"吃了编号为"+ num +"的苹果");
                Thread.sleep(100);//模拟网络延迟
                num--;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                //执行完,走人,并把锁打开
                lock.unlock();//释放锁
            }
        }
    }
}

public class appleImplements {
    public static void main(String[] args) {
        //创建桑个线程
        Apple4 a = new Apple4();
        new Thread(a,"小A").start();
        new Thread(a,"小B").start();
        new Thread(a,"小C").start();
    }
}

2.4.synchronized的好与坏

好处:保证了多线程并并发访问时的同步操作,避免线程的安全性问题
缺点:使用synchronized方法/代码块的性能比不用要低一些
建议:尽量减小synchronized的作用域

posted @ 2016-12-09 15:52  洋葱源码  阅读(265)  评论(0编辑  收藏  举报