为什么使用synchronized锁
如果多个线程对同一变量,并发访问修改,没有做同步处理最终就会导致出现脏读的问题。
非线程安全的变量
方法内的变量为线程安全的变量:jvm方法调用在操作数栈中,而操作数栈是每个线程独有的,线程间的栈是不共享的,所以方法内的变量是每个线程独有的,所以不会出现脏读(题外话:在单个线程间栈内创建数据如果存在值相同的,就指向同一个地址,就是栈共享)。
实例变量为非线程安全的变量:实例变量在实例化后将在堆上分配,而堆是线程间共享的,那么就会出现非线程安全的问题。
synchronized锁重入
- 可重入锁概念就是自己可以再次获取自己的内部锁。
public class MyService {
public synchronized void method1() {
System.out.println("method1");
method2();
}
public synchronized void method2() {
System.out.println("method2");
method3();
}
public synchronized void method3() {
System.out.println("method3");
}
}
public class ServiceThread extends Thread {
@Override
public void run() {
MyService ms = new MyService();
ms.method1();
}
}
public class Test {
public static void main(String[] args) {
ServiceThread st = new ServiceThread();
st.start();
}
}
结果:
[Console output redirected to file:D:\console.txt]
method1
method2
method3
- 可重入锁支持父子继承关系中
public class Father {
public synchronized void execute1() {
System.out.println("Father execute1");
}
}
public class Children extends Father{
public synchronized void execute2() {
System.out.println("Children execute2");
execute1();
}
}
public class ServiceThread extends Thread {
@Override
public void run() {
Children c = new Children();
c.execute2();
}
}
public class Test {
public static void main(String[] args) {
ServiceThread st = new ServiceThread();
st.start();
}
}
结果:
[Console output redirected to file:D:\console.txt]
Children execute2
Father execute1
异常,锁自动释放
public class Children {
public synchronized void execute2() {
System.out.println("Children execute2");
int i = 3 / 0;
System.out.println("i = " + i);
}
}
public class ServiceThread extends Thread {
@Override
public void run() {
Children c = new Children();
c.execute2();
}
}
public class Test {
public static void main(String[] args) {
ServiceThread st = new ServiceThread();
st.start();
}
}
结果:
[Console output redirected to file:D:\console.txt]
Children execute2
Exception in thread "Thread-0" java.lang.ArithmeticException: / by zero
at cn.spy.thread.test.Children.execute2(Children.java:8)
at cn.spy.thread.test.ServiceThread.run(ServiceThread.java:8)
synchronized同步代码块
synchronized方法对当前对象进行加锁,synchronized代码块是对某一个对象进行加锁。
1. 同步代码块和同步方法的区别
同步方法的特点就是只要是当前对象的同步方法,当前线程没执行完,其他线程只能等着。而同步代码块则是对于不同对象的同步代码块,线程1执行同步代码块1时,线程2也可以执行同步代码块2,这样效率会有所提升。而在我看来实际上两种方式只是细粒度上进一步得到了提升。同步代码块可以进行更加细微的操作。
2. synchronized(this)锁定当前对象
public class MyTask {
public void execute() {
synchronized (this) {
for (int i = 0 ; i < 3 ; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
}
public class MyThreadA extends Thread{
private MyTask myTask;
public MyThreadA(MyTask myTask) {
this.myTask = myTask;
}
@Override
public void run() {
myTask.execute();
}
}
public class MyThreadB extends Thread{
private MyTask myTask;
public MyThreadB(MyTask myTask) {
this.myTask = myTask;
}
@Override
public void run() {
myTask.execute();
}
}
public class MyTest {
public static void main(String[] args) {
MyTask myTask = new MyTask();
MyThreadA mtA = new MyThreadA(myTask);
mtA.setName("MyThreadA");
MyThreadB mtB = new MyThreadB(myTask);
mtB.setName("MyThreadB");
mtA.start();
mtB.start();
}
}
结果:
MyThreadA 0
MyThreadA 1
MyThreadA 2
MyThreadB 0
MyThreadB 1
MyThreadB 2
静态同步synchronized
synchronized关键字加到static静态方法上是给Class类上锁,而synchronized关键字加到非static静态方法上是给对象上锁。
public class MyTask {
public synchronized void execute() {
System.out.println(Thread.currentThread().getName() + " execute start");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " execute end");
}
public synchronized void print() {
System.out.println(Thread.currentThread().getName() + " print start");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " print end");
}
}
public class MyThreadA extends Thread{
private MyTask myTask;
public MyThreadA(MyTask myTask) {
this.myTask = myTask;
}
@Override
public void run() {
myTask.execute();
}
}
public class MyThreadB extends Thread{
private MyTask myTask;
public MyThreadB(MyTask myTask) {
this.myTask = myTask;
}
@Override
public void run() {
myTask.print();
}
}
public class MyTest {
public static void main(String[] args) {
MyTask myTask = new MyTask();
MyThreadA mtA = new MyThreadA(myTask);
mtA.setName("MyThreadA");
MyThreadB mtB = new MyThreadB(myTask);
mtB.setName("MyThreadB");
mtA.start();
mtB.start();
}
}
结果:
MyThreadA execute start
MyThreadA execute end
MyThreadB print start
MyThreadB print end
总结
- 方法内的变量是线程安全的变量,实例变量为非线程安全的变量。
- synchronized锁支持重重入。
- synchronized可重入锁支持父子继承关系中
- 遇到异常,synchronized锁自动释放。
- synchronized修饰方法意味着对当前调用方法的对象加锁同步。
- synchronized(this)锁定当前的对象。
- synchronized(class)锁定指定的类。
- synchronized修饰静态方法意味着给静态方法所在的类上锁。
网友评论