Java中Synchronized的用法转载

原创
小哥 3年前 (2022-10-27) 阅读数 49 #大杂烩

原文:http://blog.csdn.net/luoweifu/article/details/46613015
作者:luoweifu
请标明来源,以便转载。

多线程、多进程的编程思想(1)--《从操作系统的角度谈线程和进程》一文详细介绍了线程和进程在操作系统中的关系及其性能,这是多线程学习的基础。这篇文章将继续讨论Java线程同步中的一个重要概念synchronized.

synchronized是Java中的关键字是同步锁。它可以修改以下对象:

  1. 修改代码块时,修改后的代码块称为同步语句块,其操作范围为大括号。{}封闭的代码,操作的对象是调用此代码块的对象;
  2. 修改一个方法,修改后的方法称为同步方法,其作用范围是整个方法,作用的对象是调用该方法的对象;
  3. 修改一个静态方法,它的作用范围是整个静态方法,角色的对象是这个类的所有对象;
  4. 修改其操作范围为的类。synchronized在括号中的部分中,充当Main的对象是此类的所有对象。

修改一段代码
线程访问对象。synchronized(this)正在同步代码块时,尝试访问该对象的其他线程将被阻止。让我们看一下下面的示例:
【Demo1】:synchronized的用法

/**

  • 同步线程
    */
    class SyncThread implements Runnable {
    private static int count;

public SyncThread() {
count = 0;
}

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 e) {
e.printStackTrace();
}
}
}
}

public int getCount() {
return count;
}
}

SyncThread的调用:

SyncThread syncThread = new SyncThread();
Thread thread1 = new Thread(syncThread, "SyncThread1");
Thread thread2 = new Thread(syncThread, "SyncThread2");
thread1.start();
thread2.start();

结果如下:

SyncThread1:0
SyncThread1:1
SyncThread1:2
SyncThread1:3
SyncThread1:4
SyncThread2:5
SyncThread2:6
SyncThread2:7
SyncThread2:8
SyncThread2:9*

当两个并发线程(thread1和thread2)访问同一对象(syncThread)中的synchronized当执行代码块时,一次只能执行一个线程,而另一个线程被阻塞。在执行代码块之前,必须等待当前线程执行代码块。Thread1和thread2是相互排斥的,因为在执行中synchronized当前对象在执行代码块时被锁定。只有在代码块执行后,对象锁才能被释放,下一个线程才能执行并锁定对象。
我们再把SyncThread呼叫稍有更改:

Thread thread1 = new Thread(new SyncThread(), "SyncThread1");
Thread thread2 = new Thread(new SyncThread(), "SyncThread2");
thread1.start();
thread2.start();

结果如下:

SyncThread1:0
SyncThread2:1
SyncThread1:2
SyncThread2:3
SyncThread1:4
SyncThread2:5
SyncThread2:6
SyncThread1:7
SyncThread1:8
SyncThread2:9

并不是说线程执行synchronized在代码块期间是否阻塞了其他线程?为什么在上面的例子中thread1和thread2同时在实施中。这是因为synchronized只有对象被锁定,并且每个对象只有一个锁(lock与其关联,上面的代码相当于以下代码:

SyncThread syncThread1 = new SyncThread();
SyncThread syncThread2 = new SyncThread();
Thread thread1 = new Thread(syncThread1, "SyncThread1");
Thread thread2 = new Thread(syncThread2, "SyncThread2");
thread1.start();
thread2.start();

在这一点上,两个SyncThread的对象syncThread1和syncThread2,线程thread1执行的是syncThread1对象中的synchronized代码(run),而线程thread2执行的是syncThread2对象中的synchronized代码(run);我们知道synchronized锁是对象,那么就会有两个锁被单独锁住。syncThread1对象和syncThread2对象,并且这两个锁是互斥的,因此两个线程可以同时执行。

2.当线程访问其中一个对象时synchronized(this)正在同步代码块时,另一个线程仍然可以访问非synchronized(this)正在同步代码块。
【Demo2:多线程访问。synchronized和非synchronized代码块

class Counter implements Runnable{
private int count;

public Counter() {
count = 0;
}

public void countAdd() {
synchronized(this) {
for (int i = 0; i < 5; i ++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

//非synchronized代码块,而不是打开count读写,所以你不能用它。synchronized
public void printCount() {
for (int i = 0; i < 5; i ++) {
try {
System.out.println(Thread.currentThread().getName() + " count:" + count);
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

public void run() {
String threadName = Thread.currentThread().getName();
if (threadName.equals("A")) {
countAdd();
} else if (threadName.equals("B")) {
printCount();
}
}
}

调用代码:

Counter counter = new Counter();
Thread thread1 = new Thread(counter, "A");
Thread thread2 = new Thread(counter, "B");
thread1.start();
thread2.start();

结果如下:

A:0
B count:1
A:1
B count:2
A:2
B count:3
A:3
B count:4
A:4
B count:5

在上面的代码中countAdd是一个synchronized的,printCount是非synchronized的。从上面的结果中,您可以看到一个线程访问一个对象。synchronized块时,其他线程可以访问该对象的非synchronized没有阻塞的代码块。

指定锁定对象
【Demo3】:指定锁定对象

/**

  • 银行账户
    */
    class Account {
    String name;
    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 (InterruptedException e) {
e.printStackTrace();
}
}
//取钱
public  void withdraw(float amt) {
amount -= amt;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

public float getBalance() {
return amount;
}
}

/**

  • 账户操作类
    */
    class AccountOperator implements Runnable{
    private Account account;
    public AccountOperator(Account account) {
    this.account = account;
    }

public void run() {
synchronized (account) {
account.deposit(500);
account.withdraw(500);
System.out.println(Thread.currentThread().getName() + ":" + account.getBalance());
}
}
}

调用代码:

Account account = new Account("zhang san", 10000.0f);
AccountOperator accountOperator = new AccountOperator(account);

final int THREAD_NUM = 5;
Thread threads[] = new Thread[THREAD_NUM];
for (int i = 0; i < THREAD_NUM; i ++) {
threads[i] = new Thread(accountOperator, "Thread" + i);
threads[i].start();
}

结果如下:

Thread3:10000.0
Thread2:10000.0
Thread1:10000.0
Thread4:10000.0
Thread0:10000.0

在AccountOperator 类中的run在该方法中,我们使用synchronized 给account该对象已锁定。此时,当线程访问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创建数组对象将比任何对象视图编译的字节码:生成零长度更经济byte[]对象只需3操作码,而Object lock = new Object()则需要7生产线操作码。

修改方法
Synchronized修改方法很简单,就是在方法的前面加synchronized,public synchronized void method(){//todo}; synchronized修饰方法和修改一段代码类似,只是作用范围不一样,修饰代码块是大括号括起来的范围,而修饰方法范围是整个函数。如将【Demo1】中的run方法改为以下方法,效果相同。

*【Demo4】:synchronized修改方法

public synchronized void run() {
for (int i = 0; i < 5; i ++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

Synchronized根据整个方法的编写采取行动。
写法一:

public synchronized void method()
{
// todo
}

写法二:

public void method()
{
synchronized(this) {
// todo
}
}

写方法一修改方法,写方法二修改代码块,但写方法一和写方法二是等价的,都锁定了整个方法的内容。

在用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();   }
}

在定义接口方法时不能使用synchronized关键字。
不能使用施工方法synchronized关键字,但可以使用。synchronized要同步的代码块。
修改静态方法
Synchronized您还可以修改静态方法,如下所示:

public synchronized static void method() {
// todo
}

我们知道静态方法属于类,而不是对象。同样的,synchronized修改后的静态方法锁定此类的所有对象。关于我们的观点Demo1如下所示进行一些修改:

【Demo5】:synchronized修改静态方法

/**

  • 同步线程
    */
    class SyncThread implements Runnable {
    private static int count;

public SyncThread() {
count = 0;
}

public synchronized static void method() {
for (int i = 0; i < 5; i ++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

public synchronized void run() {
method();
}
}

调用代码:

SyncThread syncThread1 = new SyncThread();
SyncThread syncThread2 = new SyncThread();
Thread thread1 = new Thread(syncThread1, "SyncThread1");
Thread thread2 = new Thread(syncThread2, "SyncThread2");
thread1.start();
thread2.start();

结果如下:

SyncThread1:0
SyncThread1:1
SyncThread1:2
SyncThread1:3
SyncThread1:4
SyncThread2:5
SyncThread2:6
SyncThread2:7
SyncThread2:8
SyncThread2:9

syncThread1和syncThread2是SyncThread两个物体,但在。thread1和thread2并发执行使线程保持同步。这是因为run静态方法被调用method,而静态方法是类,所以。syncThread1和syncThread2它等同于使用相同的锁。这是相关的Demo1是不同的。

修改类
Synchronized它还可以作用于类,如下所示:

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

我们把Demo5做一些更多的改变。
【Demo6】:修改类

/**

  • 同步线程
    */
    class SyncThread implements Runnable {
    private static int count;

public SyncThread() {
count = 0;
}

public static void method() {
synchronized(SyncThread.class) {
for (int i = 0; i < 5; i ++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

public synchronized void run() {
method();
}
}

它的影响和[Demo5]是相同的,synchronized对班级采取行动T就是给这门课上课。T加锁,T所有对象都使用相同的锁。

总结:
A. 无论synchronized关键字被添加到方法或对象上。如果它作用的对象是非静态的,则它获取的锁是一个对象。如果synchronized操作的对象是静态方法或类,则它获取的锁对于类的所有对象都是相同的锁。
B. 每个对象只有一个锁(lock与此关联的是,无论谁获得锁,都可以运行它控制的代码。
C. 实现同步的成本很高,甚至可能导致死锁,因此请尽量避免不必要的同步控制。

参考资料:
http://blog.csdn.net/chenguang79/article/details/677720
http://developer.51cto.com/art/200906/132354.htm
————————————————
版权声明:本文是CSDN博主「luoweifu“原文,跟上。CC 4.0 by-sa版权协议,转载请附上原始来源链接和本声明。
原始链接:https://blog.csdn.net/luoweifu/article/details/46613015

版权声明

所有资源都来源于爬虫采集,如有侵权请联系我们,我们将立即删除

热门