• 多线程-synchronized


    引言

    synchronized是Java线程同步中的一个重要的概念,synchronized是独占锁(互斥锁),同时也是可重入锁(可重入锁一定程度上避免了死锁的问题,内部是关联一个计数器,加一次锁计数器值加一,为零时释放锁),也是一种重量级锁。

    synchronized是Java中的关键字,是一种同步锁,修饰的情形有以下几种:

    (1)修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个方法的对象;

    (2)修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象。

    (3)修饰一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象。

    (4)修饰一个类,其作用的范围是synchronized后面大括号{}括起来的部分,作用的对象是这个类的所有对象。

    修饰一个代码块

    (1)一个线程访问一个对象中的synchronized(this)同步代码块时,其他试图访问该对象的线程将被阻塞。

    package com.huawei.thread;
    
    class SyncThread implements Runnable {
    	private static int count;
    
    	public SyncThread() {
    		count = 0;
    	}
    
    	@Override
    	public void run() {
    		synchronized (this) {
    			for (int i = 0; i < 5; i++) {
    				try {
    					System.out.println(Thread.currentThread().getName() + ": " + (count++));
    					Thread.sleep(100);
    				} catch (InterruptedException ie) {
    					ie.printStackTrace();
    				}
    			}
    		}
    	}
    }
    
    public class Test38 {
    
    	public static void main(String[] args) {
    		SyncThread sync = new SyncThread();
    		Thread t1 = new Thread(sync, "A");
    		Thread t2 = new Thread(sync, "B");
    		t1.start();
    		t2.start();
    	}
    
    }  

    运行截图:

    当两个并发线程t1,t2访问同一个对象sync中的synchronized代码块时,在同一个时刻只能有一个线程得到执行,另一个线程受阻塞。t1和t2是互斥的,因为在执行synchronized代码块时会锁定当前的对象,只有执行完该代码块才能释放该对象锁,下一个线程才能执行并锁定该对象。

    main函数做简单修改:

    public static void main(String[] args) {
    		Thread t1 = new Thread(new SyncThread(), "A");
    		Thread t2 = new Thread(new SyncThread(), "B");
    		t1.start();
    		t2.start();
    }
    

    synchronized只锁定对象,每个对象只有一个锁与之相关联。t1和t2分别创建了SyncThread对象与之关联,因此t1和t2锁定的对象是不同的,互不干扰,不形成互斥,所有两个线程可以同时执行。

    (2)当一个线程访问对象的一个synchronized同步块时,另一个线程仍然可以访问该对象中的非synchronized不同代码块。

    package com.huawei.thread;
    
    class Counter implements Runnable {
    	private int count;
    
    	public Counter() {
    		count = 0;
    	}
    
    	public void addCount() {
    		synchronized (this) {
    			for (int i = 0; i < 5; i++) {
    				try {
    					System.out.println(Thread.currentThread().getName() + ": " + (count++));
    					Thread.sleep(100);
    				} catch (Exception e) {
    					e.printStackTrace();
    				}
    			}
    		}
    	}
    
    	public void printCount() {
    		for (int i = 0; i < 5; i++) {
    			try {
    				System.out.println(Thread.currentThread().getName() + " count: " + count);
    				Thread.sleep(100);
    			} catch (Exception e) {
    				e.printStackTrace();
    			}
    		}
    	}
    
    	@Override
    	public void run() {
    		String tName = Thread.currentThread().getName();
    		if (tName.equals("A")) {
    			addCount();
    		} else if (tName.equals("B")) {
    			printCount();
    		}
    	}
    }
    
    public class Test39 {
    
    	public static void main(String[] args) {
    		Counter counter = new Counter();
    		Thread t1 = new Thread(counter, "A");
    		Thread t2 = new Thread(counter, "B");
    		t1.start();
    		t2.start();
    	}
    
    }  

    运行截图:

    addCount是一个synchronized方法,printCount是非synchronized。从上面的结果中可以看出一个线程访问一个对象的synchronized代码块时,其他线程可以访问对象的非synchronized代码块而不受阻塞。

    (3)指定要给某个对象加锁

    package com.huawei.thread;
    
    class Account {
    	private String name;
    	private float amount;
    
    	public Account(String name, float amount) {
    		this.name = name;
    		this.amount = amount;
    	}
    
    	public void deposit(float amt) {
    		amount += amt;
    		try {
    			Thread.sleep(100);
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    	}
    
    	public void withdraw(float amt) {
    		amount -= amt;
    		try {
    			Thread.sleep(100);
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    	}
    
    	public float getAmount() {
    		return amount;
    	}
    
    	@Override
    	public String toString() {
    		return "Account [name=" + name + ", amount=" + amount + "]";
    	}
    }
    
    class AccountOperator implements Runnable {
    	private Account account;
    
    	public AccountOperator(Account account) {
    		this.account = account;
    	}
    
    	@Override
    	public void run() {
    		synchronized (account) {
    			account.deposit(500);
    			account.withdraw(500);
    			System.out.println(Thread.currentThread().getName() + ": " + account.getAmount());
    		}
    	}
    }
    
    public class Test40 {
    	static final int THREAD_NUM = 5;
    
    	public static void main(String[] args) {
    		Account account = new Account("zhang san", 1000.0f);
    		AccountOperator ao = new AccountOperator(account);
    		Thread[] threads = new Thread[THREAD_NUM];
    		for (int i = 0; i < THREAD_NUM; i++) {
    			threads[i] = new Thread(ao, "Thread" + i);
    			threads[i].start();
    		}
    	}
    
    }
    

     

    synchronized给account对象加了锁。这时,当一个线程访问account对象时,其他试图访问account对象的线程将会阻塞。

    当有一个明确的对象作为锁时,就可以用类似下面这样的方式写程序:

    public void method3(SomeObject obj)
    {
       //obj 锁定的对象
       synchronized(obj) {
          // #TODO
       }
    }  

    当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的对象来充当锁:

    class Test implements Runnable
    {
       private byte[] lock = new byte[0];  // 特殊的instance变量
       public void method()
       {
          synchronized(lock) {
             // #TODO 同步代码块
          }
       }
    
       public void run() {
    
       }
    }  

    注:零长度的byte数组对象创建起来比任何对象都经济。

    synchronized不能锁住基本的类型如:int,long,float等非对象类型

    修饰一个方法 

    synchronized修饰方法和修饰一个代码块类似,只是作用范围不一样,修饰代码块时大括号括起来的范围,而修饰方法范围是整个函数。

    (1)synchronized关键字不能继承

    虽然可以用synchronized来定义方法,但是synchronized不属于方法定义的一部分。因此,synchronized关键字不能被继承。如果在父类中的某个方法使用了synchronized关键字,而在子类中覆盖这个方法,则在子类中这个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上synchronized关键字才可以。

    在子类方法中加上synchronized关键字:

    class Parent {
       public synchronized void method() { }
    }
    class Child extends Parent {
       public synchronized void method() { }
    }

    在子类方法中调用父类的同步方法: 

    class Parent {
       public synchronized void method() {   }
    }
    class Child extends Parent {
       public void method() { super.method();   }
    } 
    

    (2)在定义接口方法时不能使用synchronized关键字

    (3)构造方法不能使用synchronized关键字,但可以使用synchronized代码块来进行同步

    修饰一个静态方法

     (1)静态方法属于类而不属于对象。synchronized修饰的静态方法锁定的是这个类的所有对象。

    package com.huawei.thread;
    
    public class Test42 {
    
    	static class SyncThread implements Runnable {
    		private static int count;
    
    		public SyncThread() {
    			count = 0;
    		}
    
    		public synchronized static void m() {
    			for (int i = 0; i < 5; i++) {
    				try {
    					System.out.println(Thread.currentThread().getName() + ": " + (count++));
    				} catch (Exception e) {
    					e.printStackTrace();
    				}
    			}
    		}
    
    		@Override
    		public synchronized void run() {
    			m();
    		}
    	}
    
    	public static void main(String[] args) {
    		Thread t1 = new Thread(new SyncThread(), "A");
    		Thread t2 = new Thread(new SyncThread(), "B");
    		t1.start();
    		t2.start();
    	}
    
    }
    

    只要修饰了静态方法,相对于整个类及其产生的对象只有一把锁。

    (2)Java类中可以有静态代码块static{},可以在其中设置同步代码块

    public class Test40 {
    	static final Integer THREAD_NUM = 5;
    	
    	static {
    		synchronized (THREAD_NUM) {
    		  // #TODO	
    		}
    	}
    }  

    其中synchronized修饰的必须是static变量或者类

    (3)如果一个类中定义了一个synchronized 的 static 函数A,也定义了一个 synchronized 的 instance函数B,那么这个类的同一对象Obj在多线程中分别访问A和B两个方法时,不会构成同步,因为它们的锁都不一样。B方法的锁是Obj这个对象,而B的锁是Obj所属的那个Class。

    package com.huawei.thread;
    
    public class Test43 {
    
    	public static void main(String[] args) {
    		My my = new My();
    		Thread t1 = new Thread(my, "A");
    		Thread t2 = new Thread(my, "B");
    		t1.start();
    		t2.start();
    	}
    
    }
    
    class My implements Runnable {
    
    	@Override
    	public void run() {
    		String name = Thread.currentThread().getName();
    		if (name.equals("A")) {
    			test1();
    		} else if (name.equals("B")) {
    			test2();
    		}
    	}
    
    	synchronized void test2() {
    		try {
    			for (int i = 0; i < 5; i++) {
    				Thread.sleep(100);
    				System.out.println("test2");
    			}
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    	}
    
    	synchronized static void test1() {
    		try {
    			for (int i = 0; i < 5; i++) {
    				Thread.sleep(100);
    				System.out.println("test1");
    			}
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    	}
    
    }  

    修饰一个类  

    class ClassName {
       public void method() {
          synchronized(ClassName.class) {
             // todo
          }
       }
    }
    

    效果与修饰一个静态方法类似。

    小结

    (1)无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁。 

    (2)每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码。 

    (3)实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。

    参考资料

     http://blog.csdn.net/luoweifu/article/details/46613015

     http://www.cnblogs.com/beiyetengqing/p/6213437.html

  • 相关阅读:
    [强网杯青少年专项赛] 惨惨战队WriteUp
    [BUUOJ记录] [HCTF 2018]WarmUp
    [WUST-CTF]Web WriteUp
    PHP check 的一些绕过技术
    [易霖博YCTF]Web WriteUp
    Java backup
    Common Knowledge
    一篇帖子教会你如何获取网盘数据
    python爬虫如何爬知乎的话题?
    浏览器的复制到剪切板功能,兼容所有浏览器,悬浮层不能复制问题解决
  • 原文地址:https://www.cnblogs.com/lujiango/p/7660538.html
Copyright © 2020-2023  润新知