• java并发编程基础——线程同步


    线程同步

    一、线程安全问题

    如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

    线程安全问题往往发生在多个线程调用同一方法或者操作同一变量,但是我们要知道其本质就是CPU对线程的随机调度,CPU无法保证一个线程执行完其逻辑才去调用另一个线程执行。

    package threadtest;
     
    public class ThreadTest  implements Runnable{
     
        static int i = 0;
        public  void incre() {
            i++;
        }
         
        @Override
        public  void run() {
            for(int j=0;j<1000000;j++) {
                incre();
            }
        }
     
        public static void main(String[] args) throws InterruptedException {
            ThreadTest tt = new ThreadTest();
            Thread t1 = new Thread(tt);
            Thread t2 = new Thread(tt);
            t1.start();
            t2.start();
            t1.join();
            t2.join();
            System.out.println(i);
        }
             
    }

    由于 i++不是原子操作,先读取值,再加1 赋值,所以当在读取i值的时候线程切换了,导致两个线程读取的i相同,导致线程安全问题。

    1363390  //结果小于2000000

    二、线程同步

    java多线程支持引入了同步监视器来解决线程同步问题,通过synchronized关键字,主要有同步方法和同步代码块

    执行同步代码前必须先获得对同步监视器的锁定(任何时刻都只有一个线程可以获得同步监视器的锁定)

    java5开始提供了更强大的同步机制,同步锁Lock

    1、同步方法: synchronized修饰方法

    package threadtest;
     
    public class ThreadTest  implements Runnable{
     
        static int i = 0;
        public synchronized void incre() {
            i++;
        }
         
        @Override
        public   void run() {
            for(int j=0;j<1000000;j++) {
                incre();
            }
        }
     
        public static void main(String[] args) throws InterruptedException {
            ThreadTest tt = new ThreadTest();
            Thread t1 = new Thread(tt);
            Thread t2 = new Thread(tt);
            t1.start();
            t2.start();
            t1.join();
            t2.join();
            System.out.println(i);
        }
             
    }

    同步方法的同步监视器就是方法所属的对象本身

    2000000 //结果正常

    synchronized修饰静态方法

    package threadtest;
     
    public class ThreadTest  implements Runnable{
     
        static int i = 0;
        /**
         * 同步静态方法的同步监视器是该类对应的class对象
         */
        public static synchronized  void incre() {
            i++;
        }
         
        @Override
        public   void run() {
            for(int j=0;j<1000000;j++) {
                incre();
            }
        }
     
        public static void main(String[] args) throws InterruptedException {
            Thread t1 = new Thread(new ThreadTest());
            Thread t2 = new Thread(new ThreadTest());
            t1.start();
            t2.start();
            t1.join();
            t2.join();
            System.out.println(i);
        }
             
    }

    上面类中synchronized修饰的静态方法,同步监视器是该类对应的class对象,i是类属性,多个线程调用不同实例,i也是线程安全的。

    2000000

    2、同步代码块

    除了使用关键字修饰实例方法和静态方法外,还可以使用同步代码块,在某些情况下,我们编写的方法体可能比较大,同时存在一些比较耗时的操作,而需要同步的代码又只有一小部分,如果直接对整个方法进行同步操作,可能会得不偿失,此时我们可以使用同步代码块的方式对需要同步的代码进行包裹,这样就无需对整个方法进行同步操作了

    package threadtest;
     
    public class ThreadTest  implements Runnable{
     
        static int i = 0;
        /**
         * 同步代码块,synchronized(obj),obj就是同步监视器
         */
        public void incre() {
            synchronized(this) {
                i++;
            }
        }
         
        @Override
        public   void run() {
            for(int j=0;j<1000000;j++) {
                incre();
            }
        }
     
        public static void main(String[] args) throws InterruptedException {
            ThreadTest tt = new ThreadTest();
            Thread t1 = new Thread(tt);
            Thread t2 = new Thread(tt);
            t1.start();
            t2.start();
            t1.join();
            t2.join();
            System.out.println(i);
        }
             
    }

    同步代码块修饰静态方法

    package threadtest;
     
    public class ThreadTest  implements Runnable{
     
        static int i = 0;
        /**
         * 同步代码块,synchronized(obj),obj就是同步监视器
         */
        public static void incre() {
            synchronized(ThreadTest.class) {
                i++;
            }
        }
         
        @Override
        public   void run() {
            for(int j=0;j<1000000;j++) {
                incre();
            }
        }
     
        public static void main(String[] args) throws InterruptedException {
            //ThreadTest tt = new ThreadTest();
            Thread t1 = new Thread(new ThreadTest());
            Thread t2 = new Thread(new ThreadTest());
            t1.start();
            t2.start();
            t1.join();
            t2.join();
            System.out.println(i);
        }
             
    }

    3、同步锁Lock

    java5开始提供了通过显示定义同步锁对象来实现同步。

    在实现线程安全中,比较常用的是ReentrantLock(可重入锁),它是Lock接口的实现类。

    package threadtest;
     
    import java.util.concurrent.locks.ReentrantLock;
     
    public class ThreadTest  implements Runnable{
     
        private final ReentrantLock lock = new ReentrantLock();
        static int i = 0;
        public void incre() {
            lock.lock();//加锁
            try {
                i++;
            } finally {
                lock.unlock();
            }
                 
        }
         
        @Override
        public   void run() {
            for(int j=0;j<1000000;j++) {
                incre();
            }
        }
     
        public static void main(String[] args) throws InterruptedException {
            ThreadTest tt = new ThreadTest();
            Thread t1 = new Thread(tt);
            Thread t2 = new Thread(tt);
            t1.start();
            t2.start();
            t1.join();
            t2.join();
            System.out.println(i);
        }
             
    }
    2000000//线程安全

    4、死锁

    当两个线程同时等待对方释放同步监视器就会发生死锁,java虚拟机没有检测,也没有采取措施来处理死锁情况,所以多线程编程时应该采取措施避免死锁出现。

    一旦出现死锁,程序不会发生任何异常情况,也没有任何提示,只是所有线程处于阻塞状态,无法继续

    下面程序就是发生死锁

    package threadtest;
    /**
     * 一个简单的死锁例子,大概的思路:两个线程A和B,两把锁X和Y,现在A先拿到锁X,然后sleep()一段时间,我们知道sleep()是不会释放锁资源的。然后如果这段时间线程B拿到锁Y,也sleep()一段时间的话,那么等到两个线程都醒过来的话,那么将互相等待对方释放锁资源而僵持下去,陷入死锁。flag的作用就是让A和B获得不同的锁。
     * @author rdb
     *
     */
    public class ThreadTest  implements Runnable{
     
        Object o1 = new Object();
        Object o2 = new Object();
        private boolean flag = true ;
         
        @Override
        public   void run() {
            if(flag) {
                flag = false;
                synchronized (o1) {
                    System.out.println(Thread.currentThread().getName());
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (o2) {
                        System.out.println("**************");
                    }
                }
            }else {
                flag = true;
                synchronized (o2) {
                    System.out.println(Thread.currentThread().getName());
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (o1) {
                        System.out.println("**************");
                    }
                }
            }
        }
     
        public static void main(String[] args) throws InterruptedException {
            ThreadTest tt = new ThreadTest();
            Thread t1 = new Thread(tt);
            Thread t2 = new Thread(tt);
            t1.start();
            t2.start();
             
             
        }
             
    }
  • 相关阅读:
    Vue 项目刷新当前页面
    Vue 使用 vue-echarts 图表插件
    Vue 路由跳转、传参、接参四种方式
    webpack 之 proxyTable 设置跨域
    二元函数的梯度下降法求解
    2020暑期华为勇敢星实习总结
    时间序列预测算法-ARIMA算法
    航天一院介绍
    腾讯数据分析笔试
    C# Fleck的WebSocket使用
  • 原文地址:https://www.cnblogs.com/jnba/p/10592970.html
Copyright © 2020-2023  润新知