Synchronized
Synchronized是Java中解决并发问题的一种常用方法,也是最简单的一种方法。
Synchronized作用
- 确保线程互斥的访问同步代码
- 保证共享变量的修改能够及时可见
- 有效解决重排序问题
Synchronized使用
synchronized是Java中的关键字,是一种同步锁。他修饰的对象有以下几种:
- 修饰一个代码块,被修饰的代码称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
- 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象
- 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象
- 修改一个类,其作用的范围是synchronized后面括起来的部分,作用主的对象是这个类的所有对象
修饰一个代码块
- 一个线程访问一个对象中的synchronized(this)同步代码块时,其他试图访问该对象的线程将被阻塞
public class Test {
public static void main(String[] args) {
SyncThread syncThread = new SyncThread();
Thread thread1 = new Thread(syncThread, "thread1");
Thread thread2 = new Thread(syncThread, "thread2");
thread1.start();
thread2.start();
}
public static class SyncThread implements Runnable {
private static int count;
@Override
public void run() {
synchronized (this) {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+":" + (count++));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
运行结果:
thread1:0
thread1:1
thread1:2
thread1:3
thread1:4
thread2:5
thread2:6
thread2:7
thread2:8
thread2:9
当两个并发线程访问同一对象中的synchronized代码时,在同一时刻只能有一个线程执行,另一个线程阻塞,必须等待当前的线程执行完这个代码块后才能执行改代码快。这两个线程是互斥的,因为在执行synchronized代码块时会锁住当前的对象,只有执行完改代码快才能释放该对象锁,下一个线程才能执行并锁定对象
将线程的调用改一下:
public class Test {
public static void main(String[] args) {
// SyncThread syncThread = new SyncThread();
// Thread thread1 = new Thread(syncThread, "thread1");
// Thread thread2 = new Thread(syncThread, "thread2");
// thread1.start();
// thread2.start();
Thread thread3 = new Thread(new SyncThread(), "thread3");
Thread thread4 = new Thread(new SyncThread(), "thread4");
thread3.start();
thread4.start();
}
public static class SyncThread implements Runnable {
private static int count;
@Override
public void run() {
synchronized (this) {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + ":" + (count++));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
运行结果:
thread3:0
thread4:1
thread3:2
thread4:3
thread3:4
thread4:5
thread3:6
thread4:7
thread3:8
thread4:9
看到这里会发现这两个线程是同时执行,不是说在一个线程在执行synchronized代码块时其他线程受阻吗?这个时因为synchronized只锁定对象,每个对象只有一个锁,而修改的代码则是创建了两个对象,所以这两个对象互补干扰,会同时执行。
- 当一个线程访问对象的一个synchronized(this)同步代码块时,另外一个线程仍然可以访问对象整的非synchronized(this)中的同步代码块。
public class Test {
public static void main(String[] args) {
SyncThread syncThread = new SyncThread();
Thread thread1 = new Thread(syncThread, "thread1");
Thread thread2 = new Thread(syncThread, "thread2");
thread1.start();
thread2.start();
//
// Thread thread3 = new Thread(new SyncThread(), "thread3");
// Thread thread4 = new Thread(new SyncThread(), "thread4");
// thread3.start();
// thread4.start();
}
public static class SyncThread implements Runnable {
private static int count;
private void count() {
synchronized (this) {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + ":" + (count++));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
private void printCount() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + ":" + (count));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
public void run() {
String name = Thread.currentThread().getName();
if (name.equals("thread1")) {
count();
} else if (name.equals("thread2")) {
printCount();
}
}
}
}
从上面的结果中可以看出,一个线程访问一个对象的synchronized代码块时,别的线程可以访问改对象非synchronized代码而不受阻塞
3.指定给某个对象加锁
public class A {
private int count;
public A(int count) {
this.count = count;
}
public void addCount(int add) {
count += add;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void reduceCount(int reduce) {
count -= reduce;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public int getCount() {
return count;
}
}
public class SyncThread implements Runnable{
private A a;
public SyncThread(A a) {
this.a = a;
}
@Override
public void run() {
synchronized (a) {
a.addCount(2);
a.reduceCount(6);
System.out.println(Thread.currentThread().getName() + ":" + a.getCount());
}
}
}
public class Test1 {
public static void main(String[] a) {
SyncThread syncThread = new SyncThread(new A(8));
for (int i = 0; i < 6; i++) {
new Thread(syncThread, "Thread" + i).start();
}
}
}
运行结果:
第一次运行:
Thread1:4
Thread5:0
Thread4:-4
Thread3:-8
Thread2:-12
Thread0:-16
第二次
Thread0:4
Thread5:0
Thread4:-4
Thread1:-8
Thread3:-12
Thread2:-16
在这个SyncThread中run方法里,用synchronized个A对象加了锁。这是当一个线程访问A对象时,其他试图访问的A对象的线程将会阻塞,知道该线程结束,其他线程才可以执行。也就是谁拿到锁谁就可以运行那个加锁的代码。
当有一个明确的对象作为锁时,就可以用类似的方式写。
当没有明确的对象作为锁时,想一段代码同步时,可以创建一个特殊的对象来充当锁:
class Test implements Runnable{
private byte[] lock = new byte[0]; // 特殊的instance变量
public void method(){
synchronized(lock) {
// todo 同步代码块
}
}
@Override
public void run() {
}
}
修饰一个方法
synchronized修饰一个方法很简单,就是在方法的前面加synchronized;synchronized修饰方法和修饰一个代码块类似,只是作用范围不一样,修饰代码块时大括号括起来的范围,而修饰方法范围时整个函数。
public void run(){
synchronized(this){
}
}
public synchronized void run(){
}
写法一修饰的是一个方法,写法二修饰的是一个代码块,但写法一跟写法二是等价的,都是锁定了整个方法的内容
在用synchronized修饰方法是需要注意:
synchronized关键字不能继承;虽然可以使用synchronized来定义方法,但是synchronized并不属于方法定义的一部分,因此synchronized不能被继承。如果在弗雷方法中使用了synchronized关键字,而在子类中覆盖了这个方法,崽子的这个方法默认是不同步的,而必须显示的在子类的这个方法中加synchronized关键字才可以。不过可以在子类方法中调用父类中相应的方法,这样虽然子类中的方法不是同步的,但子类调用了弗雷的同步方法,因此子类也相当于同步了
//在子类方法中加上synchronized关键字
class Parent{
publice synchronized void method(){}
}
class Child extends Parent{
publice synchronized void method(){}
}
//在子类方法中调用父类的同步方法
class Parent{
publice synchronized void method(){}
}
class Child extends Parent{
publice void method(){
super.method();
}
}
修饰一个静态方法
静态方法是属于类的而不是属于对象的。同样,synchronized修饰的静态方法锁定的是这个类的对象
public class SyncThread1 implements Runnable {
public static int count;
public SyncThread1() {
count = 0;
}
public static synchronized void method() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + ":" + (++count));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
public void run() {
method();
}
}
public class Test3 {
public static void main(String[] a) {
SyncThread1 syncThread1 = new SyncThread1();
SyncThread1 syncThread2 = new SyncThread1();
Thread thread1 = new Thread(syncThread1, "Thread1");
Thread thread2 = new Thread(syncThread2, "Thread2");
thread1.start();
thread2.start();
}
}
运行结果:
Thread1:1
Thread1:2
Thread1:3
Thread1:4
Thread1:5
Thread2:6
Thread2:7
Thread2:8
Thread2:9
Thread2:10
在这里分别创建了SyncThread的两个对象,但是在两个线程并发执行的时候却保持的线程同步。这是应为在run方法中调用了静态方法,而静态方法是属于类的,所以这两个SyncThread相当一同一把锁。
修饰一个类
//用法
class clasName{
public void method(){
synchronized(className.class){
}
}
}
将上面的例子做个修改
public class SyncThread1 implements Runnable {
public static int count;
public SyncThread1() {
count = 0;
}
// public static synchronized void method() {
//
// for (int i = 0; i < 5; i++) {
// try {
// Thread.sleep(1000);
// System.out.println(Thread.currentThread().getName() + ":" + (++count));
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
// }
public static void method() {
synchronized (SyncThread1.class) {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + ":" + (++count));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
@Override
public void run() {
method();
}
}
public class Test3 {
public static void main(String[] a) {
SyncThread1 syncThread1 = new SyncThread1();
SyncThread1 syncThread2 = new SyncThread1();
Thread thread1 = new Thread(syncThread1, "Thread1");
Thread thread2 = new Thread(syncThread2, "Thread2");
thread1.start();
thread2.start();
}
}
运行结果:
Thread1:1
Thread1:2
Thread1:3
Thread1:4
Thread1:5
Thread2:6
Thread2:7
Thread2:8
Thread2:9
Thread2:10
我们会发现这个跟修饰静态方法是一样的。synchronized作用于一个类是,是给这个类加锁,类的所有对象用的是同一把锁。
总结
- 无论synchronized关键字加载方法还是对象上,如果它的作用是非静态的,那么它取得的锁是对象;如果synchronized作用的对象是一个静态方法或者一个类,则它取得的锁是类,该类所有的对象都是同一把锁。
- 每个对象只有一个锁与之相关联,谁拿的锁谁就可以运行锁住的那段代码
- 实现同步时要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制