一:synchronized 引入
在学习知识前,我们先来看一个现象:
package synchronizedlearn;
/**
* 两个线程同时i++,最后结果会比预计的少
* 原因:
* i++: 看上去是一个操作,实际上是三个操作
* 1 读取 i 的数值
* 2 将 i 加 1
* 3 将 i 的值写到内存
* ------------------------------------------------
* 如何解决,在看完下面的几个代码示例后在会看这个处理方法
*/
public class DisAppearRequest implements Runnable {
static DisAppearRequest disAppearRequest = new DisAppearRequest();
static int i = 0;
@Override
public void run() {
for (int j = 0; j < 100000; j++) {
i++;
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(disAppearRequest);
Thread thread2 = new Thread(disAppearRequest);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(i);//144171 结果不是固定的
}
}
开启了2个线程,每个线程都累加了100000次,如果结果正确的话自然而然总数就应该是2* 1000000 = 2000000。可就运行多次结果都不是这个数,而且每次运行结果都不一样。这是为什么了?有什么解决方案了?这就是我们今天要聊的事情。
二:synchronized介绍以及案例
1、synchronized的基本使用:
Synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法。
2、synchronized的实现原理:
在java代码中使用synchronized可是使用在代码块和方法中,根据Synchronized用的位置可以有这些使用场景:
如图,synchronized可以用在方法上也可以使用在代码块中,其中方法是实例方法和静态方法分别锁的是该类的实例对象和该类的对象。而使用在代码块中也可以分为三种,具体的可以看上面的表格。这里的需要注意的是:如果锁的是类对象的话,尽管new多个实例对象,但他们仍然是属于同一个类依然会被锁住,即线程之间保证同步关系。
这里通过一些代码例子来对synchronized进行讲解。这里是看了悟空的课程,然后加上自己的理解来记录一下。
案例一:两个线程同时访问同一个对象的同步方法,结果是什么?
对象锁 这里是代码块形式 this 对象,这里的this对象指的是 synchronizedCodeBlock1
package synchronizedlearn;
/**
* 同步代码块
*/
public class SynchronizedCodeBlock1 implements Runnable{
static SynchronizedCodeBlock1 synchronizedCodeBlock1 = new SynchronizedCodeBlock1();
@Override
public void run() {
synchronized (this){
System.out.println("我是对象锁的代码块形式,name is "+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是对象锁的代码块形式,name is "+Thread.currentThread().getName()+"执行结束");
}
}
public static void main(String[] args) {
Thread thread1 = new Thread(synchronizedCodeBlock1);
Thread thread2 = new Thread(synchronizedCodeBlock1);
thread1.start();
thread2.start();
while (thread1.isAlive() || thread2.isAlive()){
}
System.out.println("finish");
}
}
对象锁代码块形式 object 对象
package synchronizedlearn;
/**
* 同步代码块
*/
public class SynchronizedCodeBlock1 implements Runnable{
static SynchronizedCodeBlock1 synchronizedCodeBlock1 = new SynchronizedCodeBlock1();
Object lock1 = new Object();
Object lock2 = new Object();
@Override
public void run() {
synchronized (lock1){
System.out.println("lock1---->"+"我是对象锁的代码块形式,name is "+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("lock1---->"+"我是对象锁的代码块形式,name is "+Thread.currentThread().getName()+"执行结束");
}
synchronized (lock2){
System.out.println("lock2---->"+"我是对象锁的代码块形式,name is "+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("lock2---->"+"我是对象锁的代码块形式,name is "+Thread.currentThread().getName()+"执行结束");
}
}
public static void main(String[] args) {
Thread thread1 = new Thread(synchronizedCodeBlock1);
Thread thread2 = new Thread(synchronizedCodeBlock1);
thread1.start();
thread2.start();
while (thread1.isAlive() || thread2.isAlive()){
}
System.out.println("finish");
}
}
对象锁 方法形式
package synchronizedlearn;
public class SynchronizedMethod1 implements Runnable{
static SynchronizedMethod1 synchronizedMethod1 = new SynchronizedMethod1();
@Override
public void run() {
method();
}
public synchronized void method(){
System.out.println("我是对象锁的方法修饰符形式,name is "+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是对象锁的方法修饰符形式,name is "+Thread.currentThread().getName()+"执行结束");
}
public static void main(String[] args) {
Thread thread1 = new Thread(synchronizedMethod1);
Thread thread2 = new Thread(synchronizedMethod1);
thread1.start();
thread2.start();
while (thread1.isAlive() || thread2.isAlive()){
}
System.out.println("finish");
}
}
synchronized用在方法上,每个类实例对应一把锁,每个 synchronized 方法都必须获得调用该方法的类实例的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。这种机制确保了同一时刻对于每一个类实例,其所有声明为 synchronized 的成员函数中至多只有一个处于可执行状态(因为至多只有一个能够获得该类实例对应的锁),从而有效避免了类成员变量的访问冲突(只要所有可能访问类成员变量的方法均被声明为 synchronized)。
案例二:两个线程访问synchronized的静态方法,结果如何?
从这个案例可以看出静态同步方法锁的类的对象
package synchronizedlearn;
/**
* 类锁的第一种形式
* synchronized static 形式
* synchronized 用在普通方法和静态方法上的区别!!!
*/
public class SynchronizedClassStatic implements Runnable{
static SynchronizedClassStatic synchronizedClassStatic1 = new SynchronizedClassStatic();
static SynchronizedClassStatic synchronizedClassStatic2 = new SynchronizedClassStatic();
public static synchronized void method(){
System.out.println("我是对象锁的代码块形式,name is "+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是对象锁的代码块形式,name is "+Thread.currentThread().getName()+"执行结束");
}
@Override
public void run() {
method();
}
public static void main(String[] args) {
Thread thread1 = new Thread(synchronizedClassStatic1);
Thread thread2 = new Thread(synchronizedClassStatic2);
thread1.start();
thread2.start();
while (thread1.isAlive() || thread2.isAlive()){
}
System.out.println("finish");
}
}
package synchronizedlearn;
/**
* 类锁的第二种形式
*/
public class SynchronizedClassClass implements Runnable{
static SynchronizedClassClass synchronizedClassClass1 = new SynchronizedClassClass();
static SynchronizedClassClass synchronizedClassClass2 = new SynchronizedClassClass();
public void method() {
synchronized (SynchronizedClassClass.class){
System.out.println("我是类锁的第二种形式,---->"+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("该线程"+Thread.currentThread().getName()+"执行完毕");
}
}
public static void main(String[] args) {
Thread thread1 = new Thread(synchronizedClassClass1);
Thread thread2 = new Thread(synchronizedClassClass2);
thread1.start();
thread2.start();
while (thread1.isAlive() || thread2.isAlive()){
}
System.out.println("finish");
}
@Override
public void run() {
method();
}
}
案例三:同时访问静态synchronized方法和非静态synchronized方法 类锁和对象锁不是锁住的同一个对象相互不影响
实例方法和静态方法分别锁的是该类的实例对象和该类的对象。从这个案例中我们可以看出,该类的实例对象和该类对象不是同一个。
package synchronizedlearn;
/**
* 同时访问静态synchronized方法和非静态synchronized方法
*/
public class SynchronizedStaticAndNormal implements Runnable{
static SynchronizedStaticAndNormal synchronizedStaticAndNormal = new SynchronizedStaticAndNormal();
@Override
public void run() {
if (Thread.currentThread().getName().equals("Thread-0")){
method1();
}else{
method2();
}
}
public static synchronized void method1(){
System.out.println("我是静态加锁的方法,name is "+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是加锁的方法,name is "+Thread.currentThread().getName()+"执行结束");
}
public synchronized void method2(){
System.out.println("我是非静态加锁的方法,name is "+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是加锁的方法,name is "+Thread.currentThread().getName()+"执行结束");
}
public static void main(String[] args) {
Thread thread1 = new Thread(synchronizedStaticAndNormal);
Thread thread2 = new Thread(synchronizedStaticAndNormal);
thread1.start();
thread2.start();
while (thread1.isAlive() || thread2.isAlive()){
}
System.out.println("finish");
}
}
案例 四:同时访问同一对象的不同的同步方法:锁生效
package synchronizedlearn;
/**
* 同时访问同一个对象的不同的普通同步方法
*/
public class SynchronizedDifferenceMethod implements Runnable {
static SynchronizedDifferenceMethod synchronizedDifferenceMethod = new SynchronizedDifferenceMethod();
@Override
public void run() {
if (Thread.currentThread().getName().equals("Thread-0")){
method1();
}else{
method2();
}
}
public synchronized void method1(){
System.out.println("我是加锁的方法,name is "+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是加锁的方法,name is "+Thread.currentThread().getName()+"执行结束");
}
public synchronized void method2(){
System.out.println("我是加锁的方法,name is "+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是加锁的方法,name is "+Thread.currentThread().getName()+"执行结束");
}
public static void main(String[] args) {
Thread thread1 = new Thread(synchronizedDifferenceMethod);
Thread thread2 = new Thread(synchronizedDifferenceMethod);
thread1.start();
thread2.start();
while (thread1.isAlive() || thread2.isAlive()){
}
System.out.println("finish");
}
}
案例五:同时访问同步方法和非同步方法,锁是什么情况? 非同步方法不受影响
/**
* 同时访问同步方法和非同步方法(同步方法指的是被Synchronized修饰的方法)
*
*/
public class SynchronizedYesAndNo implements Runnable {
static SynchronizedYesAndNo synchronizedYesAndNo = new SynchronizedYesAndNo();
@Override
public void run() {
if (Thread.currentThread().getName().equals("Thread-0")){
method1();
}else{
method2();
}
}
public synchronized void method1(){
System.out.println("我是加锁的方法,name is "+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是加锁的方法,name is "+Thread.currentThread().getName()+"执行结束");
}
public void method2(){
System.out.println("我是没加锁的方法,name is "+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是没加锁的方法,name is "+Thread.currentThread().getName()+"执行结束");
}
public static void main(String[] args) {
Thread thread1 = new Thread(synchronizedYesAndNo);
Thread thread2 = new Thread(synchronizedYesAndNo);
thread1.start();
thread2.start();
while (thread1.isAlive() || thread2.isAlive()){
}
System.out.println("finish");
}
}
关于synchronized的总结:
两个线程同时访问一个对象的同步方法 --->相互等待,锁生效。 两个线程访问两个对象的同步方法 --->相互没有影响,锁无效 两个线程访问synchronized的静态方法 --->即使实例不同,锁也生效。 同时访问同步方法与非同步方法 ---> 非同步方法不受影响 同时访问同一个对象的不同的普通同步方法 ---> 锁生效 同时访问静态synchronized方法和非静态synchronized方法 --->类锁和对象锁不是锁住的同一个对象相互不影响 方法抛出异常后会释放锁