初阶多线程安全问题(I)

了解:线程有用户线程(非守护线程)和守护线程(后台线程)

  • 问题引入:有一个COUNT = 0 变量,同时启动20个 线程,每个线程执行1000次,每次循环COUNT++,等这20个子线程执行完毕后,再main线程打印COUNT(预期2000),但是打印结果却总是小于预期值,这就涉及到线程安全问题。
public class UnsafeThread {
    private static int COUNT = 0;
    public static void main(String[] args) throws InterruptedException {
        //有一个COUNT = 0 变量,同时启动20个 线程,每个线程执行1000次,每次循环COUNT++
        //等这20个子线程执行完毕后,再main线程打印COUNT(预期2000)
        Thread[] threads = new Thread[20];
        for (int i = 0; i < 20;i++) {
            threads[i] = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 1000;j++) {
                        COUNT++;
                    }
                }
            });
        }
        for (Thread t : threads) {
            t.start();
        }
        //让main线程阻塞等待所有的20个线程执行完毕后再打印COUNT
        for (Thread t : threads) {
            t.join();
        }
        System.out.println(COUNT);
        //执行结果每次都是小于20000
    }
}

线程安全概念:如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境应该的结果,则说明这个程序线程安全。

线程不安全的原因:

1. 原子性(不能切分的最小单位)
多行指令(java某些代码,看着是一行,其实会分解为多条指令执行),如果代码行前后有依赖关系,不能插入其他影响我执行结果的指令(存在线程共享变量),如果能够插入就没有原子性,反之则有。


例1:methodA(new 对象()).menthidB() ---->methodA的返回对象调用methodB–》看着是一行,其实多行
例2:特殊的一行代码:(1)n++,n–,++n,–n,分解为三条指令 (2) new 对象:分解为三条指令


2. 可见性系统调度cpu执行线程内某个方法,产生cpu视角的主存,即工作内存
主存:线程共享
工作内存:线程私有内存 + cpu高速缓存/寄存器
对主存中共享数据的操作,存在主存到工作内存 ==》从主存读取(拷贝)、工作内存修改、写回主存

3. 有序性
例如:1.去前台取快递2.去教室写作业3.去前台拿U盘
如果是在单线程的情况下,JVM,CPU指令可能会对其进行优化,产生重排序,比如按照1 -> 3 -> 2顺序执行也可以,还提高了效率,这叫做指令重排序
但是若在多线程情况下就会出现问题,例如可能快递是在写作业的十分重内被另一个线程放过来,此时如果指令重排序,代码就是错误的。

线程不安全问题的解决:加锁(关键字:synchronized )

1.作用:对一段进行加锁操作,让某段代码满足三个特性(即原子性、可见性、有序性)
2.原理:多个线程间同步互斥(一段代码在任意一个时间点只有一个线程执行:加锁、释放锁加锁–>基于对象来进行加锁/释放锁,而不是直接锁代码)
3.语法:
(1)同步代码块:synchronized(某个对象){…}
(2)同步方法:实例同步方法(this加锁)、静态同步方法(当前类对象加锁)

代码改进如下:在加锁之后就可以打印出预期值
同步代码块:
在这里插入图片描述
静态同步方法:

   private static int COUNT = 0;
    //synchronized加在static之前之后都可以
//    public synchronized static void increment() {
//        COUNT++;
//    }
    //对当前类对象进行加锁,线程间是同步互斥的
    public static synchronized void increment(){
        COUNT++;
    }

—>调用

                @Override
                public void run() {
                    for (int j = 0; j < 1000; j++) {
                        increment();
                    }
                }

实例同步方法:

//对this对象加锁
public static synchronized void increment(){
        COUNT++;
    }

—>调用

        SynchronizedTest st = new SynchronizedTest();
        Thread[] threads = new Thread[20];
        for (int i = 0; i < 20; i++) {
            threads[i] = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 1000; j++) {
//                        synchronized (cl) { //加锁 同步代码块
//                            COUNT++;
//                        } //释放锁
//                        increment();//静态同步方法调用
                       //1.
                        st.increment(); //实例方法同步调用
                       //2.
                          synchronized (st) {
                              COUNT++;
                          }
                    }
                }
            });
        }

注1:只有对同一个对象加锁,才会让线程产生同步互斥的作用

如下代码是对不同的对象加锁,没有同步互斥的作用

//对不同的对象加锁,没有同步互斥的作用,程序并发并行执行
public static void increment() {
        synchronized (new SynchronizedTest()) {
            COUNT++;
        }
 }

注2:synchronized具有可重入性(同一个线程可以对一个对象锁多次申请,基于计数器实现,申请获取+1,释放-1)

    public static synchronized void decrement(){
        COUNT--;
    }
    public static synchronized void increment(){
        COUNT++;
        decrement();
    }
    //调用increment()
    //运行结果为0

在这里插入图片描述

注3:synchronized多个线程同步互斥
(1)一个时间只有一个线程执行(同步互斥)
(2)竞争失败的线程,不停的在阻塞态与运行态之间切换(用户态和内核态切换)
(3)同步线程数量越多其性能越低

代码:

public class SynchronizedTest {
    private static int COUNT = 0;
    //synchronized加在static之前之后都可以
//    public synchronized static void increment() {
//        COUNT++;
//    }

//    public static synchronized void increment(){
//        COUNT++;
//    }
    public static void increment1() {
        synchronized (new SynchronizedTest()) {
            COUNT++;
        }
    }
    //对this对象加锁
    public synchronized void increment(){
        COUNT++;
    }


    public static void main(String[] args) throws InterruptedException {
        Class cl = SynchronizedTest.class;
        SynchronizedTest st = new SynchronizedTest();
        //有一个COUNT = 0 变量,同时启动20个 线程,每个线程执行1000次,每次循环COUNT++
        //等这20个子线程执行完毕后,再main线程打印COUNT(预期2000)
        Thread[] threads = new Thread[20];
        for (int i = 0; i < 20; i++) {
            threads[i] = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 1000; j++) {
//                        synchronized (cl) { //加锁
//                            COUNT++;
//                        }
//                        increment();//静态同步方法调用
                        st.increment(); //实例方法同步调用
                    }
                }
            });
        }
        for (int i = 0; i < 1; i++) {
            threads[19 + i] = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 1000; j++) {
                        st.increment();
                    }
                }
            });
        }


        for (Thread t : threads) {
            if (t != null)
             t.start();
        }

        for (Thread t : threads) {
            if (t != null)
             t.join();
        }
        System.out.println(COUNT);
        //输出20000
    }
}

******************************************************************************************************************

模拟实现:一个教室,座位有50个,同时有三个老师安排同学的座位, 每个老师安排100个同学,模拟使用多线程实现,座位编号为1-50 /0-49,三个线程同时启动来安排同学,同学可以循环操作来安排,一直到座位排满

public class SynchronizedTest2 {
    private static int STUDENT = 50;
    
    public static void main(String[] args) {
        Task task = new Task();
        for (int i = 0; i < 3;i++) {
            new Thread(task).start();
        }
    }

    private static class Task implements Runnable{
        @Override
        public void run() {
            for (int i = 0; i < 100;i++) {
                synchronized (this) {
                    if (COUNT > 0) {
                        COUNT--;
                        System.out.println(Thread.currentThread().getName() + ":" + COUNT);
                    }
                }
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }
    }

    public static void main1(String[] args) throws InterruptedException {
        Class cl = SynchronizedTest2.class;
        Thread[] thacher = new Thread[3];
        for (int i = 0; i < 3;i++) {
            thacher[i] = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (STUDENT > 0) {
                        synchronized (cl){ //加锁
                            if (STUDENT > 0) {
                                System.out.println(Thread.currentThread().getName() + ",还有" + STUDENT--);
                            }
                            try { 
                                Thread.sleep(100);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            });
        }
        for (Thread t : thacher) {
            t.start();
        }
    }
}

若让老师安排的学生的人数可以控制应该如何修改?
代码如下:

public class SynchronizedTest3 {

    private static int COUNT = 50;
    public static void main(String[] args) {
        new Thread(new Task(10)).start();
        new Thread(new Task(20)).start();
        new Thread(new Task(20)).start();

    }

    private static class Task implements Runnable{
        private int number; //控制一个老师可以安排的数量

        public Task(int number) {
            this.number = number;
        }

        @Override
        public void run() {
            for (int i = 0; i < 100;i++) { //并发并行执行
                synchronized (Task.class) {
                    if (COUNT > 0 && number > 0) { //一定要再次判断
                        COUNT--;
                        number--;
                        System.out.printf("%s:count=%s,num=%s\n" ,Thread.currentThread().getName(),COUNT,number);
                    }
                }
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }
    }
}

volatile关键字:修饰共享变量
(1)保证可见性
(2)禁止指令重排序,建立内存屏障
(3)不能保证原子性


常见的使用场景:不依赖共享变量来赋值的操作,一般是进行读写分离操作,提高性能

(1)写操作不依赖共享变量,赋值是一个常量
(2)作用在读,写依赖于其他线程安全手段(加锁)
依赖共享变量赋值不是原子性操作

private static volatile boolean STOP = false;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值