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

 

 

 

      

原文地址:https://www.cnblogs.com/seven7seven/p/3670314.html