线程安全

线程安全

如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。

程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

案例:

卖票(只能卖100张票)

初始:

复制代码
public static void main(String[] args) {
        //创建票对象
        Ticket ticket = new Ticket();
        
        //创建3个窗口
        Thread t1  = new Thread(ticket, "窗口1");
        Thread t2  = new Thread(ticket, "窗口2");
        Thread t3  = new Thread(ticket, "窗口3");
        
        t1.start();
        t2.start();
        t3.start();
    }
}
复制代码
复制代码
public class Ticket implements Runnable {
//共100票
int ticket = 100;

@Override
public void run() {
    //模拟卖票
    while(true){
        if (ticket > 0) {
            //模拟选坐的操作
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
                System.out.println(Thread.currentThread().getName() + "正在卖票:" + ticket--);
        }
    }
}
复制代码

结果:

 

运行结果发现:上面程序出现了问题

票出现了重复的票

错误的票 0、-1

总结:

其实,线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作。

一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

-1和0的出现原因:

当刚刚判断完还有票时CPU资源被其它线程占用进入休眠状态,当有空闲资源在进行时就不会再判断直接进行了减票操作就会出现0和-1的状况

解决方式:

线程同步

利用(Synchronized)

 

线程同步的方式有两种:

 

方式1:同步代码块

 

方式2:同步方法

同步代码块(上厕所原理)

 

在代码块声明上 加上synchronized

synchronized (锁对象) {
    可能会产生线程安全问题的代码
}

注意:

同步代码块中的锁对象可以是任意的对象;但多个线程时,要使用同一个锁对象才能够保证线程安全。

复制代码
public class Tickets implements Runnable{
private /*static*/ int ticket=100;
//对象锁
private Object obj=new Object();
public void run() {
    while(true){
        synchronized (obj) {
            if(ticket>0){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                    System.out.println(Thread.currentThread().getName()+"出售第"+ticket--+"张票");
            }else{
                return;
            }
        }
    }
}
复制代码

原理:线程运行首先要拿到锁(obj)才能进行下面的程序,如果锁被占用那么只能等待,线程循环运行完后自动归还锁供下一个线程循环运行。

同步方法

在方法声明上加上synchronized

public synchronized void method(){

    可能会产生线程安全问题的代码

}

注意:

同步方法中的锁对象是 this

②同步锁别名:对象锁,对象监视器

演示:

private /*static*/ int ticket=100;
public void run() {
    while(true){
        sale();
    }
}
复制代码
public /*static*/ synchronized void sale(){
    if(ticket>0){
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
            System.out.println(Thread.currentThread().getName()+"出售第"+ticket--+"张票");
    }else{
        return;
    }
}
复制代码

静态同步方法:

在方法声明上加上static synchronized

public static synchronized void method(){
可能会产生线程安全问题的代码
}

静态同步方法中的锁对象是     类名.class

补充:

StringBuilder和StringBuffer真正区别体现:

StringBuffer(方法都有synchronized,都是同步的方法,但这样就会降低速度)

新方式:

Lock接口

Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。

常用方法:

演示(还是拿上面的卖票为例):

复制代码
public class MewTicket implements Runnable{
    private int ticket=100;
    private Lock lock=new ReentrantLock();
    public void run() {
        while(true){
            lock.lock();
            if(ticket>0){
                try {
                Thread.sleep(10);
                System.out.println(Thread.currentThread().getName()+"出售第"+ticket--+"张票");
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }finally{
                    //释放锁
                    lock.unlock();
                }
            }
        }
    }
}
复制代码

死锁现象

同步锁使用的弊端:当线程任务中出现了多个同步(多个锁)时,如果同步中嵌套了其他的同步。

这时容易引发一种现象:程序出现无限等待,这种现象我们称为死锁。这种情况能避免就避免掉。

synchronzied(A锁){
    synchronized(B锁){

    }
}

定义锁对象:

public class lockA {
    private lockA(){
    }
    public static final lockA locka=new lockA();
}
public class lockB {
    private lockB(){
    }
    public static final lockB lockb=new lockB();
}

任务:

复制代码
public class deadlock implements Runnable{
    private int i=0;
    public void run() {
        while(true){
            if(i%2==0){
                //偶数先进A再进B
                synchronized (lockA.locka) {
                    System.out.println("if......lockA");
                    synchronized (lockB.lockb) {
                        System.out.println("if......lockB");
                    }
                }
            }else{
                //奇数先进B再进A
                synchronized (lockB.lockb) {
                    System.out.println("else......lockB");
                    synchronized (lockA.locka) {
                        System.out.println("else......lockA");
                    }
                }
            }
            i++;
        }
    }
}
复制代码

创建线程:

复制代码
public static void main(String[] args) {
    deadlock d1=new deadlock();
    Thread t1=new Thread(d1);
    Thread t2=new Thread(d1);
    t1.start();
    t2.start();
}
复制代码

等待唤醒机制

线程之间的通信:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。通过一定的手段使各个线程能有效的利用资源。

而这种手段即—— 等待唤醒机制。

等待唤醒机制所涉及到的方法:

①wait() :等待,将正在执行的线程释放其执行资格 和 执行权,并存储到线程池中。

②notify():唤醒,唤醒线程池中被wait()的线程,一次唤醒一个,而且是任意的。

③notifyAll(): 唤醒全部:可以将线程池中的所有wait() 线程都唤醒。

其实,所谓唤醒的意思就是让 线程池中的线程具备执行资格。必须注意的是,这些方法都是在同步中才有效。

同时这些方法在使用时必须标明所属锁,这样才可以明确出这些方法操作的到底是哪个锁上的线程。

举例:

分析:

1.当input发现Resource中没有数据时,开始输入,输入完成后,叫output来输出。如果发现有数据,就wait();

2.当output发现Resource中没有数据时,就wait() ;当发现有数据时,就输出,然后,叫醒input来输入数据。

演示:

public class Resouce {
    public String name;
    public String sex;
    public boolean flag=false;
}

输入:

复制代码
public class Input implements Runnable{
    private Resouce r;
    public Input(Resouce r) {
        super();
        this.r = r;
    }
    private int i=0;
    public void run(){
        while(true){
            //注意要用同一个锁对象,保证同步(r)
            synchronized (r) {
                //标记为true
                if(r.flag==true){
                    try {
                        //这些方法在使用时必须标明所属锁
                        r.wait();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
                if(i%2==0){
                    r.name="张三";
                    r.sex="男";
                }else{
                    r.name="李四";
                    r.sex="nv";
                }
                //改变flag,唤醒Output
                r.flag=true;
                r.notify();
            }
            i++;
        }
    }
}
复制代码

输出:

复制代码
public class Output implements Runnable{
    private Resouce r;
    public Output(Resouce r) {
        super();
        this.r = r;
    }

    public void run() {
        while(true){
            synchronized (r) {
                //标记为false就wait
                if(r.flag==false){
                    try {
                        r.wait();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
                //如果为true就打印
                System.out.println(r.name+"..."+r.sex);
                //更改flag唤醒input
                r.flag=false;
                r.notify();
            }
        }
    }
}
复制代码

测试:

复制代码
public class text {
    public static void main(String[] args) {
        Resouce r=new Resouce();//定义同一个对象
        Input in=new Input(r);
        Output out=new Output(r);
        Thread tin=new Thread(in);
        Thread tout=new Thread(out);
        tin.start();
        tout.start();
    }
}
复制代码

练习:

有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池用一个数组int[] arr = {10,5,20,50,100,200,500,800,2,80,300};

创建两个抽奖箱(线程)设置线程名称分别为“抽奖箱1”,“抽奖箱2”,随机从arr数组中获取奖项元素并打印在控制台上,格式如下:

抽奖箱1 又产生了一个 10 元大奖

抽奖箱2 又产生了一个 100 元大奖

。。。。。。

演示:

定义奖池:

public class jiangchi {
    public int[] arr={10,5,20,50,100,200,500,800,2,80,300};
    public boolean flag=false;
}

抽奖箱1:

复制代码
public class xiang1 implements Runnable{
    private jiangchi j;
    Random a=new Random();//创建随机数对象
    public xiang1(jiangchi j) {
        super();
        this.j = j;
    }
    public void run() {
        while(true){
            synchronized (j) {
                if(j.flag==true){
                    try {
                        j.wait();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }else{
                    //随机抽取奖品
                    int arr[]=j.arr;
                    int rdm=0;
                    int random = a.nextInt(11);
                    rdm=random;
                    System.out.println("抽奖箱1 又产生了一个 "+arr[rdm]+"元大奖");
                    j.flag=true;
                    j.notify();
                }
            }
        }
    }
}
复制代码

抽奖箱2:

复制代码
public class xiang2 implements Runnable{
    private jiangchi j;
    Random a=new Random();//创建随机数对象
    public xiang2(jiangchi j) {
        super();
        this.j = j;
    }
    public void run() {
        while(true){
            synchronized (j) {
                if(j.flag==false){
                    try {
                        j.wait();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }else{
                    //随机抽取奖品
                    int arr[]=j.arr;
                    int rdm=0;
                    int random = a.nextInt(11);
                    rdm=random;
                    System.out.println("抽奖箱2 又产生了一个 "+arr[rdm]+"元大奖");
                    j.flag=false;
                    j.notify();
                }
            }
        }
    }
}
复制代码

测试:

复制代码
public static void main(String[] args) {
    jiangchi jc=new jiangchi();
    xiang1 x1=new xiang1(jc);
    xiang2 x2=new xiang2(jc);
    Thread xiang1=new Thread(x1);
    Thread xiang2=new Thread(x2);
    xiang1.start();
    xiang2.start();
}
复制代码
posted @ 2018-08-30 10:46  文昭  阅读(112)  评论(0编辑  收藏  举报