Java之线程安全的解决方案

什么是线程安全

Java线程的两个特性 ,可见性有序性

  1. 线程之间不能直接传递数据进行交互,只能通过变量来实现数据交互。而当一个对象在多个工作内存中都存在它的复制体时,如果有一个方式使主内存中的这个变量改变时,那么其他所有的线程也得看到修改后的值,这就是可见性
  2. 那么什么是有序性呢? 拿银行出来说事,例如:一张银行卡里有一百块钱,甲去银行取100块钱,乙同时也去银行取100块钱,当他们俩同时取钱时,那甲取到100,乙取到100,那银行不久亏了100嘛,所以对于这种bug,就必须得设定当甲取钱时,其他人就不得操作银行卡,这便是有序性

线程安全:如果线程执行过程中不会产生共享资源的冲突就是线程安全的。

当多线程访问时,采用加锁机制,当一个线程访问该类的某个数据时,锁机制就会锁住其他线程,于是其他线程就不能进行访问直到该线程访问或操作完,其他线程才可使用。

解决线程不安全问题的几个方法

  1. 使用单例模式,局部变量不存在线程安全问题,因为不含实例变量,每个线程进入的方法会有单独的空间。
  2. 加上synchronized关键字同步,可以同步整个方法,也可以同步部分代码块。但是该方法是排他锁,性能不好。
  3. 使用Lock对象(tryLock,lock,unlock),可区分排他锁(写锁)和共享锁(读锁),提升性能。
  4. 使用读写锁,提升性能,可以一起读,但读的时候不能写写的时候不能读和写
  5. 使用本地线程ThreadLocal,为每个线程保存一份单独的空间,该方法相当于使用空间换时间
  6. 使用多实例,就是每个线程单独使用一个新的对象

synchronized的使用

 //  输出类
public class Printer {
    boolean start = true;
    public synchronized void printernum() throws InterruptedException {
        for (int i = 1; i <=52 ; i++) {
            System.out.print(i);
            if(i%2==0){
                start=false;
                this.notify();
                this.wait();
            }
//            Thread.sleep(500);
        }
    }
    public synchronized void printerword() throws InterruptedException { //加上了synchronized关键字便加上了锁
        while (start) this.wait();
        for (int i =65; i <=90 ; i++) {
            System.out.print((char) i);
            this.notify();
            if(i!=90)this.wait();
//            Thread.sleep(500);
        }
    }
}

 //  测试类
public class Test {
    public static void main(String[] args) {
        Printer printer = new Printer();
        Runnable r1 = () ->{
            try {
                printer.printernum();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        Runnable r2 = () ->{
            try {
                printer.printerword();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        new Thread(r1).start();
        new Thread(r2).start();
    }
}

synchronized称为互斥同步锁,也叫阻塞同步锁,能对没有获取锁的线程阻塞

Java的互斥同步锁是synchronizedReentrantLock
前者是语言级别实现的锁,后者是API层面的锁;synchronized是jvm实现的,遇到异常就会释放锁,但是ReentrantLock必须由程序主动释放锁,比如程序必须要在finally中关闭锁,比synchronized更灵活。
互斥同步锁都是可重入锁,它保证不会造成死锁,但很消耗性能

  1. synchronized可以锁局部代码块
  2. synchronized写在实例方法上,默认的锁对象为this,当一个对象的实例方法使用了synchronized,那么这个对象的其他synchronized方法也同样加了this锁,其他线程也同样需要等待锁释放才能运行这些synchronized方法。
  3. synchronized写在静态方法上,默认的锁对象为该方法的类的字节码,其他使用该字节码同步锁的方法,需要等待该字节码锁被释放。

Lock对象的使用

// 输出类
public class PrinterName {
    Lock lock = new ReentrantLock();
    public  void sayname(){
        try {
            if(lock.tryLock(5, TimeUnit.SECONDS)) {
                System.out.println("name.. start.." + Thread.currentThread().getName());
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            } else {
                System.out.println("name的else" + Thread.currentThread().getName());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public void sayname2() {
        try {
            // 尝试获取锁,获取不到等待5S. 5S后没获取到就返回false
            if (lock.tryLock(5, TimeUnit.SECONDS)) {
                System.out.println("name22222222.. start.." + Thread.currentThread().getName());
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            } else {
                System.out.println("name2222的else" + Thread.currentThread().getName());
        }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

  //  同步锁线程类
public class MysynchronzedThread implements Runnable{

    PrinterName printerName;
    public MysynchronzedThread(PrinterName printerName){
        this.printerName =printerName;
    }
    @Override
    public void run() {
        while (true){
            if((((int)(Math.random() * 100)) % 2) == 0){
                printerName.sayname();
            }else{
                printerName.sayname2();
            }
        }
    }
}

 // 测试类
public class Test {
    public static void main(String[] args) {
        PrinterName printerName = new PrinterName();
        MysynchronzedThread thread1 = new MysynchronzedThread(printerName);
        MysynchronzedThread thread2 = new MysynchronzedThread(printerName);
        Thread threadA = new Thread(thread1,"A");
        Thread threadB = new Thread(thread2,"B");
        //threadA.setPriority(Thread.MAX_PRIORITY - 1);
        //threadB.setPriority(Thread.MAX_PRIORITY);
        threadA.start();
        threadB.start();
    }
}

读写锁的使用

  //  读写锁线程类
public class MyReadwriterThread extends Thread{
    private ReadWriter readWriter;
    public MyReadwriterThread(String threadName, ReadWriter readWriter){
        super(threadName);
        this.readWriter =readWriter;
    }
    public void run(){
        while (true){
            int num = (int) (Math.random()*100);
            if(num%2==0) readWriter.readName();
            else readWriter.writerName();
        }
    }
}

//  方法
public class ReadWriter {
    ReadWriteLock lock = new ReentrantReadWriteLock();
    String name;
    public void readName(){
        // 获取这个锁的读锁
        Lock readlock = this.lock.readLock();
        readlock.lock();
        System.out.println(Thread.currentThread().getName() + "进入read:" + name);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "准备结束read:" + name);
        // 释放读锁
        readlock.unlock();
    }
    public void writerName(){
        Lock writeLock = this.lock.writeLock();
        writeLock.lock();
        String randomname = (char)((int)(Math.random()*100))+" ";
        System.out.println(Thread.currentThread().getName() + "正在赋值..准备赋值为:" + randomname);
        this.name = randomname;
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "赋值完毕,结果为:" + name);
        writeLock.unlock();
    }
}

  // 测试类
public class Test {
    public static void main(String[] args) {
        ReadWriter readWriter = new ReadWriter();
        MyReadwriterThread thread1 = new MyReadwriterThread("a线程",readWriter);
        MyReadwriterThread thread2 = new MyReadwriterThread("b线程",readWriter);
        thread1.start();
        thread2.start();
    }
}

本地线程ThreadLocal的使用

 //  本地线程类
public class MyprinterThread implements Runnable{
    //本地线程,作用于死锁的解决
    public static ThreadLocal<String> threadLocal = new ThreadLocal<>();
    private String username;
    private Printer printer;

    public MyprinterThread(String username, Printer printer) {
        this.username = username;
        this.printer = printer;
    }
    public void run(){
        for (int i = 0; i < 100; i++) {
           if(threadLocal.get()==null){
               threadLocal.set(username);
           }
           printer.printerName();

        }
    }
}

  // 输出方法 的类
public class Printer {
    String name;
    int age;
    int gender;

    public void printerName(){
        File file = new File("d:"+File.separator+name+".txt");
        try(OutputStream os = new FileOutputStream(file,true)){
            if(!file.exists()) file.createNewFile();
            for (int i = 0; i < name.length(); i++) {
                os.write(name.charAt(i));
            }
            os.write("\r\n".getBytes() );
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

   //  测试类  在文件中添加数据
public class TestPrinter {
    public static Integer num = 10;
    public static void main(String[] args) {
        Printer printer = new Printer();
        MyprinterThread a = new MyprinterThread("aaaaaaaaaaaaaaaaa", printer);
        MyprinterThread b= new MyprinterThread("bbbbbbbbbbbbbbbbbbb",printer );
        new Thread(a).start();
        new Thread(b).start();
    }
}

什么是死锁? 怎么解决死锁?

同步(synchronization)是指当一个线程访问数据时,其它线程就不能对同一个数据进行访问,即同一时刻只能有一个线程访问该数据。
同步最常见的方式就是使用锁,也就是
线程锁
。锁是一种非强制机制,每一个线程在访问数据或资源之前,首先试图获取锁,并在访问结束之后释放锁。在锁被占用时试图获取锁,线程会进入等待状态,直到锁被释放再次变为可用。

死锁就是当两个或多个线程直接相互等待彼此释放锁时,就会造成死锁。

死锁产生的四个必要条件:
1、互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
2、不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
3、请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
4、循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。

解决方法 :
1.使用Lock的tryLock,一段时间获取不到就执行unlock释放
2.synchronized使用同一个锁对象
3.使用ThreadLocal

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值