前面介绍了多线程的诸多优点,好用归好用,同时也出现了一个问题就是线程的同步。当不做线程的同步控制,多个线程跑起来并对一个共享变量做改动,执行完毕后,你会发现共享变量的值不如你愿。因为这些线程没能实现线程安全。关于线程安全和线程非安全,看下百度的解释:
线程安全&线程非安全
线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。 线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。
保证线程安全,java提供了以下几种同步策略:
- synchronized关键字。
- volatile关键字。
- JUC包中的Lock接口。
4.ThreadLocal管理变量。
本篇主要介绍一下synchronized关键字的使用。
通俗点描述synchronized,它就像是挂在一个衣柜上的锁。记得大学和兄弟们打完球,一起去澡堂冲凉,走进更衣室,会看见有很多衣柜,一个个衣柜就如同一个个共享变量,如果一个衣柜上有锁(synchronized),说明这个柜(共享变量)正在被人(线程)使用,你如果非要用这个柜子,就得等正在使用这个柜子的人用完柜子,释放了锁,你再使用。
先示范一把线程非安全的例子:
public class Test5 implements Runnable {
private int count = 0;
@Override
public void run() {
for(int i = 0; i < 8; i++){
System.out.println(Thread.currentThread().getName()+ ":" + count++);
}
}
public static void main(String[] args) {
Test5 test5 = new Test5();
Thread thread1 = new Thread(test5,"thread1");
Thread thread2 = new Thread(test5,"thread2");
thread1.start();
thread2.start();
}
}
运行结果:(本想着count值为15,执行结果却是14,且thread1和thread2交叉着修改着count)
synchronized修饰一个方法
如果想让上面例子线程安全,且最后的count为15,使用synchronized修饰方法。修改上述代码的run()方法,两种写法:
①直接在方法访问修饰符后面加synchronized
@Override
public synchronized void run() {
for(int i = 0; i < 8; i++){
System.out.println(Thread.currentThread().getName()+ ":" + count++);
}
}
②在方法内部使用synchronized修饰方法代码块
@Override
public void run() {
synchronized (this){
for(int i = 0; i < 8; i++){
System.out.println(Thread.currentThread().getName()+ ":" + count++);
}
}
}
两种结果的执行结果是一致的,可以看出:当两个线程访问同一个对象(test5)的synchronized方法时,同一时刻只能有一个线程执行,另一个阻塞,两个线程是互斥的,只有当第一个执行完毕释放了锁,第二个线程才能获取锁重新锁定对象。
继续玩Test5,将其main方法内部做下修改,修改如下:
public static void main(String[] args) {
Thread thread1 = new Thread(new Test5(),"thread1");
Thread thread2 = new Thread(new Test5(),"thread2");
thread1.start();
thread2.start();
}
执行结果如下:
这是怎么回事,不是已经用synchronized修饰了run(),锁定对象了吗,怎么两个线程交叉执行,且count都是7,难道使用的姿势不对???
原因:synchronized锁定的是对象,new Test5()两次会创建两个独立的对象,所以会有两把锁锁定两个对象,thread1执行着对象1的run(),thread2执行着对象2的run(),所以两个线程互不影响,各执行各的。
synchronized(this),this为何物?
public class Test5 implements Runnable {
private static int count = 0;
@Override
public void run() {
synchronized (this){
for(int i = 0; i < 8; i++){
System.out.println(Thread.currentThread().getName()+ ":" + count++);
}
}
}
public static void main(String[] args) {
Test5 test5 = new Test5();
Thread thread1 = new Thread(test5,"thread1");
Thread thread2 = new Thread(test5,"thread2");
thread1.start();
thread2.start();
}
}
执行结果(线程安全):
修改main方法如下:
public static void main(String[] args) {
Thread thread1 = new Thread(new Test5(),"thread1");
Thread thread2 = new Thread(new Test5(),"thread2");
thread1.start();
thread2.start();
}
从这个例子就可以看出:这里的this是指当前对象,不管是对象A还是对象B,刚开始运行这段代码的时候会给这段代码加个锁,这样即使运行到中间被替换了,另一个线程也不会执行这段代码,因为这段代码加锁了,而钥匙在给代码加锁的那个线程手里,只有加锁的线程运行完这段代码,才会给代码解锁.然后其他线程才能执行这段代码。
修饰一个静态方法
静态方法是属于整个类的,所以synchronized修饰静态方法锁定的就是这个类的所有对象。
public class Test5 implements Runnable {
private static int count = 0;
@Override
public void run() {
addCount();
}
public static synchronized void addCount(){
for(int i = 0; i < 8; i++){
System.out.println(Thread.currentThread().getName()+ ":" + count++);
}
}
public static void main(String[] args) {
Test5 test5 = new Test5();
Thread thread1 = new Thread(test5,"thread1");
Thread thread2 = new Thread(test5,"thread2");
thread1.start();
thread2.start();
}
}
执行结果:
修改Test5的main(),再次执行
public static void main(String[] args) {
Thread thread1 = new Thread(new Test5(),"thread1");
Thread thread2 = new Thread(new Test5(),"thread2");
thread1.start();
thread2.start();
}
可以发现执行结果还是上图,new Test5()创建了两个不同的对象,但thread1、thread2的执行确实线程安全的,原因是synchronized修饰的是静态方法,静态方法是属于类的,两个对象用的是同一个锁,所以同步。
修饰一个类
修改Test5的addCount()方法,修改如下:
public static void addCount(){
synchronized (Test5.class){
for(int i = 0; i < 8; i++){
System.out.println(Thread.currentThread().getName()+ ":" + count++);
}
}
}
执行结果(线程安全的):
synchronized不支持继承
synchronized可以修饰方法,但不属于方法定义的一部分,不能够被继承。如果父类的一个方法使用了synchronized,子类中重写了此方法,那么在子类中的这个方法默认是不同步的。如果要达到同步效果,一是在子类方法前也加上synchronized,二是在子类方法中调用父类的同步方法。
总结
synchronized是java中的关键字,是种同步锁。
①对于同步的方法或代码块,要先获得对象锁才能进入同步方法或代码块进行操作;
②synchronized可修饰方法,分为静态方法和非静态方法,修饰静态方法锁定的是整个类,修饰非静态方法锁定的是一个对象;
③synchronized可修饰类,修饰的是这个类的所有对象;
④代码块synchronized(a),对象锁就是a。
⑤synchronized修饰方法不支持继承。
略陈固陋,如有不当之处,欢迎各位看官批评指正!