线程安全问题举例

1.安全问题举例

在这里插入图片描述
在这里插入图片描述

1.1 介绍

  • 1.死锁
    多个线程互相占用对方资源,互不释放,一直等待
    检查工具:jconsole

  • 2.饥饿
    涉及线程优先级,就是高级别的线程一直在执行,低级别的线程一直得不到资源来执行

  • 3.活锁
    比如两个线程,都需要用到某个资源的时候,看到互相都需要,就各自退出来继续等待,当正要获取资源的时候,看到对方也正需要资源,于是又退出来继续等待,反反复复这样下去

1.2 多个线程操作一个对象,一个对象操作一个资源

public class MyNumber {
    private Integer num;
    
    public MyNumber() {
        num = 10;
    }
    public MyNumber(Integer num) {
        this.num = num;
    }
    public Integer getNum() {
        return num;
    }
    public void setNum(Integer num) {
        this.num = num;
    }
}
public class UserManagerSingle {
    private MyNumber num;

    public UserManagerSingle() {
        this.num = new MyNumber();
    }
    public Integer getNumFromMyNumber(){

        Integer tmp = num.getNum();
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        num.setNum(tmp - 1);
        return tmp;
    }

}
public class MyThread extends Thread{
    private UserManagerSingle user;

    public MyThread(String name, UserManagerSingle user) {
        super(name);
        this.user = user;
    }
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("线程名:【"+this.getName() +"】  获取序号:"+user.getNumFromMyNumber() + "  时间:"+System.currentTimeMillis());
        }
    }
}
public class TestThread {
    public static void main(String[] args) {

        testSingle();

    }

    public static void testSingle(){

        UserManagerSingle userManagerSingle = new UserManagerSingle();
        //下面模拟三个线程,同时访问一个后台应用类
        new MyThread("t1",userManagerSingle).start();
        new MyThread("t2",userManagerSingle).start();
        new MyThread("t3",userManagerSingle).start();

    }
}

在这里插入图片描述

1.3 多个线程操作多个对象,多个对象操作一个资源

2.同步

2.1 同步块

注意:任何时刻只有一条线程可以或得同步监视器的锁定,当同步代码块执行结束后,该线程自然释放了对该同步监视器的锁定
通常推荐使用可能被并发访问的共享资源充当同步监视器

  • 1.控制整个方法(除此以外其他类代码不变)
public class MyThread extends Thread{
    private UserManagerSingle user;

    public MyThread(String name, UserManagerSingle user) {
        super(name);
        this.user = user;
    }
    @Override
    public void run() {
        synchronized (user){
            for (int i = 0; i < 5; i++) {
                System.out.println("线程名:【"+this.getName() +"】  获取序号:"+user.getNumFromMyNumber() + "  时间:"+System.currentTimeMillis());
            }
        }
    }
}

如上,
1.加锁是用user这个对象来加锁的,因为user是共享资源,所有线程都会访问这个类,若这个类对象不释放锁,那么所有线程将等待
2.注意synchronized 是加载for之前,那么就表示必须等待当前这个线程所有操作全部执行完后,才能开始下一个线程
在这里插入图片描述

  • 2.控制方法中部分代码(除此以外其他类代码不变)
public class MyThread extends Thread{
    private UserManagerSingle user;

    public MyThread(String name, UserManagerSingle user) {
        super(name);
        this.user = user;
    }
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            synchronized (user){
                System.out.println("线程名:【"+this.getName() +"】  获取序号:"+user.getNumFromMyNumber() + "  时间:"+System.currentTimeMillis());
            }
        }
    }
}

如上,
synchronized 存放在for之内,那么当循环执行过一次后,会释放锁,这时其他等待的线程就有机会获得锁而进行各自线程里的操作,这样就不只单单先执行完一个线程的事了,而是相互竞争执行
在这里插入图片描述

2.2 同步方法

同步方法无需显示指定同步监视器,同步方法监视器是this,即就是对象本身自己
若修饰静态方法,那么同步锁的对象是class对象
任何对象都可以作为锁,那么锁信息是存在对象头中
(除此以外其他类代码不变)

public class MyThread extends Thread{
    private UserManagerSingle user;

    public MyThread(String name, UserManagerSingle user) {
        super(name);
        this.user = user;
    }
    @Override
    public void run() {
        print();
    }
    //这样写同步方法,是不会实现同步功能的,因为每个MyThread对象是不一样的
    public synchronized void print(){
        for (int i = 0; i < 5; i++) {
            System.out.println("线程名:【"+this.getName() +"】  获取序号:"+user.getNumFromMyNumber() + "  时间:"+System.currentTimeMillis());
        }

    }
}

在这里插入图片描述
由上可知,因为每次都会new MyThread(“t1”,userManagerSingle).start();则在MyThread中同步方法的监视器this都是不一样的,都是指向新生成的不同对象,所以并不能实现同步功能
同步方法要写到同步资源本身类里即可,如下:

public class MyThread extends Thread{
    private UserManagerSingle user;

    public MyThread(String name, UserManagerSingle user) {
        super(name);
        this.user = user;
    }
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            user.printNum();
        }
    }
}
public class UserManagerSingle {
    private MyNumber num;

    public UserManagerSingle() {
        this.num = new MyNumber();
    }
    public Integer getNumFromMyNumber(){

        Integer tmp = num.getNum();
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        num.setNum(tmp - 1);
        return tmp;
    }
    //同步资源这里写同步方法
    public synchronized void printNum(){
        System.out.println("线程名:【"+Thread.currentThread().getName() +"】  获取序号:"+getNumFromMyNumber() + "  时间:"+System.currentTimeMillis());
    }
}

在这里插入图片描述

2.3 同步锁

2.3.1 可重入锁ReentrantLock

(除此以外其他类代码不变)

public class MyThread extends Thread{
    private UserManagerSingle user;
    //lock写在这里并不能实现线程同步,因为MyThread是不同类,而lock也会生成不同对象,并不唯一
    private final ReentrantLock lock = new ReentrantLock();

    public MyThread(String name, UserManagerSingle user) {
        super(name);
        this.user = user;
    }
    @Override
    public void run() {
        try {
            lock.lock();
            for (int i = 0; i < 5; i++) {
                System.out.println("线程名:【"+this.getName() +"】  获取序号:"+user.getNumFromMyNumber() + "  时间:"+System.currentTimeMillis());
            }
        }catch (Exception e){
            System.out.println("e = " + e);
        }finally {
            lock.unlock();
        }

    }
}

在这里插入图片描述

lock必须在多个线程中保持唯一,才能控制同步,所以Lock一般也是放在共享资源里创建的,和同步方法比较像,如下:

public class MyThread extends Thread{
    private UserManagerSingle user;

    public MyThread(String name, UserManagerSingle user) {
        super(name);
        this.user = user;
    }
    @Override
    public void run() {
        user.printNum();
    }
}
public class UserManagerSingle {
    private MyNumber num;
    //共享资源处声明Lock
    private final ReentrantLock lock = new ReentrantLock();

    public UserManagerSingle() {
        this.num = new MyNumber();
    }
    public Integer getNumFromMyNumber(){

        Integer tmp = num.getNum();
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        num.setNum(tmp - 1);
        return tmp;
    }

    //同步Lock
    public void printNum(){
        try {
            lock.lock();
            for (int i = 0; i < 5; i++) {
                System.out.println("线程名:【"+Thread.currentThread().getName() +"】  获取序号:"+getNumFromMyNumber() + "  时间:"+System.currentTimeMillis());
            }
        }catch (Exception e){
            System.out.println("e = " + e);
        }finally {
            lock.unlock();
        }
    }

}

在这里插入图片描述

2.4 原子类操作

public class TestThread {

    public static void main(String[] args) {
        testSingle();
    }

    public static void testSingle(){
        UserManagerSingle userManagerSingle = new UserManagerSingle(new MyNumber());
        //下面模拟三个线程,同时访问一个后台应用类
        new MyThread("t1",userManagerSingle).start();
        new MyThread("t2",userManagerSingle).start();
        new MyThread("t3",userManagerSingle).start();
    }

}
public class MyThread extends Thread{
    private UserManagerSingle user;

    public MyThread(String name, UserManagerSingle user) {
        super(name);
        this.user = user;
    }
    @Override
    public void run() {
        user.getNumFromMyNumber();
    }
}
public class UserManagerSingle {
    private MyNumber num;

    public UserManagerSingle(MyNumber num) {
        this.num = num;
    }
    public void getNumFromMyNumber(){
        for (int i = 0; i < 5; i++) {
            System.out.println("线程名:【"+Thread.currentThread().getName() +"】  获取序号:"+ num.getNum() + "  时间:"+System.currentTimeMillis());
        }
    }
}
public class MyNumber {
    //增加原子类操作
    private AtomicInteger num = new AtomicInteger(0);

    public MyNumber() {
    }
    public Integer getNum() {
        return num.getAndIncrement();
    }
}

在这里插入图片描述

3.锁

3.1 可重入锁

广义上的可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁
ReentrantLock和synchronized都是可重入锁

public class MyReentrantLock {
    public synchronized void a(){
        System.out.println("线程:" + Thread.currentThread().getName()+" 执行a()" + " 当前实例"+this.hashCode());
        b();
    }

    public synchronized void b(){
        System.out.println("线程:" + Thread.currentThread().getName()+" 执行b()" + " 当前实例"+this.hashCode());
    }

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                MyReentrantLock rl = new MyReentrantLock();
                rl.a();
            }
        }).start();
    }
}

在这里插入图片描述
总结:
1.有上可知,虽然a和b方法的锁都在同一个对象spinLock上,但是一个线程调用该对象a方法后,依然还能调用该对象的b方法

3.2 自旋锁

自旋锁可以使线程在没有取得锁的时候,不被挂起,而转去执行一个空循环,(即所谓的自旋,就是自己执行空循环),若在若干个空循环后,线程如果可以获得锁,则继续执行。若线程依然不能获得锁,才会被挂起。

使用自旋锁后,线程被挂起的几率相对减少,线程执行的连贯性相对加强。因此,对于那些锁竞争不是很激烈,锁占用时间很短的并发线程,具有一定的积极意义,但对于锁竞争激烈,单线程锁占用很长时间的并发程序,自旋锁在自旋等待后,往往毅然无法获得对应的锁,不仅仅白白浪费了CPU时间,最终还是免不了被挂起的操作 ,反而浪费了系统的资源。

public class SpinLock {

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(new Random().nextInt(1000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程:" + Thread.currentThread().getName()+" 执行" + " 当前实例"+this.hashCode());
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(new Random().nextInt(2000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程:" + Thread.currentThread().getName()+" 执行" + " 当前实例"+this.hashCode());
            }
        }).start();

        getAllRunThread();

        //这里必须是2,因为java运行会多一个Monitor Ctrl-Break线程生成
        while(Thread.activeCount() != 2){
            //自旋
        }
        System.out.println("所有线程执行完");
    }

    //获取当前线程下所有活动线程数
    public static void getAllRunThread(){
        ThreadGroup currentGroup =
                Thread.currentThread().getThreadGroup();
        int noThreads = currentGroup.activeCount();
        Thread[] lstThreads = new Thread[noThreads];
        currentGroup.enumerate(lstThreads);
        for (int i = 0; i < noThreads; i++)
            System.out.println("线程号:" + i + " = " + lstThreads[i].getName());

    }

}

在这里插入图片描述
总结:
1.自旋就是当该线程没有取得锁时,不停执行空循环,知道后面获取后再执行下一步

3.3 死锁

所谓死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。

public class MyDeadLock {
    Object o1 = new Object();
    Object o2 = new Object();
    
    public void a() throws InterruptedException {
        synchronized (o1){//获取o1锁
            Thread.sleep(100);
            synchronized (o2){
                System.out.println("执行a()方法!!");
            }
        }
    }
    public void b() throws InterruptedException {
        synchronized (o2){//获取O2锁
            Thread.sleep(100);
            synchronized (o1){
                System.out.println("执行a()方法!!");
            }
        }
    }

    public static void main(String[] args) {
        final MyDeadLock myDeadLock = new MyDeadLock();
        //线程1
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    myDeadLock.a();//这里获取了o1锁,但o2锁并未获取
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        //线程2
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    myDeadLock.b();//这里获取了o2锁,但o1锁并未获取
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

    }

}

总结:
1.由上可知,一个线程获取o1锁,一个线程获取o2锁,但是他们都没有彼此释放自己获取的锁,故一直等待

3.4 Volatile

1.Volatile称之为轻量级锁,被volatile修饰的变量,在线程之间都是可见的
2.一个线程修改了这个变量的值,那么在其他所有线程中都能够读取到这个修改后的值
3.Synchronized除了线程之间互斥以外,还有一个非常大的作用,就是保证可见性
一个可见性的典型应用

public class MyVolatile {

    public volatile boolean run = false;


    public static void main(String[] args) {
        final MyVolatile myVolatile = new MyVolatile();
        //线程1
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    try {
                        Thread.sleep(1000);
                        System.out.println("i = " + i+" 当前线程"+Thread.currentThread().getName());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                myVolatile.run = true;
            }
        }).start();
        //线程2
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (!myVolatile.run){
                    //当run没有为true时,一直等待
                }
                System.out.println("现在才运行执行这个线程"+Thread.currentThread().getName());
            }
        }).start();

    }
}

在这里插入图片描述

4. 通讯

场景:
启动三个线程,让他们依次顺序执行各自的方法

4.1 wait与notify

public class MyWaitNotify {

    private int signal;


    public synchronized void a(){
        if(signal != 0){//=0的时候才执行
            try {
                wait();//用wait必须要加synchronized,notify()和notifyAll()的线程在调用这些方法前必须"拥有"对象的锁
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("执行方法a()");
        signal++;
        notifyAll();//notify()不能用,因为这个是随机唤醒某个线程
    }

    public synchronized void b(){
        if(signal != 1){//=1,才执行
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("执行方法b()");
        signal++;
        notifyAll();
    }

    public synchronized void c(){
        if(signal != 2){//=2才执行
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("执行方法c()");
        signal = 0;
        notifyAll();
    }

    public static void main(String[] args) {

        MyWaitNotify mwn = new MyWaitNotify();

        A a = new A(mwn);
        B b = new B(mwn);
        C c = new C(mwn);

        new Thread(a).start();
        new Thread(b).start();
        new Thread(c).start();

    }



}

class A implements Runnable{

    MyWaitNotify wn;

    public A(MyWaitNotify wn) {
        this.wn = wn;
    }

    @Override
    public void run() {

        while (true){
            wn.a();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

class B implements Runnable{

    MyWaitNotify wn;

    public B (MyWaitNotify wn) {
        this.wn = wn;
    }

    @Override
    public void run() {

        while (true){
            wn.b();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

class C implements Runnable{

    MyWaitNotify wn;

    public C (MyWaitNotify wn) {
        this.wn = wn;
    }

    @Override
    public void run() {

        while (true){
            wn.c();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

在这里插入图片描述

4.2 condition

public class MyWaitNotify {

    private int signal;

    Lock lock = new ReentrantLock();
    Condition a = lock.newCondition();
    Condition b = lock.newCondition();
    Condition c = lock.newCondition();


    public void a(){
        lock.lock();
        if(signal != 0){//=0的时候才执行
            try {
                a.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("执行方法a()");
        signal++;
        b.signal();
        lock.unlock();
    }

    public void b(){
        lock.lock();
        if(signal != 1){
            try {
                b.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("执行方法b()");
        signal++;
        c.signal();
        lock.unlock();
    }

    public void c(){
        lock.lock();
        if(signal != 2){
            try {
                c.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("执行方法c()");
        signal = 0;
        a.signal();
        lock.unlock();
    }

    public static void main(String[] args) {

        MyWaitNotify mwn = new MyWaitNotify();

        A a = new A(mwn);
        B b = new B(mwn);
        C c = new C(mwn);

        new Thread(a).start();
        new Thread(b).start();
        new Thread(c).start();

    }

}

class A implements Runnable{

    MyWaitNotify wn;

    public A(MyWaitNotify wn) {
        this.wn = wn;
    }

    @Override
    public void run() {

        while (true){
            wn.a();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

class B implements Runnable{

    MyWaitNotify wn;

    public B (MyWaitNotify wn) {
        this.wn = wn;
    }

    @Override
    public void run() {

        while (true){
            wn.b();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

class C implements Runnable{

    MyWaitNotify wn;

    public C (MyWaitNotify wn) {
        this.wn = wn;
    }

    @Override
    public void run() {

        while (true){
            wn.c();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

在这里插入图片描述

5. ThreadLocal

每个线程的局部变量

public class MyThreadlocal {

    private ThreadLocal<Integer> count = new ThreadLocal<Integer>(){
        @Override
        protected Integer initialValue() {
            return new Integer(0);
        }
    };

    public Integer getNext(){
        Integer i = count.get();
        i++;
        count.set(i);
        return i;
    }

    public static void main(String[] args) {

        final MyThreadlocal myThreadlocal = new MyThreadlocal();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("线程:" + Thread.currentThread().getName() + "序号:" + myThreadlocal.getNext());
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("线程:" + Thread.currentThread().getName() + "序号:" + myThreadlocal.getNext());
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("线程:" + Thread.currentThread().getName() + "序号:" + myThreadlocal.getNext());
                }
            }
        }).start();

    }

}

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值