zoukankan      html  css  js  c++  java
  • JAVA笔记15-线程同步

    一、概念

    1、多个线程之间访问同一资源,进行协调的过程是线程同步。例如两个人同时操作同一银行账户。解决方法:加锁

    2、Java种引入对象互斥锁的概念,保证共享数据操作的完整性。每个对象都对应于一个可称为“互斥锁”的标记,这个标记保证在任一时刻,只能有一个线程访问该对象。关键字synchronized来与对象的互斥锁联系。

      当synchronized修饰方法时,是指当前调用该方法的对象被锁定。当synchronized(*)修饰代码块时,*指定的对象被锁定。如果*为this,则与synchronized修饰方法达到的效果一致。

    public class Test extends Thread{ 
        public static void main(String args[]){
            Test t = new Test();
            Test t2 = new Test();
            t.start();
            t.m2();
        }
        public  synchronized void m1(){
            System.out.println("m1");
            try{
                Thread.sleep(10000);
            }catch(Exception e){
            
            }
        }
        public void m2(){
            System.out.println("m2");
            try{
                Thread.sleep(10000);
            }catch(Exception e){
            }
        }
        public void run(){
            m1();
        }
    }

    输出:m2m1

    public class Test extends Thread{ 
        public static void main(String args[]){
            Test t = new Test();
            Test t2 = new Test();
            t.start();
            t.m2();
        }
        public  synchronized void m1(){
            System.out.println("m1");
            try{
                Thread.sleep(10000);
            }catch(Exception e){
            
            }
        }
        public  synchronized void m2(){
            System.out.println("m2");
            try{
                Thread.sleep(10000);
            }catch(Exception e){
            }
        }
        public void run(){
            m1();
        }
    }

    输出:m2 m1

    public class Test extends Thread{ 
        public static void main(String args[]){
            Test t = new Test();
            Test t2 = new Test();
            t.start();
            t2.start();
            t.m2();
            t2.m2();
        }
        public  synchronized void m1(){
            System.out.println("m1");
            try{
                Thread.sleep(10000);
            }catch(Exception e){
            
            }
        }
        public  synchronized void m2(){
            System.out.println("m2");
            try{
                Thread.sleep(10000);
            }catch(Exception e){
            }
        }
        public void run(){
            m1();
        }
    }

    输出:m1m1 m2 m2

    public class Test extends Thread{
        static Object lock=new Object(); 
        public static void main(String args[]){
            Test t = new Test();
            Test t2 = new Test();
            t.start();
            t2.start();
            t.m2();
            t2.m2();
        }
        public  void m1(){
            synchronized(lock){
            System.out.println("m1");
            try{
                Thread.sleep(10000);
            }catch(Exception e){
            
            }
            }
        }
        public  void m2(){
            synchronized(lock){
            System.out.println("m2");
            try{
                Thread.sleep(10000);
            }catch(Exception e){
            }
            }
        }
        public void run(){
            m1();
        }
    }

    输出:m1 m1 m2 m2

    二、举例

    t1,t2两个线程同时访问同一个对象test。

     1 public class TestSync implements Runnable{
     2     Timer timer = new Timer();
     3     public static void main(String args[]){
     4         TestSync test = new TestSync();
     5         Thread t1 = new Thread(test);
     6         Thread t2 = new Thread(test);
     7         t1.setName("t1");
     8         t2.setName("t2");
     9         t1.start();
    10         t2.start();
    11     }
    12     public void run(){
    13         timer.add(Thread.currentThread().getName());
    14     }
    15 }
    16 
    17 class Timer{
    18     private static int num = 0 ;
    19     public void add(String name){
    20         synchronized(this){
    21             num++;
    22             try{
    23                 Thread.sleep(1);//改成 wait(100);不行,因为wait释放锁
    24             }catch(InterruptedException e){
    25             }
    26             System.out.println(name+",你是第"+num+"个使用timer的线程");
    27         }
    28     }
    29 }

    输出:

    t1,你是第1个使用timer的线程
    t2,你是第2个使用timer的线程

    如果去掉synchronized则输出错误:

    t2,你是第2个使用timer的线程
    t1,你是第2个使用timer的线程

    错误原因:一个线程的执行过程中,被另一个线程打断了。

    解决方法一:在20行加入synchoronized互斥锁(上面的代码)。因为synchoronized锁定的是当前对象,而

    Thread t1 = new Thread(test);
    Thread t2 = new Thread(test);

    所以能够达到同步效果,因为t1,t2是同一个test对象,对应的timer也是同一个。如果

    Thread t1 = new Thread(test1);
    Thread t2 = new Thread(test2);

    则不能达到同步效果,因为t1,t2不是同一对象,对应的timer不是同一个。

    package test2;
    public class TestSync implements Runnable{
        static Timer timer = new Timer();
        public static void main(String args[]){
            TestSync test = new TestSync();
            TestSync test2 = new TestSync();
            Thread t1 = new Thread(test);
            Thread t2 = new Thread(test2);
            t1.setName("t1");
            t2.setName("t2");
            t1.start();
            t2.start();
        }
        public void run(){
            timer.add(Thread.currentThread().getName());
        }
    }
    
    class Timer{
        private static int num = 0 ;
        public synchronized void add(String name){
                num++;
                try{
                    Thread.sleep(3000);//改成 wait(100);不行,因为wait释放锁
                }catch(InterruptedException e){
                }
                System.out.println(name+",你是第"+num+"个使用timer的线程");
        }
    }

    输出正确,因为timer是同一个对象。

    解决方法二:或者在19行加入同步方法。如下:

    public class TestSync implements Runnable{
        Timer timer = new Timer();
        public static void main(String args[]){
            TestSync test = new TestSync();
            Thread t1 = new Thread(test);
            Thread t2 = new Thread(test);
            t1.setName("t1");
            t2.setName("t2");
            t1.start();
            t2.start();
        }
        public void run(){
            timer.add(Thread.currentThread().getName());
        }
    }
    
    class Timer{
        private static int num = 0 ;
        public synchronized void add(String name){
            //synchronized(this){
                num++;
                try{
                    Thread.sleep(1);
                }catch(InterruptedException e){
                }
                System.out.println(name+",你是第"+num+"个使用timer的线程");
            //}
        }
    }

    注意调用sleep的线程不释放锁,但调用wait的线程释放锁。

    三、死锁的模拟

    public class TestDeadLock implements Runnable{
        public int flag = 1 ;
        static Object o1 = new Object();
        static Object o2 = new Object();
        public void run(){
            System.out.println("flag="+flag);
            if(flag==1){
                synchronized(o1){
                    try{
                        Thread.sleep(500);
                    }catch(Exception e){
                        e.printStackTrace();
                    }
                    synchronized(o2){
                        System.out.println("1");
                    }
                }
            }
            if(flag==0){
                synchronized(o2){
                    try{
                        Thread.sleep(500);
                    }catch(Exception e){
                        e.printStackTrace();
                    }
                    synchronized(o1){
                        System.out.println("0");
                    }
                }
            }
        }
        public static void main(String[] args){
            TestDeadLock t1 = new TestDeadLock();
            TestDeadLock t2 = new TestDeadLock();
            t1.flag = 1 ;
            t2.flag = 0 ;
            Thread t11 = new Thread(t1);
            Thread t22 = new Thread(t2);
            t11.start();
            t22.start();
        }
    }

    死锁的条件(四个同时满足):互斥、部分锁定、循环等待、不可剥夺

    解决方法:锁的粒度加粗(一次性锁定全部需要的资源,破坏部分锁定)

         规定顺序(破坏循环等待)

           可以剥夺(破坏不可剥夺)

    四、synchronized面试题

    (1)

    问:上面程序中一个线程调用m1方法时,另一个线程可以同时调用m2方法吗?

    答案:可以同时执行。main()函数运行输出1000,b=1000,说明可以同时执行两个方法。(如果输出100,b=1000则说明不可以)。如果将m2方法改为:

    结果又是什么呢?答案:可以同时执行。输出2000,b=2000。

    执行过程:m1修改b为1000,之后睡眠,m2修改b为2000,main中打印2000,m1睡眠结束打印b=2000。

    注:那个方法加了synchronized只是说明该方法同时可以被一个线程调用,但是其他线程仍然可以自由调用非同步的方法(可能对同步方法产生影响,例如涉及到该同步方法中的变量)。

    (2)正确做法:m1,m2都加同步

    所以说,b相当于一个资源,如何控制该资源能够被正确访问呢?这就需要把涉及到该资源b的所有方法都考虑到!哪些方法是不是要设置为synchronized,所以本题中需要把m1,m2方法都加锁,即用synchronized修饰为同步方法。如下:

    结果又是什么?答案:输出1000,b=1000

    执行过程:m1,m2不能同时执行。运行m2方法,b的值改为2000,之后运行m1方法输出b改为1000,m1睡眠,main主线程输出1000,m1方法输出b=1000。

    5、两个方法一个读该对象,另一个修改该对象的值。一般的情况下改的方法加锁(同步方法),因为不允许多个线程同时改;读的方法不加锁(非同步方法),因为可以同时读。所以是加只读锁。

     

     

     

          

  • 相关阅读:
    数组方法的扩展,如map,reduce,fliter,forEach方法
    设计模式总体概括
    centos yum 安装 tomcat
    idea springboot 打包 war
    idea使用tomcat运行maven打包的war
    CentOS 7 用 yum 安装 Nginx
    CentOS更换yum源
    城市代码表mysql
    更改idea启动内存信息
    (三)多表代码生成
  • 原文地址:https://www.cnblogs.com/seven7seven/p/3670314.html
Copyright © 2011-2022 走看看