java多线程5


一、线程安全

多线程的执行顺序不可重现,但是必须要求执行结果必须可以重现

线程的共享数据操作不完整性就一定会出现数据被破坏,而导致结果无法预知的问题—线程的安全问题

同步处理的引入

在java语言中存在两种内建的synchronized语法:synchronized代码块和synchronized方法( 静态方法和非静态方法)可以解决线程安全问题

首先synchronized将并行改为串行,当然会影响程序的执行效率,执行速度会受到影响。其次synchronized操作线程的堵塞,也就是由操作系统控制CPU的内核进行上下文的切换,这个切换本身也是耗时的。所以使用synchronized关键字会降低程序的运行效率。

线程安全是并发编程中的重要关注点,应该注意到的是,造成线程安全问题的主要诱因有两点,一是存在共享数据(也称临界资源),二是存在多条线程共同操作共享数据

当存在多个线程操作共享数据时,需要保证同一时刻有且只有一个线程在操作共享数据,其他线程必须等到该线程处理完数据后再进行,这种方式有个名称叫互斥锁,即能达到互斥访问目的的锁,也就是说当一个共享数据被当前正在访问的线程加上互斥锁后,在同一个时刻,其他线程只能处于等待的状态,直到当前线程处理完毕释放该锁

  • 在Java中每个对象都有一个对象锁,其实就是对象头上的一个标记值而已
  • 同步处理的目标实际上就是实现线程排队执行的目的

写法1:同步方法
提供共享数据和同步方法的类

public class Test01 {
    private static int a;

    public synchronized void add() {
        a++;
        System.out.println(Thread.currentThread() + "  加  " + a);
    }

    public synchronized void sub() {
        a--;
        System.out.println(Thread.currentThread() + "  减  " + a);
    }
}

执行加法的线程类

public class Test02 extends Thread {
    private Test01 te;

    public Test02(Test01 te) {
        this.te = te;
    }
    public void run() {    
        for (int a = 0; a < 10; a++) {
        	try {
            	Thread.sleep(200);
        	}catch (Exception e){
            	e.printStackTrace();
        	}
            te.add();
        }
    }
}

执行减法的线程类

public class Test03 extends Thread {
    private Test01 te;
    public Test03(Test01 te){
        this.te=te;
    }

    @Override
    public void run() {
        for (int a =0;a<10;a++){
            try {
                Thread.sleep(200);
            }catch (Exception e){
                e.printStackTrace();
            }
            te.sub();
        }
    }
}

测试类

public class Test04 {
    public static void main(String[] args) {
        Test01 te =new Test01();
        new Test02(te).start();
        new Test03(te).start();
    }
}

原理:当一个线程在执行add方法时,其它线程不能执行add或者sub方法【同步方法都不能执行,因为是synchronized关键字会引入一个互斥锁,只有拥有锁的线程才能执行同步方法,其它线程只能阻塞等待】,synchronized属于重入锁,即当前线程可以执行其它的synchronized方法,但是其它线程不能执行当前对象中的synchronized方法,可以执行没有synchronized约束的方法

写法2:同步代码块

public class Test01 {
    private static int a;

    public void  add() {
        a++;
        System.out.println(Thread.currentThread() + "  加  " + a);
    }

    public  void sub() {
        a--;
        System.out.println(Thread.currentThread() + "  减  " + a);
    }
}

public class Test02 extends Thread {
    private Test01 te;

    public Test02(Test01 te) {
        this.te = te;
    }

    public void run() {
        synchronized (te){

            for (int a = 0; a < 10; a++) {
                try {
                    Thread.sleep(200);
                }catch (Exception e){
                    e.printStackTrace();
                }
                te.add();
            }
        }

    }
}

public class Test03 extends Thread {
    private Test01 te;
    public Test03(Test01 te){
        this.te=te;
    }

    @Override
    public void run() {
        synchronized (te){
            for (int a =0;a<10;a++){
                try {
                    Thread.sleep(200);
                }catch (Exception e){
                    e.printStackTrace();
                }
                te.sub();
            }
        }

    }
}

public class Test04 {
    public static void main(String[] args) {
        Test01 te =new Test01();
        new Test02(te).start();
        new Test03(te).start();
    }
}

写法3:同步静态方法

public class Test01 {
    private static int a;

    public static synchronized void add() {
        a++;
        System.out.println(Thread.currentThread() + "  加  " + a);
    }

    public static synchronized void sub() {
        a--;
        System.out.println(Thread.currentThread() + "  减  " + a);
    }
}

public class Test02 extends Thread {
    
    public void run() {
        for (int a = 0; a < 10; a++) {
            try {
                Thread.sleep(200);
            } catch (Exception e) {
                e.printStackTrace();
            }
            Test01.add();
        }
    }
}

public class Test03 extends Thread {

    @Override
    public void run() {

        for (int a = 0; a < 10; a++) {
            try {
                Thread.sleep(200);
            } catch (Exception e) {
                e.printStackTrace();
            }
            Test01.sub();
        }
    }
}

public class Test04 {
    public static void main(String[] args) {
        new Test02().start();
        new Test03().start();
    }
}

在方法上添加synchronized关键字后就可以保证在一个时刻上只有一个线程在调用某个方法【锁只能有一个】,不会出现并发的情形,达到排队执行的效果。

  • 在Java中synchronized可保证在同一个时刻,只有一个线程可以执行某个方法或者某个代码块(主要是对方法或者代码块中存在共享数据的操作),同时还应该注意到synchronized另外一个重要的作用,synchronized可保证一个线程的变化(主要是共享数据的变化)被其他线程所看到(保证可见性,完全可以替代volatile功能),这点确实也是很重要的。
  • 常见的4种Java线程锁:原子量AtomicInteger、信号量Semaphone、同步处理synchronized和重入锁ReentrantLock
  • jdk6之前是重量级锁,JDK6开始优化为锁的状态总共有四种,无锁状态(没有synchronized)、偏向锁、轻量级锁和重量级锁。锁状态的改变是根据竞争激烈程度进行的,在几乎无竞争的条件下,会使用偏向锁,在轻度竞争的条件下,会由偏向锁升级为轻量级锁, 在重度竞争的情况下,会升级到重量级锁。 随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁,但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级

synchronized的三种应用:对象锁、类锁和同步块

注意synchronized不能修饰构造器、变量、内部类等

对象锁

修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁

方法上添加synchronized就叫做同步方法,例如public synchronized void draw(double amount){}

在一个类对象中所有的同步方法是互斥的

  • 只要有一个线程进行了当前类对象(只能一个对象)的同步方法,则不允许其它线程在进入当前这个对象的任何同步方法,但是允许进入非同步方法
  • 同样当前线程则可以进入当前类对象的其它同步方法,也允许进入非同步方法,当线程进入同步方法,则获取同步锁,离开同步方法则释放同步锁
  • 这个锁就是当前类对象

这种方法不是最佳选择,因为这里的同步处理颗粒度太大了(所有当前对象中的同步处理方法都是互斥的),会影响并发性

线程安全的类:是通过使用同步方法的类,同步监视器是this

  • 该类的对象可以被多个线程安全地访问
  • 每个线程调用该对象的任意方法之后都将得到正确结果
  • 每个线程调用该对象的任意方法之后,该对象状态依然保持合理状态

同步静态方法

修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁

同步监视器是当前类对象Class c=Date.class

同步方法块

上面的两种方式比较死板,普通方法同步是以当前对象作为锁,静态方法同步是以当前类对象作为锁

所以引入更为灵活的方式:同步块,将锁对象作为参数进行传递

synchronized(account){}同步监视器可以阻止多个线程对同一个共享资源的并发访问,任何时刻只有一个线程可以获得对同步监视器的锁定,当同步代码块执行完成后,该线程会释放堆该同步监视器的锁定。

synchronized底层语义原理

对于synchronized语句当Java源代码被javac编译成bytecode的时候,会在同步块的入口位置和退出位置分别插入monitorenter和monitorexit字节码指令。而synchronized方法则会被翻译成普通的方法调用和返回指令,在VM字节码层面并没有任何特别的指令来实现被synchronized修饰的方法,而是在Class文件的方法表中将该方法的access_flags字段中的synchronized标志位置1,表示该方法是同步方法并使用调用该方法的对象或该方法所属的Class在JVM的内部对象表示Class做为锁对象

使用synchronized加锁的字节码会出现monitorenter和monitorexit两个指令,可以理解为代码块执行前的加锁和退出同步时的解锁

  • 执行monitorenter指令时,线程会为锁对象关联一个ObjectMonitor对象。
  • 线程遇到synchronized同步时,先会进入ObjectMonitor对象的EntryList队列中,然后尝试把ObjectMonitor对象的owner变量设置为当前线程,同时ObjectMonitor对象的monitor中的计数器count加1,即获得对象锁。否则通过尝试自旋一定次数加锁,失败则进入ObjectMonitor对象的cxq队列阻塞等待
  • synchronized是可重入,非公平锁,因为entryList的线程会先自旋尝试加锁,而不是加入cxq排队等待,不公平

二、生产者消费者模式

public class Work01 {
    //同步资源(仓库)
    private volatile Object da;

    //生产
    public synchronized void add(Object date) {
        while (da != null) {
            try {
                wait();         //不为空则说明仓库不为空,不生产
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        this.da = date;
        notifyAll();
        System.out.println("生产了一个日期:" + date);
    }

    //消费
    public synchronized void sub() {
        while (da == null) {
            try {
                wait();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        System.out.println("消费了一个日期:" + da);
        da = null;
        notifyAll();

    }
    
}

public class Work02 extends Thread {
    private Work01 wo;

    public Work02(Work01 wo) {
        this.wo = wo;
    }

    public void run() {
        for (int a = 0; a < 10; a++) {
            Object obj =new Date();
            wo.add(obj);
        }
    }
}

public class Work03 extends Thread {
    private Work01 wo;

    public Work03(Work01 wo) {
        this.wo = wo;
    }

    public void run() {
        for (int a = 0; a < 10; a++) {
            wo.sub();
        }
    }
}

public class Work04 {
    public static void main(String[] args) {
        Work01 wo =new Work01();
        new Work02(wo).start();
        new Work03(wo).start();

    }
}

效果为:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值