参考链接:
https://blog.csdn.net/luoweifu/article/details/46613015
Java中并发编程使用中,最频繁和最简单的使用是synchronized关键字了吧,使用了synchronized关键字,代码或者对象只能同时被一个线程操作。synchronized可以分为以下几类:
1.同步代码块(synchronized修饰代码块)
2.同步方法(synchronized修饰方法)
3.同步静态方法(synchronized修饰静态方法)
4.同步类(synchronized修饰类)
5.同步对象(synchronized修饰对象)
1.同步代码块例子
public class SynchronizedCode implements Runnable {
private static int count = 0;
void functionA() {
synchronized (this) {
for (int i = 0; i < 5; i++) {
count++;
System.out.println(Thread.currentThread().getName() + ":"
+ "count=" + count);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public void run() {
functionA();
}
public static void main(String[] args) {
SynchronizedCode code = new SynchronizedCode();
Thread thread1 = new Thread(code, "thread1");
Thread thread2 = new Thread(code, "thread2");
thread1.start();
thread2.start();
}
}
控制台输出:
thread1:count=1
thread1:count=2
thread1:count=3
thread1:count=4
thread1:count=5
thread2:count=6
thread2:count=7
thread2:count=8
thread2:count=9
thread2:count=10
分析:
可以看出,在同一个对象中,访问同步代码块时只能有一个线程thread1访问,另外一个线程thread2一直在等待,直到第一个线程完全执行完毕。
典型格式:
functionXXX() {
synchronized (this) {
//do sth
}
2.同步方法例子
synchronized void functionA() {
for (int i = 0; i < 5; i++) {
count++;
System.out.println(Thread.currentThread().getName() + ":"
+ "count=" + count);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
控制台输出:
thread1:count=1
thread1:count=2
thread1:count=3
thread1:count=4
thread1:count=5
thread2:count=6
thread2:count=7
thread2:count=8
thread2:count=9
thread2:count=10
分析:
可以看到,从代码上,和同步代码块极为类似,只是将synchronized挪到方法上了,这样synchronized的同步范围扩大为整个方法,但是实际作用和同步代码块基本一样。
典型格式:
synchronized void functionXXX() {
//do sth
}
3.改进上述例子说明同步方法只锁方法,作用范围为同一个对象
对上述第二个例子简单修改如下:
public class SynchronizedCode implements Runnable {
private static int count = 0;
synchronized void functionA() {
for (int i = 0; i < 5; i++) {
count++;
System.out.println(Thread.currentThread().getName() + ":"
+ "count=" + count);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void run() {
functionA();
}
public static void main(String[] args) {
SynchronizedCode code1 = new SynchronizedCode();
SynchronizedCode code2 = new SynchronizedCode();
Thread thread1 = new Thread(code1, "thread1");
Thread thread2 = new Thread(code2, "thread2");
thread1.start();
thread2.start();
}
}
控制台输出:
thread1:count=1
thread2:count=2
thread2:count=4
thread1:count=4
thread2:count=5
thread1:count=5
thread2:count=7
thread1:count=7
thread1:count=9
thread2:count=9
分析:从log上看,好像synchronized不起作用了?输出为两个线程交替进行,但是仔细观察main函数,会发现此时有两个SynchronizedCode对象,而对于同步方法或者同步代码块,它们只同步一个对象中的对应同步范围,对于其他对象内部的执行逻辑,则管不到了。所以同步方法或者同步代码块是一个对象锁。
4.同步静态方法的例子
修改上述Demo中的方法,更改如下:
public class SynchronizedCode implements Runnable {
private static int count = 0;
static synchronized void functionA() {
for (int i = 0; i < 5; i++) {
count++;
System.out.println(Thread.currentThread().getName() + ":"
+ "count=" + count);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void run() {
functionA();
}
public static void main(String[] args) {
SynchronizedCode code1 = new SynchronizedCode();
SynchronizedCode code2 = new SynchronizedCode();
Thread thread1 = new Thread(code1, "thread1");
Thread thread2 = new Thread(code2, "thread2");
thread1.start();
thread2.start();
}
}
控制台输出:
thread1:count=1
thread1:count=2
thread1:count=3
thread1:count=4
thread1:count=5
thread2:count=6
thread2:count=7
thread2:count=8
thread2:count=9
thread2:count=10
分析:
观察main方法,可知,目前创建了SynchronizedCode的两个实例对象,各自有一个线程去访问SynchronizedCode类,可以看到线程1和线程2保持同步,为什么会这样呢?因为thread start调用了run方法,run里面的调用的functionA是一个静态同步方法,我们知道静态方法是属于类的,所以此时加的同步锁也是针对类的,同一时间,只能有一个SynchronizedCode类的对象来访问functionA方法,因此保持了线程的同步。
典型格式:
static synchronized void functionA() {
//do sth
}
5.同步类(class)的例子
public class SynchronizedCode implements Runnable {
private static int count = 0;
void functionA() {
synchronized (SynchronizedCode.class) {
for (int i = 0; i < 5; i++) {
count++;
System.out.println(Thread.currentThread().getName() + ":"
+ "count=" + count);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public void run() {
functionA();
}
public static void main(String[] args) {
SynchronizedCode code1 = new SynchronizedCode();
SynchronizedCode code2 = new SynchronizedCode();
Thread thread1 = new Thread(code1, "thread1");
Thread thread2 = new Thread(code2, "thread2");
thread1.start();
thread2.start();
}
控制台输出:
thread1:count=1
thread1:count=2
thread1:count=3
thread1:count=4
thread1:count=5
thread2:count=6
thread2:count=7
thread2:count=8
thread2:count=9
thread2:count=10
分析:
可以对比例子三和例子四的代码和输出。这种加锁方式和同步静态方法类似,锁是加在类上,即同一个类的所有实例,一次只能有一个实例拥有锁,这是一把类锁。
典型格式:
void functionXXX() {
...
synchronized (XXX.class) {
//do sth
}
...
}
6.同步对象的例子
此种情况稍显复杂,需要另外写个demo,如下
public class Acount {
private String UserName;
private float Money;
public Acount(String userName, float money) {
super();
UserName = userName;
Money = money;
}
public String getUserName() {
return UserName;
}
public void setUserName(String userName) {
UserName = userName;
}
public float getMoney() {
return Money;
}
public void setMoney(float money) {
Money = money;
}
}
public class BankOperator implements Runnable {
Acount a;
// 存钱
public void deposit(float money) {
a.setMoney(a.getMoney() + money);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":"
+ a.getMoney());
}
// 取钱
public void withdraw(float money) {
a.setMoney(a.getMoney() - money);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":"
+ a.getMoney());
}
public BankOperator(Acount a) {
super();
this.a = a;
}
public static void main(String[] args) {
Acount acount = new Acount("chj", 100);
BankOperator b = new BankOperator(acount);
Thread t1 = new Thread(b, "thread1");
Thread t2 = new Thread(b, "thread2");
Thread t3 = new Thread(b, "thread3");
Thread t4 = new Thread(b, "thread4");
t1.start();
t2.start();
t3.start();
t4.start();
}
public void run() {
// 假设每个线程存50 取50
withdraw(50);
deposit(50);
}
}
控制台输出:
thread1:withdraw-100.0
thread4:withdraw-50.0
thread3:withdraw0.0
thread2:withdraw50.0
thread1:deposit100.0
thread4:deposit100.0
thread3:deposit100.0
thread2:deposit100.0
分析:观察main方法,我们目前模拟4个操作员对chj账户进行操作,由于没有加锁,四个操作员可以同时操作账户,于是出现了-100的情况,如果我们需要一个操作员的存取动作完全结束才能操作,应该如何做呢?
最简单的方式是改为
public void run() {
// 假设每个线程存50 取50
synchronized (a) {
withdraw(50);
deposit(50);
}
}
这样对于a账户,一个线程的存取操作就变成一个原子操作,需要一同执行完毕,
此时,控制台输出:
thread1:withdraw50.0
thread1:deposit100.0
thread4:withdraw50.0
thread4:deposit100.0
thread3:withdraw50.0
thread3:deposit100.0
thread2:withdraw50.0
thread2:deposit100.0
典型格式:
public void functionXXX()
{
//obj 锁定的对象
synchronized(obj)
{
// todo
}
}
其他注意点
1.当一个线程访问对象的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该对象中的非synchronized(this)同步代码块,例子请看参考链接的Demo2 参考这里
2.synchronized方法可以被继承么?
答案是不可以,父类的synchronized修饰的方法是同步的,子类要想实现父类同名方法同步,第一可以使用super调用父类方法,第二只能自己继承方法后加上synchronized关键字
3.同步的弊端
a:性能下降,多线程的好处就是多个任务同时进行,但是synchronized的作用是一次只让一个线程操作一个对象或者方法,其他对象只能等待。过多的同步会导致性能的下降
b:导致死锁,某些情况同步还会导致死锁,比如:
void test(){
synchronized (a) {
synchronized (b) {
//do sth
}
}
...
}
如果有两个线程访问test方法,线程1先给a对象加锁,线程2先给b加锁,接下来就好戏上场了,线程1等2释放b的锁,线程2等1释放a的锁,然后就over了。
4.
当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的对象来充当锁:
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的锁是类锁,该类的所有实例拥有同一把锁。
修饰对象,修饰普通方法,修饰代码块的都是对象锁,只针对同一个对象,换了对象则不能对其他对象的内容起作用。
以上为原文https://blog.csdn.net/luoweifu/article/details/46613015的理解和整理,如有错误,请指出。