欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java之线程安全

程序员文章站 2022-07-14 10:40:43
...

什么是线程安全

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

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

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

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

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

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

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

相关标签: 线程安全

上一篇: throw 与throws

下一篇: throws与throw