多线程安全1

-多线程安全

当多个线程同时共享,同一个全局变量或静态变量,做写的操作时,可能会发生数据冲突问题,也就是线程安全问题。但是做读操作是不会发生数据冲突问题。

案例

案例:需求现在有100张火车票,有两个窗口同时抢火车票,请使用多线程模拟抢票效果。

package com.flyfish.thread.test;

class ThreadTrain1 implements Runnable {
    private int count = 100;
    private static Object oj = new Object();

    @Override
    public void run() {
        while (count > 0) {
            try {
                Thread.sleep(50);
            } catch (Exception e) {
                // TODO: handle exception
            }
            sale();
        }
    }

    public void sale() {
        // 前提 多线程进行使用、多个线程只能拿到一把锁。
        // 保证只能让一个线程 在执行 缺点效率降低
        // synchronized (oj) {
//		if (count > 0) {
        System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - count + 1) + "票");
        count--;
//		}
        // }
    }
}

public class ThreadDemo {
    public static void main(String[] args) {
        ThreadTrain1 threadTrain1 = new ThreadTrain1();
        Thread t1 = new Thread(threadTrain1, "①号窗口");
        Thread t2 = new Thread(threadTrain1, "②号窗口");
        t1.start();
        t2.start();
    }
}

运行结果

在这里插入图片描述

部分火车票会重复出售。
结论发现,多个线程共享同一个全局成员变量时,做写的操作可能会发生数据冲突问题。

线程安全解决办法:

  • 使用多线程之间同步synchronized或使用锁(lock)。、
  • 将可能会发生数据冲突问题(线程不安全问题),只能让当前一个线程进行执行。
  • 代码执行完成后释放锁,让后才能让其他线程进行执行。这样的话就可以解决线程不安全问题。

什么是多线程之间同步?

  • 当多个线程共享同一个资源,不会受到其他线程的干扰。

同步代码块

  • 什么是同步代码块?

就是将可能会发生线程安全问题的代码,给包括起来。
synchronized(同一个数据){
可能会发生线程冲突问题
}
就是同步代码块
synchronized(对象)//这个对象可以为任意对象
{
需要被同步的代码
}
对象如同锁,持有锁的线程可以在同步中执行
没持有锁的线程即使获取CPU的执行权,也进不去

同步的前提:

1.必须要有两个或者两个以上的线程

2. 必须是多个线程使用同一个锁

必须保证同步中只能有一个线程在运行

好处:解决了多线程的安全问题

弊端:多个线程需要判断锁,较为消耗资源、抢锁的资源。

代码示例

public void sale() {
        // 前提 多线程进行使用、多个线程只能拿到一把锁。
        // 保证只能让一个线程 在执行 缺点效率降低
        synchronized (oj) {
            if (count > 0) {
                System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - count + 1) + "票");
                count--;
            }
        }
    }

运行效果

在这里插入图片描述

同步函数

在方法上修饰synchronized 称为同步函数

代码示例

 public synchronized void sale() {
        if (trainCount > 0) {
            try {
                Thread.sleep(40);
            } catch (Exception e) {
            }
            System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "张票.");
            trainCount--;
        }
    }

this锁(对象锁)和Static(类锁)

Mythread extends Thread
	synchronized (Mythread.class) {
	    //锁类  即此类的实例拥有共同的锁 即锁共有
	    //可以当成是类变量的感觉..所以对象共享
	}
	synchronized (this) {
    	//锁对象  即各实例都有各自的锁  
    	//无意义
	}
}

但如果是实现自runable接口则可以使用this
MyThread implements Runnable MyThread my =new MyThread();
Thread t = new Thread(my);
Thread t2 = new Thread(my);
Thread t3 = new Thread(my);
因创建线程对象不同 因为runable方式的几条线程是共享一个对象
查阅文档 JDK5以后有Lock锁可代替 可不知因何测后仍有同步问题…可能是方式不对
其实为了避免问题 俩种线程方式都可直接锁 xxx.class 即可

  • this 锁

class ClassA {
    public synchronized void A()
    {
        System.out.println("AAAAAAAAAAAAAAAAA");
        while (true)
        {
        }
    }
    public synchronized void B()
    {
        System.out.println("BBBBBBBBBBBBBBBBB");
        while (true)
        {
        }
    }
}
public class MethodSynchronizedTest {

    public static void main(String[] args) {
        final ClassA clazz = new ClassA();
        // 启动一个线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                clazz.A();// 调用A方法
            }
        }).start();
        // 启动另一个线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                clazz.B();// 调用B方法
            }
        }).start();
    }
}

运行结果

在这里插入图片描述

class TicketWindow3 implements Runnable {
    private int max_value = 0;
    private Object lock = new Object();
    private boolean flag = true;

    @Override
    public void run() {
        if (flag) {
            while (true) {
                //同步函数其实用到的锁就是 this 锁, this锁针对的是对象
                //静态锁,锁是类的字节码信息,因此如果一个类的函数为静态方法,那么我们需要通过该类的 class 信息进行加锁;
                synchronized (lock) {
                    if (max_value > 50) {
                        break;
                    }
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                    }
                    System.out.println(Thread.currentThread().getName() + ":lock..." + max_value++);
                }
            }
        } else {
            while (true) {
                if (ticket()) {
                    break;
                }
            }
        }
    }
    private synchronized boolean ticket() {
        if (max_value > 50) {
            return true;
        }
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
        }
        System.out.println(Thread.currentThread().getName() + ": method.." + max_value++);
        return false;
    }
    public void change() throws InterruptedException {
        Thread.sleep(30);// 读者可以自行思考为什么要sleep
        this.flag = false;
    }
}
public class Bank3 {

    public static void main(String[] args) throws InterruptedException {
        TicketWindow3 tw3 = new TicketWindow3();
        Thread t1 = new Thread(tw3);
        Thread t2 = new Thread(tw3);
        t1.start();
        tw3.change();
        t2.start();
    }
}

在这里插入图片描述

文字说明: 可能到输出性信息,其中会有 51 这样的信息输出,为什么会这样呢?因为上述的代码 两处业务逻辑同步锁是两把锁,如果您将lock 换成 this,这个现象就不会出现

  • static 锁

class TicketWindow4 implements Runnable {
    private static int max_value = 0;
    private boolean flag = true;
    @Override
    public void run() {
        if (flag) {
            while (true) {
                //同步函数其实用到的锁就是 this 锁, this锁针对的是对象
                //静态锁,锁是类的字节码信息,因此如果一个类的函数为静态方法,那么我们需要通过该类的 class 信息进行加锁;
                synchronized (TicketWindow4.class) {
                    if (max_value > 50) {
                        break;
                    }
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                    }
                    System.out.println(Thread.currentThread().getName() + ":lock..." + max_value++);
                }
            }
        } else {
            while (true){
                if (ticket()){
                    break;}}
        }
    }
    private synchronized static boolean ticket() {
        if (max_value > 50) {
            return true;
        }
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
        }
        System.out.println(Thread.currentThread().getName() + ": method.." + max_value++);
        return false;
    }
    public void change() throws InterruptedException {
        Thread.sleep(30);// 读者可以自行思考为什么要sleep
        this.flag = false;
    }
}
public class Bank4 {
    public static void main(String[] args) throws InterruptedException {
        TicketWindow4 tw3 = new TicketWindow4();
        Thread t1 = new Thread(tw3);
        Thread t2 = new Thread(tw3);
        t1.start();
        tw3.change();
        t2.start();
    }
}

运行结果

在这里插入图片描述

两种锁同时使用

public class TestSynchronized {
    public synchronized void test1() {
        int i = 5;
        while (i-- > 0) {
            System.out.println(Thread.currentThread().getName() + " : " + i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException ie) {
            }
        }
    }
    public static synchronized void test2() {
        int i = 5;
        while (i-- > 0) {
            System.out.println(Thread.currentThread().getName() + " : " + i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException ie) {
            }
        }
    }
    public static void main(String[] args) {
        final TestSynchronized myt2 = new TestSynchronized();
        Thread test1 = new Thread(new Runnable() {
            @Override
            public void run() {
                myt2.test1();
            }
        }, "test1");
        Thread test2 = new Thread(new Runnable() {
            @Override
            public void run() {
                TestSynchronized.test2();
            }
        }, "test2");
        test1.start();
        test2.start();
    }
}

在这里插入图片描述

上面代码synchronized同时修饰静态方法和实例方法,但是运行结果是交替进行的,这证明了类锁和对象锁是两个不一样的锁,控制着不同的区域,它们是互不干扰的。同样,线程获得对象锁的同时,也可以获得该类锁,即同时获得两个锁,这是允许的。
结论:
A: synchronized static是某个类的范围,synchronized static cSync{}防止多个线程中多个实例同时访问这个 类中的synchronized static 方法。它可以对类的所有对象实例起作用。
B: synchronized 是某实例的范围,synchronized isSync(){}防止多个线程中这一个实例同时访问这个类的synchronized 方法。

多线程死锁

同步中嵌套同步,导致锁无法释放

package com.flyfish.thread.test;

class ThreadTrain6 implements Runnable {
    // 这是货票总票数,多个线程会同时共享资源
    private int trainCount = 100;
    public boolean flag = true;
    private Object mutex = new Object();

    @Override
    public void run() {
        if (flag) {
            while (true) {
                synchronized (mutex) {

                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // 锁(同步代码块)在什么时候释放? 代码执行完, 自动释放锁.
                    // 如果flag为true 先拿到 obj锁,在拿到this 锁、 才能执行。
                    // 如果flag为false先拿到this,在拿到obj锁,才能执行。
                    // 死锁解决办法:不要在同步中嵌套同步。
                    sale();
                }
            }
        } else {
            while (true) {
                sale();
            }
        }
    }

    /**
     * @methodDesc: 功能描述:(出售火车票)
     * @author: 余胜军
     * @param:
     * @createTime:2017年8月9日 下午9:49:11
     * @returnType: void
     * @copyright:上海每特教育科技有限公司
     */
    public synchronized void sale() {
        synchronized (mutex) {
            if (trainCount > 0) {
                System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "张票.");
                trainCount--;
            }
        }
    }
}

public class DeadlockThread {

    public static void main(String[] args) throws InterruptedException {

        ThreadTrain6 threadTrain = new ThreadTrain6(); // 定义 一个实例
        Thread thread1 = new Thread(threadTrain, "一号窗口");
        Thread thread2 = new Thread(threadTrain, "二号窗口");
        thread1.start();
       Thread.sleep(40);
        threadTrain.flag = false;
        thread2.start();
    }
}

线程一先启动 synchronized (mutex)锁定 mutex 对象 然后主线程休眠 flag=false 线程二启动 synchronized void sale()锁定当前对象两个线程各获得一把锁,线程死锁

多线程有三大特性

1. 是原子性

即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
一个很经典的例子就是银行账户转账问题:
比如从账户A向账户B转1000元,那么必然包括2个操作:从账户A减去1000元,往账户B加上1000元。这2个操作必须要具备原子性才能保证不出现一些意外的问题。
我们操作数据也是如此,比如i = i+1;其中就包括,读取i的值,计算i,写入i。这行代码在Java中是不具备原子性的,则多线程运行肯定会出问题,所以也需要我们使用同步和lock这些东西来确保这个特性了。
原子性其实就是保证数据一致、线程安全一部分,

2. 是可见性

当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
若两个线程在不同的cpu,那么线程1改变了i的值还没刷新到主存,线程2又使用了i,那么这个i值肯定还是之前的,线程1对变量的修改线程没看到这就是可见性问题。

3. 有序性

程序执行的顺序按照代码的先后顺序执行。
一般来说处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。如下:
int a = 10; //语句1
int r = 2; //语句2
a = a + 3; //语句3
r = a*a; //语句4
则因为重排序,他还可能执行顺序为 2-1-3-4,1-3-2-4
但绝不可能 2-1-4-3,因为这打破了依赖关系。
显然重排序对单线程运行是不会有任何问题,而多线程就不一定了,所以我们在多线程编程时就得考虑这个问题了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值