zoukankan      html  css  js  c++  java
  • 多线程安全(synchronized、三大特性、Java内存模型)

    线程安全问题?

    什么是线程安全问题?简单的说,当多个线程在共享同一个变量,做读写的时候,会由于其他线程的干扰,导致数据误差,就会出现线程安全问题。

    比如说,多个窗口同时卖票这个案例:

     1 public class ThreadTrain2 implements Runnable {
     2     private int tickets = 50;
     3     @Override
     4     public void run() {
     5         while(tickets > 0){
     6                 if (tickets > 0) {                            
                  System.out.println(Thread.currentThread().getName() + "卖了第" + (50 - tickets + 1) + "张票"); 7 tickets--; 8 } 9 } 10 } 11 } 12 public static void main(String[] args) { 13 ThreadTrain2 tt = new ThreadTrain2(); 14 Thread th1 = new Thread(tt, "1号窗口"); 15 Thread th2 = new Thread(tt, "2号窗口"); 16 th1.start(); 17 th2.start(); 18 } 19 }

    模拟两个窗口共同卖50张票,什么都不考虑,按照上面的写法,运行的结果有时候并不是我们想要的,会完全乱了套。

    我们该如何解决多线程安全问题?

    使用多线程同步(synchronized)或者加锁lock

    什么是多线程同步?就是当多个线程共享同一个资源时,不会受到其他线程的干扰。

    为什么这两种方法可以解决线程的安全问题?

    当把可能发生冲突的代码包裹在synchronized或者lock里面后,同一时刻只会有一个线程执行该段代码,其他线程必须等该线程执行完毕释放锁以后,才能去抢锁,获得锁以后,才拥有执行权,这样就解决的数据的冲突,实现了线程的安全。

     卖票的案例同步后为:

     1 public class ThreadTrain2 implements Runnable {
     2     private int tickets = 50;
     3     private static Object obj = new Object();//锁的对象,可以是任意的对象
     4     @Override
     5     public void run() {
     6         while(tickets > 0){ 
    7
    synchronized (obj) {// 同步代码块 8 if (tickets > 0) { 9 System.out.println(Thread.currentThread().getName() + "卖了第" + (50 - tickets + 1) + "张票"); 10 tickets--; 11 } 12 } 13 } 14 } 15 public static void main(String[] args) { 16 ThreadTrain2 tt = new ThreadTrain2(); 17 Thread th1 = new Thread(tt, "1号窗口"); 18 Thread th2 = new Thread(tt, "2号窗口"); 19 th1.start(); 20 th2.start(); 21 } 22 } 23

     上面是同步代码块的加锁方式,可以解决线程安全问题。同时,还有一种同步函数的方式,就是在方法上直接加synchronized,可以实现同样的效果,那么现在有一个问题,在方法上加synchronized修饰,锁的对象是什么呢???this。。下面来验证一下为什么是this:

    public class ThreadTrain1 implements Runnable {
        private int tickets = 100;
        private static Object obj = new Object();
        private static boolean flag = true;
        @Override
        public void run() {
            if (flag) {
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                synchronized (this) {// 同步代码块
                    while (tickets > 0) {
                        if (tickets > 0) {
                            System.out.println(Thread.currentThread().getName() + "卖了第" + (100 - tickets + 1) + "张票");
                            tickets--;
                        }
                    }
                }
            } else {
                while (tickets > 0) {
                    sale();
                }
            }
        }
        public synchronized void sale() {
            if (tickets > 0) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
                System.out.println(Thread.currentThread().getName() + "卖了第" + (100 - tickets + 1) + "张票");
                tickets--;
            }
        }
        public static void main(String[] args) {
            ThreadTrain1 tt = new ThreadTrain1();
            Thread th1 = new Thread(tt, "1号窗口");
            Thread th2 = new Thread(tt, "2号窗口");
            th1.start();
            try {
                Thread.sleep(40);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            ThreadTrain1.flag = false;
            th2.start();
        }
    }
    View Code

     点击+号查看代码,代码中的执行结果是绝对正确的,我们是采用一个线程使用同步代码块,另一个线程使用同步函数的方式,看是否会发生数据错误,作为对比,下面的代码中同步代码块我们不使用this,而是使用obj这个对象:

    public class ThreadTrain1 implements Runnable {
        private int tickets = 100;
        private static Object obj = new Object();
        private static boolean flag = true;
        @Override
        public void run() {
            if (flag) {
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                synchronized (obj) {// 同步代码块
                    while (tickets > 0) {
                        if (tickets > 0) {
                            System.out.println(Thread.currentThread().getName() + "卖了第" + (100 - tickets + 1) + "张票");
                            tickets--;
                        }
                    }
                }
            } else {
                while (tickets > 0) {
                    sale();
                }
            }
        }
        public synchronized void sale() {
            if (tickets > 0) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
                System.out.println(Thread.currentThread().getName() + "卖了第" + (100 - tickets + 1) + "张票");
                tickets--;
            }
        }
        public static void main(String[] args) {
            ThreadTrain1 tt = new ThreadTrain1();
            Thread th1 = new Thread(tt, "1号窗口");
            Thread th2 = new Thread(tt, "2号窗口");
            th1.start();
            try {
                Thread.sleep(40);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            ThreadTrain1.flag = false;
            th2.start();
        }
    }
    View Code

    显然,这段代码最后会出现数据冲突的情况,因为两个线程拿到的不是同一把锁,也证明了同步函数锁的是this。

    明白了同步函数的锁是this,那么加上static以后,锁的对象会不会发生改变,还是依然是this???

    先锁this,验证是否是this:

    public class ThreadTrain1 implements Runnable {
        private static int tickets = 100;
        private static boolean flag = true;
        @Override
        public void run() {
            if (flag) {
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                synchronized (this) {// 同步代码块
                    while (tickets > 0) {
                        if (tickets > 0) {
                            System.out.println(Thread.currentThread().getName() + "卖了第" + (100 - tickets + 1) + "张票");
                            tickets--;
                        }
                    }
                }
            } else {
                while (tickets > 0) {
                    sale();
                }
            }
        }
        public static synchronized void sale() {
            if (tickets > 0) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
                System.out.println(Thread.currentThread().getName() + "卖了第" + (100 - tickets + 1) + "张票");
                tickets--;
            }
        }
        public static void main(String[] args) {
            ThreadTrain1 tt = new ThreadTrain1();
            Thread th1 = new Thread(tt, "1号窗口");
            Thread th2 = new Thread(tt, "2号窗口");
            th1.start();
            try {
                Thread.sleep(40);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            ThreadTrain1.flag = false;
            th2.start();
        }
    }
    View Code

    出现了数据错误,这里我们不做猜测,只做验证,静态的同步函数锁的是当前类的字节码文件,代码验证:

    public class ThreadTrain1 implements Runnable {
        private static int tickets = 100;
        private static boolean flag = true;
        @Override
        public void run() {
            if (flag) {
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                synchronized (ThreadTrain1.class) {// 同步代码块
                    while (tickets > 0) {
                        if (tickets > 0) {
                            System.out.println(Thread.currentThread().getName() + "卖了第" + (100 - tickets + 1) + "张票");
                            tickets--;
                        }
                    }
                }
            } else {
                while (tickets > 0) {
                    sale();
                }
            }
        }
        public static synchronized void sale() {
            if (tickets > 0) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
                System.out.println(Thread.currentThread().getName() + "卖了第" + (100 - tickets + 1) + "张票");
                tickets--;
            }
        }
        public static void main(String[] args) {
            ThreadTrain1 tt = new ThreadTrain1();
            Thread th1 = new Thread(tt, "1号窗口");
            Thread th2 = new Thread(tt, "2号窗口");
            th1.start();
            try {
                Thread.sleep(40);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            ThreadTrain1.flag = false;
            th2.start();
        }
    }
    View Code

     多线程死锁

     同步中嵌套同步,锁没有来得及释放,一直等待,就导致死锁。

    下面这段代码,多运行几次就会出现死锁,思路是开启两个线程,让这两个线程执行的代码获取的锁的顺序不同,第一个线程需要先获得obj对象锁,然后再获得this锁,才可以执行代码,然后释放两把锁。线程2需要先获得this锁,再获取obj对象锁才可执行代码,然后释放两把锁。但是,当线程1获得了obj锁之后,线程2获得了this锁,这时候线程1需要获得this锁才可执行,但是线程2也无法获取到obj对象锁执行代码并释放,所以两个线程都拿着一把锁不释放,这就产生了死锁。

    public class ThreadTrain3 implements Runnable {
        private static int tickets = 100;
        private static Object obj = new Object();
        private static boolean flag = true;
        @Override
        public void run() {
            if (flag) {
                while (true) {
                    System.out.println("111111");
                    synchronized (obj) {// 同步代码块
                        sale();
                    }
                }
            } else {
                
                while (true) {
                    System.out.println(222222);
                    sale();
                }
            }
        }
        public synchronized void sale() {
            synchronized(obj){
            if (tickets > 0) {
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "卖了第" + (100 - tickets + 1) + "张票");
                tickets--;
            }
            }
        }
        public static void main(String[] args) {
            ThreadTrain3 tt = new ThreadTrain3();
            Thread th1 = new Thread(tt, "1号窗口");
            Thread th2 = new Thread(tt, "2号窗口");
            th1.start();
            try {
                Thread.sleep(40);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            ThreadTrain3.flag = false;
            System.out.println(flag);
            th2.start();
        }
    }
    View Code

     多线程的三大特性

    原子性

    原子性就是在执行一个或者多个操作的过程中,要么全部执行完不被任何因素打断,要么不执行。比如银行转账,A账户减去100元,B账户必须增加100元,对这两个账户的操作必须保证原子性,才不会出现问题。还有比如:i=i+1的操作,需要先取出i,然后对i进行+1操作,然后再给i赋值,这个式子就不是原子性的,需要同步来实现数据的安全。

    原子性就是为了保证数据一致,线程安全。

    可见性

    当多个线程访问同一个变量时,一个线程修改了变量的值,其他的线程能立即看到,这就是可见性。

    这里讲一下Java内存模型?简称JMM,决定了一个线程与另一个线程是否可见,包括主内存(存放共享的全局变量)和私有本地内存(存放本地线程私有变量)

    本地私有内存存放的是共享变量的副本,线程操作共享变量,首先操作的是自己本地内存的副本,当同一时刻只有一个线程操作共享变量时,该线程操作完毕本地内存,然后会刷新到主内存,然后主内存会通知另一个线程,进而更新;但是如果同一时刻有多个线程操作共享变量,会来不及更新主内存进而通知其他线程更新变量,就会出现冲突问题。

    有序性

    就是程序的执行顺序会按照代码先后顺序进行执行,一般情况下,处理器由于要提高执行效率,对代码进行重排序,运行的顺序可能和代码先后顺序不同,但是结果一样。单线程下不会出现问题,多线程就会出现问题了。

    volatile

    保证可见性,但是不保证原子性。

    下面这个案例10个线程共享同一个count,进行+1操作:

    public class VolatileTest extends Thread{
        private volatile static int count = 0;
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                count++;
            }
            System.out.println(Thread.currentThread().getName()+":"+count);
        }
        public static void main(String[] args) {
            VolatileTest[] list = new VolatileTest[10];
            for (int i = 0; i < list.length; i++) {
                list[i] = new VolatileTest();
            }
            for (int i = 0; i < list.length; i++) {
                list[i].start();
            }
        }
    }

     多运行几次,就会出现最后结果有不到1000的情况,也就证明了volatile不会保证原子性。

    保证原子性,jdk1.5之后,并发包提供了很多原子类,例如AtomicInteger :

    public class VolatileTest2 extends Thread{
        private static AtomicInteger count = new AtomicInteger();
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                count.incrementAndGet();
            }
            System.out.println(Thread.currentThread().getName()+":"+count.get());
        }
        public static void main(String[] args) {
            VolatileTest2[] list = new VolatileTest2[10];
            for (int i = 0; i < list.length; i++) {
                list[i] = new VolatileTest2();
            }
            for (int i = 0; i < list.length; i++) {
                list[i].start();
            }
        }
    }
    AtomicInteger解决了同步, 最后的结果最大的肯定是1000
  • 相关阅读:
    Go 语言简介(下)— 特性
    Array.length vs Array.prototype.length
    【转】javascript Object使用Array的方法
    【转】大话程序猿眼里的高并发架构
    【转】The magic behind array length property
    【转】Build Your own Simplified AngularJS in 200 Lines of JavaScript
    【转】在 2016 年做 PHP 开发是一种什么样的体验?(一)
    【转】大话程序猿眼里的高并发
    php通过token验证表单重复提交
    windows 杀进程软件
  • 原文地址:https://www.cnblogs.com/javatalk/p/9917348.html
Copyright © 2011-2022 走看看