一,什么是线程同步
线程同步其实也就是资源归属者使用的统一性,既是当多个多个对象操作某个对象时,不能对最先使用的对象造成数据损坏。
这个数据以变量为例,首先必须是私有变量,保证对象外不可见。
如下面两个线程同时操作同一个对象foo
package thread;
public class Foo {
private int i=100;
public int getI(){
return i;
}
public void min(int j){
i-=j;
}
}
创建两个线程,并启动
package thread;
public class MyRunable implements Runnable {
private Foo foo=new Foo();
@Override
public void run() {
for(int i=0;i<3;i++){
foo.min(20);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":当前foo对象的值"+foo.getI());
}
}
public static void main(String[] args) {
MyRunable myRunable=new MyRunable();
Thread syncT1=new Thread(myRunable, "syncT1");
Thread syncT2=new Thread(myRunable, "syncT2");
syncT1.start();
syncT2.start();
}
}
结果是
syncT2:当前foo对象的值60
syncT1:当前foo对象的值60
syncT1:当前foo对象的值20
syncT2:当前foo对象的值20
syncT1:当前foo对象的值-20
syncT2:当前foo对象的值-20
如果只看一个对象的话,每次减20结果应该是80,60,40,所以此时对象是不安全的
怎么去实现线程同步就要用到锁的机制了。
线程同步的锁机制
1,类锁与对象锁
类锁:大家应该都清楚,每个类其实都有一个class对象的,这个对象存放的为该类的类属性或称为静态属性。给静态方法加锁(
synchronized)也就说此类的所有对象(其实java是不建议使用对象访问类的静态方法和属性的)的该方法持有该锁。那么当多个线程访问该类的这个静态方法时,只有一个线程能够获得该类的类锁,其他的线程处于阻塞状态,等待该线程释放该锁。那么其他线程可以访问该类的其他(加/不加锁)静态方法吗?可以访问其他(加/不加锁)非静态方法吗?
先从理论上解析:
其实类锁他是属于每个类的,只要虽然他是加到方法上的,加到方法只是为某个线程获取锁提供一种途径,而且并不是所有的方法都需要同步的,只要本类有一个方法获取类锁,所有其他的同步方法将不能被其他线程访问。
那么非静态方法呢,其实是可以的,具体为什么,可以稍加透露,因为非静态方法是加在对象上的是对象锁,这个锁的范围仅此对象,无法代表一个类对象,所以类锁被持有,非静态方法是可以被访问的。
我们下面做下测试:
如果静态方法加锁两个线程同时操作数据递减20的话,那么锁就是有效的
还是同样的两个类稍加改造:
package thread;
public class Foo {
private static int i=100;
public int getI(){
return i;
}
public synchronized static void min(int j){
i=i-j;
}
}
测试线程类为
package thread;
public class MyRunable implements Runnable {
private Foo foo=new Foo();
@Override
public void run() {
for(int i=0;i<3;i++){
Foo.min(20);
System.out.println(Thread.currentThread().getName()+":当前foo对象的值"+foo.getI());
}
}
public static void main(String[] args) {
MyRunable myRunable=new MyRunable();
Thread syncT1=new Thread(myRunable, "syncT1");
Thread syncT2=new Thread(myRunable, "syncT2");
syncT1.start();
syncT2.start();
}
}
输出结果为
syncT1:当前foo对象的值80
syncT2:当前foo对象的值60
syncT2:当前foo对象的值20
syncT1:当前foo对象的值40
syncT2:当前foo对象的值0
syncT1:当前foo对象的值-20
如果foo类的静态锁去掉的话
package thread;
public class Foo {
private static int i=100;
public int getI(){
return i;
}
public static void min(int j){
i=i-j;
}
}
结果为:
syncT1:当前foo对象的值80
syncT1:当前foo对象的值40
syncT1:当前foo对象的值20
syncT2:当前foo对象的值60
syncT2:当前foo对象的值0
syncT2:当前foo对象的值-20
显然静态锁是有效的
那么其他线程访问Foo类的其他未加锁静态方法呢,我们对Foo类稍加改造
package thread;
public class Foo {
private static int i=100;
public int getI(){
return i;
}
public synchronized static void min(int j){
i=i-j;
}
public synchronized static void min2(int j){
i=i-j;
}
}
线程测试类也修改一下
package thread;
public class MyRunable implements Runnable {
private Foo foo=new Foo();
@Override
public void run() {
for(int i=0;i<3;i++){
String threadName=Thread.currentThread().getName();
if("syncT1".equals(threadName)){
Foo.min(20);
}else if("syncT2".equals(threadName)){
Foo.min2(20);
}
System.out.println(Thread.currentThread().getName()+":当前foo对象的值"+foo.getI());
}
}
public static void main(String[] args) {
MyRunable myRunable=new MyRunable();
Thread syncT1=new Thread(myRunable, "syncT1");
Thread syncT2=new Thread(myRunable, "syncT2");
syncT1.start();
syncT2.start();
}
}
这样我们的两个线程一个去访问加锁的静态方法,另一个访问另一个加锁的静态方法,如果线程2如果无需等待线程1释放锁的话,那么得出的结果应该不是等差数列,那么结果呢
syncT1:当前foo对象的值80
syncT2:当前foo对象的值60
syncT2:当前foo对象的值40
syncT2:当前foo对象的值20
syncT1:当前foo对象的值0
syncT1:当前foo对象的值-20
结果就是同时只有一个方法操作了i
那么访问foo对象的非静态加锁方法呢,我们同上做下改动
package thread;
public class Foo {
private static int i=100;
public int getI(){
return i;
}
public synchronized static void min(int j){
i=i-j;
}
public synchronized void min2(int j){
i=i-j;
}
}
package thread;
public class MyRunable implements Runnable {
private Foo foo=new Foo();
@Override
public void run() {
for(int i=0;i<3;i++){
String threadName=Thread.currentThread().getName();
if("syncT1".equals(threadName)){
Foo.min(20);
}else if("syncT2".equals(threadName)){
foo.min2(20);
}
System.out.println(Thread.currentThread().getName()+":当前foo对象的值"+foo.getI());
}
}
public static void main(String[] args) {
MyRunable myRunable=new MyRunable();
Thread syncT1=new Thread(myRunable, "syncT1");
Thread syncT2=new Thread(myRunable, "syncT2");
syncT1.start();
syncT2.start();
}
}
结果为
syncT1:当前foo对象的值60
syncT2:当前foo对象的值60
syncT1:当前foo对象的值40
syncT2:当前foo对象的值20
syncT1:当前foo对象的值0
syncT2:当前foo对象的值-20
显然类锁是对对象无效的
2,总结
1,每个类都有且只有一个内置类锁。
2,synchronized只能加在类或对象的方法上,不能加在类和属性域上。
3,类锁和对象锁互不相关,互不冲突。
4,当一个线程访问到该类的任意一个加锁的静态方法时,此时该类的所有声明同步的静态方法其他线程不可访问处在阻塞状态,也就是说一个线程获取该类的同步锁,其他静态同步方法(类方法)是不能被其他线程访问。处于线程安全的。