Java并发编程--基础(二)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/LuuvyJune/article/details/86479346

阅读《Java高并发编程详解》后的笔记。

线程安全与数据同步

上篇中,模拟营业大厅叫号机,程序设计最大号码是50,运行结果很正常,但是如果变成500,多次运行可能出现以下问题:

  • 某个号码略过没有出现
  • 某个号码多次显示
  • 号码超过了500
/**
 * 模拟营业大厅叫号机
 *
 */
public class TicketRunnable implements Runnable{

    private int index = 1;

    private final static int MAX = 500;

    @Override
    public void run() {

        while(index <= MAX){
            System.out.println(Thread.currentThread() + "的号码是" + (index++));
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String args[]){

        final TicketRunnable ticketRunnable = new TicketRunnable();

        Thread thread1 = new Thread(ticketRunnable,"一号窗口");

        Thread thread2 = new Thread(ticketRunnable,"二号窗口");

        Thread thread3 = new Thread(ticketRunnable,"三号窗口");

        Thread thread4 = new Thread(ticketRunnable,"四号窗口");

        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
    }
}

这里,产生了数据不同步的问题,其原因可结合JAVA内存模型以及CPU缓存知识来分析,这两部分内容单独进行记录。

一句话,产生以上这些情况,是因为多个线程对index(共享资源)同时操作引起的,解决这个问题需使用synchronized关键字。

synchronized

1、简介

synchronized可以实现一个简单的策略来防止线程干扰和内存一致性错误,如果一个对象对多个线程是可见的,那么对该对象的所有读或者写都将通过同步的方式来解决:

(1)提供一种锁机制,确保共享资源的互斥访问;

(2)包括monitor enter和 monitor exit两个JVM指令,保证任何时候任何线程执行到 monitor enter成功之前,必须从主内存获取数据,而不是缓存,monitor exit运行成功之后,共享变量被更新的值必须刷入主内存(JAVA内存模型以及CPU缓存知识);

(3)遵循java happens-before规则,一个monitor exit指令之前必定要有一个monitor enter(JAVA内存模型以及CPU缓存)。

2、用法

用于对代码块或方法进行修饰,不能对class以及变量进行修饰。

上面的代码使用synchronized,问题可以解决:

public class TicketRunnable implements Runnable{

    private int index = 1;

    private final static int MAX = 500;

    private final static Object MUTEX = new Object();

    @Override
    public void run() {
        synchronized (MUTEX){
            //获取与mutex相关联的monitor锁
            while(index <= MAX){
                System.out.println(Thread.currentThread() + "的号码是" + (index++));       
            }
        }
    }



3、原理

如果对上面的class文件进行反汇编,会出现monitor enter 和monitor exit指令。

(1)monitor enter

每个对象都与monitor关联,一个monitor的锁只能被一个线程在同一时间获得,一个线程尝试获得与对象关联的monitor所有权时会发生如下事情:

(2)monitor exit

当某个线程获得过与某个对象关联的monitor的所有权时,释放monitor的所有权就是将monitor的计数器减一。如果计数器结果为0,该线程就不再拥有对该monitor的所有权。

(3)This monitor 与class monitor

synchronized同步类的不同实例方法/静态方法,争抢的是同一个monitor的lock,与之关联的引用是This monitor/class monitor的实例引用。

public synchronized void method() -->this monitor

public static synchronized void methid() -->class monitor

4、使用synchronized 注意的问题

(1)与monitor关联的对象不能为空

private final  Object MUTEX = null;
    
    public void syncMethod(){
        
        synchronized (MUTEX){
            
        }
    }

每一个对象和一个monitor关联,对象为空,monitor无从谈起。

(2)作用域要适当

synchronized 尽量作用于共享资源的读写作用域,如果作用域越大,代表其效率越低,甚至失去并发优势。

(3)不同的monitor锁相同的方法

public class TicketRunnable implements Runnable{


    private final  Object MUTEX = new Object();


    @Override
    public void run() {
        //...
        synchronized (MUTEX){
           //...
        }
        //...
    }

    public static void main(String args[]){
        for (int i=0;i<5;i++){
            TicketRunnable ticketRunnable = new TicketRunnable();
            Thread tread = new Thread(ticketRunnable);
            tread.start();
        }
    }
}

上面的代码构造了5个Runnable的实例,Runnable作为线程执行单元传递给Thread,每一个线程争抢的monitor关联引用都是独立的,而线程之间进行monitor lock的争抢只能发生在与monitor关联的同一个引用上,所以上面synchronized不能起到互斥的作用。

(4)多个锁交叉导致死锁

private final Object MUTEX_READ = new Object();
    private final Object MUTEX_WRITE = new Object();
    
    public void read(){
        synchronized (MUTEX_READ){
            synchronized (MUTEX_WRITE){
                //...
            }
        }
    }
    
    public void write(){
        synchronized (MUTEX_WRITE) {
            synchronized (MUTEX_READ) {
                //...
            }
        }
    }

5、程序死锁

(1)交叉锁  哲学家吃面

(2)内存不足

(3)一问一答式的数据交换,服务端和客户端吧都在等待双方发送数据

(4)数据库锁  某线程执行 for update退出事务,其他线程访问死锁

(5)死循环引起 程序不工作,cpu占有率居高不下(死锁)

HashMap-->线程非安全

ConcurrentHashMap或者Collections.synchronizedMap-->线程安全

猜你喜欢

转载自blog.csdn.net/LuuvyJune/article/details/86479346
今日推荐