内部锁:synchronized 关键字
Java 中每个对象都有一个与之关联的内部锁(intrinsic lock)。这种锁也称为监视器(Monitor),这种内部锁是一种排他锁,可以保障原子性,可见性与有序性。
内部锁是通过 synchronized 关键字实现的。synchronized 关键字修饰代码块,修饰方法。修饰实例方法就称为同步实例方法,修饰静态方法就称为同步静态方法。
修饰代码块的语法:
synchronized(对象锁){
同步代码块,可以在同步代码块中访问共享数据
}
- 同步代码块
使用this当前对象作为锁对象
package com.jason.java.duoxiancheng.intrinsiclock;
/**
* synchronized 同步代码块
* @author Jason
*/
public class Test01 {
public static void main(String[] args) {
//创建两个线程,分别调用mm()方法
Test01 obj = new Test01();
new Thread(new Runnable() {
@Override
public void run() {
obj.mm(); //使用的锁对象this就是obj对象
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
obj.mm();//使用的锁对象this也是obj对象
}
}).start();
}
//定义方法,打印100行字符串
public void mm(){
synchronized (this) {//经常使用this当前对象作为锁对象
for (int i = 0; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
}
}
}
输出结果:Thread-0先输出0-100,Thread-1后输出
如果线程的锁不同,则不能实现同步
package com.jason.java.duoxiancheng.intrinsiclock;
/**
* synchronized 同步代码块
* 如果线程的锁不同,则不能实现同步
* 想要同步必须使用用一个锁对象
*
* @author Jason
*/
public class Test02 {
public static void main(String[] args) {
//创建两个线程,分别调用mm()方法
Test02 obj = new Test02();
Test02 obj2 = new Test02();
new Thread(new Runnable() {
@Override
public void run() {
obj.mm(); //使用的锁对象this就是obj对象
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
obj2.mm();//使用的锁对象this是 obj2 对象
}
}).start();
}
//定义方法,打印100行字符串
public void mm() {
synchronized (this) {//经常使用this当前对象作为锁对象
for (int i = 0; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
}
}
}
输出结果:Thread-0 和 Thread-1 两个线程交替输出,因为两个线程执行mm()方法并没有使用同一个锁
使用一个常量对象作为锁对象
package com.jason.java.duoxiancheng.intrinsiclock;
/**
* synchronized 同步代码块
* 使用一个常量对象作为锁对象
* @author Jason
*/
public class Test03 {
public static void main(String[] args) {
//创建两个线程,分别调用mm()方法
Test03 obj = new Test03();
Test03 obj2 = new Test03();
new Thread(new Runnable() {
@Override
public void run() {
obj.mm(); //使用的锁对象this就是obj对象
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
obj2.mm();//使用的锁对象this是 obj2 对象
}
}).start();
}
public static final Object OBJ = new Object();
//定义方法,打印100行字符串
public void mm() {
synchronized (OBJ) {//使用一个常量对象作为锁对象
for (int i = 0; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
}
}
}
使用一个常量对象作为锁对象,不同方法中的代码也可以同步
package com.jason.java.duoxiancheng.intrinsiclock;
/**
* synchronized 同步代码块
* 使用一个常量对象作为锁对象,不同方法中的代码也可以同步
*
* @author Jason
*/
public class Test04 {
public static void main(String[] args) {
Test04 obj = new Test04();
Test04 obj2 = new Test04();
new Thread(new Runnable() {
@Override
public void run() {
obj.mm(); //使用的锁对象this就是obj对象
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
obj2.mm();//使用的锁对象this是 obj2 对象
}
}).start();
//第三个线程调用静态方法
new Thread(new Runnable() {
@Override
public void run() {
sm();
}
});
}
//定义一个常量
public static final Object OBJ = new Object();
//定义方法,打印100行字符串
public void mm() {
synchronized (OBJ) {//使用一个常量对象作为锁对象
for (int i = 0; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
}
}
public static void sm(){
synchronized (OBJ) {//使用一个常量对象作为锁对象
for (int i = 0; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
}
}
}
- 同步方法
同步实例方法,把整个方法作为同步代码块,默认的锁对象是 this 对象
package com.jason.java.duoxiancheng.intrinsiclock;
/**
* synchronized 同步实例方法
* 把整个方法作为同步代码块,默认的锁对象是 this 对象
* @author Jason
*/
public class Test05 {
public static void main(String[] args) {
//创建 Test05对象,通过对象名调用 mm()方法
Test05 obj = new Test05();
//一个线程调用 mm()方法
new Thread(new Runnable() {
@Override
public void run() {
obj.mm(); //使用的锁对象this就是obj对象
}
}).start();
//另一个线程调用mm22()方法
new Thread(new Runnable() {
@Override
public void run() {
obj.mm22();//使用的锁对象this是 obj2 对象
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
//使用的锁对象this是 new 创建的新对象,不是一个锁对象不能同步
new Test05().mm22();
}
}).start();
}
//定义方法,打印100行字符串
public void mm() {
synchronized (this) {//使用this当前对象作为锁对象
for (int i = 0; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
}
}
//使用 synchronized 修饰实例方法,同步实例方法,默认 this 作为锁对象
public synchronized void mm22(){
for (int i = 0; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
}
}
同步静态方法,默认的锁对象是当前类的运行时类对象
package com.jason.java.duoxiancheng.intrinsiclock;
/**
* synchronized 同步静态方法
* 把整个方法作为同步代码块,默认的锁对象是当前类的运行时类对象
* @author Jason
*/
public class Test06 {
public static void main(String[] args) {
Test06 obj = new Test06();
//一个线程调用 mm()方法
new Thread(new Runnable() {
@Override
public void run() {
obj.mm(); //使用的锁对象是 Test06.class
}
}).start();
//另一个线程调用mm22()方法
new Thread(new Runnable() {
@Override
public void run() {
Test06.mm22();//使用的锁对象是Test06.class
}
}).start();
}
//定义方法,打印100行字符串
public void mm() {
//使用当前类的运行时类对象作为锁对象,可以简单的理解为把 Test06 类的字节码文件作为锁对象
synchronized (Test06.class) {
for (int i = 0; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
}
}
//使用 synchronized 修饰静态方法,同步静态方法,默认运行时类 Test06.class 作为锁对象
public synchronized static void mm22(){
for (int i = 0; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
}
}
同步方法,执行效率低。不需要同步的操作代码也被迫成为同步操作 同步代码块,锁的粒度细,执行效率高,可以只同步需要同步操作的地方
package com.jason.java.duoxiancheng.intrinsiclock;
/**
* @author Jason
* 同步方法锁的粒度粗,执行效率低,同步代码块执行效率高
*/
public class Test07 {
public static void main(String[] args) {
Test07 obj = new Test07();
new Thread(new Runnable() {
@Override
public void run() {
obj.doLongTimeTask2();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
obj.doLongTimeTask2();
}
}).start();
}
//同步方法,执行效率低。不需要同步的操作代码也被迫成为同步操作
public synchronized void doLongTimeTask(){
try {
System.out.println("Task Begin");
Thread.sleep(3000);//模拟任务需要准备 3 秒 钟
System.out.println("开始同步");
for (int i = 0; i <= 10000; i++) {
System.out.println(Thread.currentThread().getName() + "--" + i);
}
System.out.println("Task End");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//同步代码块,锁的粒度细,执行效率高,可以只同步需要同步操作的地方
public void doLongTimeTask2(){
try {
System.out.println("Task Begin");
Thread.sleep(3000);//模拟任务需要准备 3 秒 钟
System.out.println("开始同步");
synchronized (this){
for (int i = 0; i <= 10000; i++) {
System.out.println(Thread.currentThread().getName() + "--" + i);
}
}
System.out.println("Task End");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- 脏读
package com.jason.java.duoxiancheng.intrinsiclock;
/**
* 脏读
* 出现读取属性值出现了一些意外,读取的是中间值,而不是修改之后的值
* 出现脏读的原因是 对共享数据的修改 与对共享数据的读取不同步
* 解决方法 不仅对修改数据的代码进行同步,还要对读取数据的代码块同步
*
* @author Jason
*/
public class Test08 {
public static void main(String[] args) throws InterruptedException {
PublicValue publicValue = new PublicValue();
//开启子线程设置用户名和密码
SubThread t1 = new SubThread(publicValue);
t1.start();
//为了确定设置成功
//如果 不 sleep 一下,main线程先执行,获取的就是旧值
Thread.sleep(100);
//在 main 线程中读取用户名,密码
//如果 getValue 不是同步方法,读取到的值可能是旧值,因为get值和set值没有同步
publicValue.getValue();
}
static class SubThread extends Thread {
private PublicValue publicValue;
public SubThread(PublicValue publicValue) {
this.publicValue = publicValue;
}
@Override
public void run() {
publicValue.setValue("ZDJ", "888");
}
}
static class PublicValue {
private String name = "jason";
private String pwd = "666";
public synchronized void getValue() {
System.out.println(Thread.currentThread().getName() + ", getter -- name: " + name + ",--pwd: " + pwd);
}
public synchronized void setValue(String name, String pwd) {
this.name = name;
try {
Thread.sleep(1000); //模拟操作 name 属性需要一定时间
} catch (InterruptedException e) {
e.printStackTrace();
}
this.pwd = pwd;
System.out.println(Thread.currentThread().getName() + ", setter --name:" + name + ", --pwd: " + pwd);
}
}
}
- 线程出现异常会自动释放锁
package com.jason.java.duoxiancheng.intrinsiclock;
/**
* 同步过程中线程出现异常,会自动释放锁对象
* @author Jason
*/
public class Test09 {
public static void main(String[] args) {
Test09 obj = new Test09();
//一个线程调用 mm()方法
new Thread(new Runnable() {
@Override
public void run() {
obj.mm(); //使用的锁对象是 Test09.class
}
}).start();
//另一个线程调用mm22()方法
new Thread(new Runnable() {
@Override
public void run() {
Test09.mm22();//使用的锁对象是Test09.class
}
}).start();
}
//定义方法,打印100行字符串
public void mm() {
//使用当前类的运行时类对象作为锁对象,可以简单的理解为把 Test09 类的字节码文件作为锁对象
synchronized (Test06.class) {
for (int i = 0; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
if (i == 50) {
Integer.parseInt("abc");
}
}
}
}
//使用 synchronized 修饰静态方法,同步静态方法,默认运行时类 Test09.class 作为锁对象
public synchronized static void mm22(){
for (int i = 0; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
}
}
Thread-0 输出到50的时候释放锁,停止输出,开始走 Thread-1
- 死锁
有点没太懂,回头再看看
package com.jason.java.duoxiancheng.intrinsiclock;
/**
* 死锁
* 在多线程程序中,同步时可能需要使用多个锁,如果获得锁的顺序不一致,可能会导致死锁
* 如何避免死锁 当需要获得多个锁的时候,所有线程获得锁的顺序保持一致即可
*
* @author Jason
*/
public class Test10 {
public static void main(String[] args) {
SubThread t1 = new SubThread();
t1.setName("a");
t1.start();
SubThread t2 = new SubThread();
t2.setName("b");
t2.start();
}
static class SubThread extends Thread {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
@Override
public void run() {
if ("a".equals(Thread.currentThread().getName())) {
synchronized (lock1) {
System.out.println("a 线程获得了 lock1 锁,还需 要获得 lock2 锁");
synchronized (lock2) {
System.out.println("a 线程获得 lock1 后又 获得了 lock2,可以想干任何想干的事");
}
}
}
if ("b".equals(Thread.currentThread().getName())) {
synchronized (lock2) {
System.out.println("a 线程获得了 lock2 锁,还需 要获得 lock1 锁");
synchronized (lock1) {
System.out.println("a 线程获得 lock2 后又 获得了 lock1,可以想干任何想干的事");
}
}
}
}
}
}