1.在java多线程编程中对象锁、类锁、同步机制synchronized详解:
对象锁:在java中每个对象都有一个唯一的锁,对象锁用于对象实例方法或者一个对象实例上面的。
类锁:是用于一个类静态方法或者class对象的,一个类的实例对象可以有多个,但是只有一个class对象。
同步机制synchronized:synchronized关键字用于修饰方法或者单独的synchronized代码块,当一个线程想执行synchronized中的内容时,必须先获取到对象锁,当对象锁没有线程占用时,进入synchronized方法会自动获取到对象锁,执行完毕后会自动释放锁,如果对象锁被A线程占用,B线程想执行synchronized的代码只能等待A个线程执行完毕后,释放对象锁,B线程才能获取到对象锁进入方法执行。一个线程获得对象A的锁,也可以获得对象B的锁,两个不同类的对象锁没有关联。
举例说明包含synchronized的方法和synchronized代码块:
package com.test;
public class TestThread {
public void test1() {
synchronized (this) {
int i = 0;
while (i++<5) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}
}
public synchronized void test2() {
int i = 0;
while (i++<5) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}
public static void main(String[] args) {
final TestThread test = new TestThread();
Thread test1 = new Thread(new Runnable() {
public void run() {
test.test1();
}
}, "test1");
Thread test2 = new Thread(new Runnable() {
public void run() {
test.test2();
}
}, "test2");
test1.start();
test2.start();
}
}
以上代码是因为执行同一个对象,所以另一个线程要等前一个对象,执行完成后释放掉对象锁,拿到对象锁才能继续执行synchronized里面的东西。
如果我们去掉synchronized修饰的方法或者synchronized代码块,将会打印出以下的结果:
如何要是去掉一个synchronized后,输出的语句是交叉执行的。这就说明,对于同一个对象,如果线程A得到了对象锁,线程B可以访问对象没有同步的方法和代码。进行同步的代码和没有同步的代码是互不影响的。
举例类锁:
package com.test;
public class MyThreadClass {
public static void main(String[] args) {
final MyThreadClass my=new MyThreadClass();
Thread thread1=new Thread(new Runnable() {
@Override
public void run() {
try {
my.test1();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"test1");
Thread thread2=new Thread(new Runnable() {
@Override
public void run() {
try {
MyThreadClass.test2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"test2");
thread1.start();
thread2.start();
}
public void test1() throws InterruptedException{
synchronized (MyThreadClass.class) {
int i=0;
while(i++<5){
System.out.println(Thread.currentThread().getName()+":"+i);
Thread.sleep(500);
}
}
}
public static synchronized void test2() throws InterruptedException{
int i=0;
while(i++<5){
System.out.println(Thread.currentThread().getName()+":"+i);
Thread.sleep(500);
}
}
}
执行结果:
类锁和对象锁其实是一样的,只是针对于不同的对象。
如果两个方法一个被synchronized修饰,一个静态方法被synchronized修饰(体现类锁和对象锁的区别),代码如下:
package com.test;
public class MyThreadClass2 {
public static void main(String[] args) {
final MyThreadClass2 my=new MyThreadClass2();
Thread thread1=new Thread(new Runnable() {
@Override
public void run() {
try {
my.test1();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"test1");
Thread thread2=new Thread(new Runnable() {
@Override
public void run() {
try {
MyThreadClass2.test2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"test2");
thread1.start();
thread2.start();
}
public synchronized void test1() throws InterruptedException{
int i=0;
while(i++<5){
System.out.println(Thread.currentThread().getName()+":"+i);
Thread.sleep(500);
}
}
public static synchronized void test2() throws InterruptedException{
int i=0;
while(i++<5){
System.out.println(Thread.currentThread().getName()+":"+i);
Thread.sleep(500);
}
}
}
代码执行结果如下:
结果也是交叉输出的,虽然都是被synchronized修饰,但是一个是属于对象的,一个是属于class。所以类锁和对象锁是不同的,他们控制着不同的区域,互不干扰。
疑问:java中既然synchronized修饰的方法和synchronized代码块作用是一样的,为什么还需要synchronized代码块呢?
使用synchronized修饰方式是直接在方法上面加锁,synchronized方法块是在方法里面加锁,一个范围大,一个范围小。还有一个最主要的在应用场景如下:在Class中创建一个对象,在Class中要执行这个对象的某个方法,为了防止多个线程同时执行,采用同步加锁,但是如果这个方法出现死循环或者执行时间很长,其他线程也不能执行对象的其他同步方法,需要等待这个线程执行完毕,影响系统性能。如果采用synchronized修饰方法和synchronized代码块可能都会出现这种情况,我们来模拟一下这种状况:
创建一个java对象类,里面有两个方法:
package com.test;
public class Test {
public void test1(){
}
public void test2(){
}
}
创建测试类代码如下:
package com.test;
public class MyThreadClass4 {
static Test test=new Test();
public static void main(String[] args) {
final MyThreadClass4 my=new MyThreadClass4();
Thread thread1=new Thread(new Runnable() {
@Override
public void run() {
try {
my.test1();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"test1");
Thread thread2=new Thread(new Runnable() {
@Override
public void run() {
try {
my.test2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"test2");
thread1.start();
thread2.start();
}
long startTime;
public synchronized void test1() throws InterruptedException{
test.test1();
System.out.println(Thread.currentThread().getName());
startTime = System.currentTimeMillis();//获取当前时间
Thread.sleep(5000);
}
public synchronized void test2() throws InterruptedException{
long endTime = System.currentTimeMillis();
test.test2();
System.out.println(Thread.currentThread().getName());
System.out.println("程序运行时间:"+(endTime-startTime)+"ms");
}
}
在class里面创建对象实例,然后将两个方法分别调用,在调用方法上面都加上synchronized修饰方法Thread.sleep(5000)来模拟方法执行时间过长或者死循环,但是可以看到时间为我们设置的时间,执行结果不理想执行结果如下:
package com.test;
public class MyThreadClass3 {
static Test test=new Test();
public static void main(String[] args) {
final MyThreadClass3 my=new MyThreadClass3();
Thread thread1=new Thread(new Runnable() {
@Override
public void run() {
try {
my.test1();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"test1");
Thread thread2=new Thread(new Runnable() {
@Override
public void run() {
try {
my.test2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"test2");
thread1.start();
thread2.start();
}
long startTime;
public void test1() throws InterruptedException{
synchronized (test) {
test.test1();
System.out.println(Thread.currentThread().getName());
startTime = System.currentTimeMillis();//获取当前时间
Thread.sleep(5000);
}
}
public synchronized void test2() throws InterruptedException{
long endTime = System.currentTimeMillis();
test.test2();
System.out.println(Thread.currentThread().getName());
System.out.println("程序运行时间:"+(endTime-startTime)+"ms");
}
}
synchronized代码块中我们只是对当前test对象加锁,和执行这块代码的对象没有任何关系。执行test1的同时,我照样可以执行其他的synchronized同步方法,增强系统性能。执行结果如下: