概述
synchronized关键字,被synchronized修饰的区域,只有一个线程 可以访问,用来实现 线程同步,处理 多线程 访问修改 共享资源。也就是:线程安全
synchronized通过锁机制实现
使用方式
- 修饰方法
- 修饰代码块
通过修饰this实现 - 修饰对象
通过修饰代码块实现。通过修饰 其他对象 实现 - 修饰类
通过修饰代码块实现。通过修饰 当前类名.class 实现 - 修饰静态方法
使用注意
在
synchronized代码块内做判断,否则判断可能会失效,如:多卖票不能在
synchronized方法中执行循环,会造成只有一个线程循环执行,其他线程不执行
锁
类锁
用来锁类的,一个类的所有对象共享一个class对象,共享一组静态方法,类锁的作用就是使持有者可以同步地调用静态方法。
当synchronized指定修饰 静态方法 或者 class对象 时,拿到的就是类锁
类锁是所有对象共同争抢一把锁
对象锁
用来锁对象的,虚拟机为每个的非静态方法和非静态域都分配了自己的空间
所以synchronized修饰 非静态方法 或者 this 的时候拿到的就是对象锁,对象锁是每个对象各有一把的
修饰方法
同一个对象 的方法,只有一个线程可以访问,其作用范围是整个方法
注意
- 接口方法时不能使用synchronized关键字;
- 构造方法不能使用synchronized关键字,但可以使用
synchronized代码块进行同步; synchronized关键字无法继承;- 如果在父类中的某个方法使用了
synchronized关键字,而在子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的。 - 子类方法同步的解决方案
- 子类方法也加上
synchronized关键字 - 子类方法中调用父类同步的方法,例如:使用 super.xxxMethod()调用父类方法
- 子类方法也加上
- 如果在父类中的某个方法使用了
例子-修饰对象
有bug代码-Runnable方式
public class RunnableImpl7 implements Runnable {
private Object lock = new Object();
public final int COUNT = 100;
private static int ticket = 1;
@Override
public void run() {
while (ticket <= COUNT) {
// 有多个线程等待执行此代码块,只有一个线程在执行,该线程执行后,假设票号码是100,但其他线程还会继续执行此代码块,导致多卖票了
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + "-售出票号:" + ticket);
ticket++;
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
Runnable r=new RunnableImpl7();
Thread t1=new Thread(r,"窗口1");
Thread t2=new Thread(r,"窗口2");
t1.start();
t2.start();
}
}
执行结果:
窗口1-售出票号:96
窗口1-售出票号:97
窗口1-售出票号:98
窗口1-售出票号:99
窗口2-售出票号:100
窗口2-售出票号:101 // 卖票超出上限
窗口1-售出票号:102 // 卖票超出上限
无bug-Runnable方式
关键: 要在 synchronized 代码块的里面加判断
public class T3 implements Runnable {
private Object o1 = new Object();
public final int COUNT = 100;
private static int ticket = 1;
@Override
public void run() {
while (true) {
synchronized (o1) {
if(ticket > COUNT){ // 只有一个线程执行此代码,判断成功就继续往下执行,否则就终止,不会多卖票
break;
}
System.out.println(Thread.currentThread().getName() + "-售出票号:" + ticket);
ticket++;
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
Runnable r=new T3();
Thread t1=new Thread(r,"窗口1");
Thread t2=new Thread(r,"窗口2");
Thread t3=new Thread(r,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
有bug-Thread方式
package test;
public class 线程同步bug extends Thread{
private Object lock = new Object();
public final int COUNT = 100;
private static int ticket = 1;
@Override
public void run() {
while (ticket <= COUNT) {
// 创建多个对象,每个对象都有 lock 锁对象,每个线程使用自己的 lock 锁,所以线程同步失败,应该共用一个锁
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + "-售出票号:" + ticket);
ticket++;
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
// 创建多个对象,每个对象都有 lock 锁对象
Thread t1=new 线程同步bug();
Thread t2=new 线程同步bug();
t1.start();
t2.start();
}
}
有bug-Thread方式
package test;
public class Thread线程同步bug2 extends Thread{
// 多个对象,共用一把锁
private static Object lock = new Object();
public final int COUNT = 100;
private static int ticket = 1;
@Override
public void run() {
while (ticket <= COUNT) {
// 有多个线程等待执行此代码块,只有一个线程在执行,该线程执行后,假设票号码是100,但其他线程还会继续执行此代码块,导致多卖票了
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + "-售出票号:" + ticket);
ticket++;
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
Thread t1=new Thread线程同步bug2();
Thread t2=new Thread线程同步bug2();
t1.start();
t2.start();
}
}
无bug-Thread方式
package test;
public class Thread线程同步无bug extends Thread{
private static Object lock = new Object();
public final int COUNT = 100;
private static int ticket = 1;
@Override
public void run() {
while (true) {
synchronized (lock) {
if(ticket > COUNT){ // 只有一个线程执行此代码,判断成功就继续往下执行,否则就终止,不会多卖票
break;
}
System.out.println(Thread.currentThread().getName() + "-售出票号:" + ticket);
ticket++;
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
Thread t1=new Thread线程同步无bug();
Thread t2=new Thread线程同步无bug();
t1.start();
t2.start();
}
}
例子-修饰方法
有bug的代码1
定义线程接口类
public class T1 implements Runnable {
public final int COUNT=100;
private int ticket=1;
@Override
public void run() {
while(ticket<=COUNT){
handle();//可能有多个线程在同时执行到此行,但只有一个线程进入该方法,该线程执行后,假设票号码已经等于100,其他线程还会继续进入该方法,卖票超出上限
};
}
private synchronized void handle(){
System.out.println(Thread.currentThread().getName() + "-售出票号:" + ticket);
ticket++;
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
T1 ri=new T1();
Thread t1=new Thread(ri,"窗口1");
Thread t2=new Thread(ri,"窗口2");
t1.start();
t2.start();
}
}
执行结果:
窗口1-售出票号:96
窗口1-售出票号:97
窗口1-售出票号:98
窗口1-售出票号:99
窗口2-售出票号:100
窗口2-售出票号:101 // 卖票超出上限
窗口1-售出票号:102 // 卖票超出上限
有bug的代码2
线程接口类
public class RunnableImpl2 implements Runnable {
public final int COUNT=100;
private int ticket=1;
@Override
public void run() {
handle();
}
// 同一时刻只有一个线程访问该方法,所以该方法会一直被当前线程执行,不能在 `synchronized` 方法中执行循环
private synchronized void handle(){
while(ticket<=COUNT){
System.out.println(Thread.currentThread().getName()+"-售出票号:"+ticket);
ticket++;
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
执行结果:只有一个窗口卖票
当 synchronized 修饰的方法时,同一时刻只有一个线程访问该方法,所以该方法会一直被当前线程执行
注意:
临界区的选择
无 bug 代码
关键: 要在 synchronized 代码块的里面加判断
public class T1 implements Runnable {
public final int COUNT=100;
private int ticket=1;
@Override
public void run() {
while(ticket<=COUNT){
handle();//可能有多个线程在同时执行到此行,但只有一个线程进入该方法,该线程执行后,假设i的值已经等于100,其他线程还会继续进入该方法
}
}
private synchronized void handle(){
if(ticket<=COUNT) {//只有一个线程执行此代码,判断成立就继续执行,否则就不执行,才有效控制,不会多卖票
System.out.println(Thread.currentThread().getName() + "-售出票号:" + ticket);
ticket++;
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
T1 ri=new T1();
Thread t1=new Thread(ri,"窗口1");
Thread t2=new Thread(ri,"窗口2");
Thread t3=new Thread(ri,"窗口2");
t1.start();
t2.start();
t3.start();
}
}
有bug代码3
在上面正确的代码中,修改测试类,如下:
public class Main3 {
public static void main(String[] args) {
RunnableImpl3 ri=new RunnableImpl3();
RunnableImpl3 ri2=new RunnableImpl3();
Thread t1=new Thread(ri,"窗口1");
Thread t2=new Thread(ri2,"窗口2");
t1.start();
t2.start();
}
}
执行结果:
窗口1-售出票号:1
窗口2-售出票号:1
窗口2-售出票号:2
窗口1-售出票号:2
窗口1-售出票号:3
窗口1、窗口2会卖相同的票
当 synchronized 修饰普通方法时,同一个对象 的方法,只有一个线程可以访问,但创建2个对象,就无法控制了。
可以用 修饰类 或 修饰静态方法 解决
修饰代码块-修饰this
同一个对象 的 代码块,只有一个线程可以访问,其作用范围是代码块
有bug代码1
public class RunnableImpl4 implements Runnable {
public final int COUNT=100;
private int ticket=1;
@Override
public void run() {
while(ticket<=COUNT){
synchronized(this) {//有多个线程等待执行此代码块,只有一个线程在执行,该线程执行后,假设i是100,但其他线程还会继续执行此代码块
System.out.println(Thread.currentThread().getName() + "-售出票号:" + ticket);
ticket++;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
}
public static void main(String[] args) {
Runnable r=new RunnableImpl4();
Thread t1=new Thread(r,"窗口1");
Thread t2=new Thread(r,"窗口2");
Thread t3=new Thread(r,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
执行结果:
窗口1-售出票号:96
窗口1-售出票号:97
窗口1-售出票号:98
窗口1-售出票号:99
窗口2-售出票号:100
窗口2-售出票号:101 // 卖票超出上限
窗口1-售出票号:102 // 卖票超出上限
有bug的代码2
public class RunnableImpl4 implements Runnable {
public final int COUNT=100;
private int ticket=1;
@Override
public void run() {
synchronized(this) {
while(ticket<=COUNT){
System.out.println(Thread.currentThread().getName() + "-售出票号:" + ticket);
ticket++;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
}
}
执行结果:
只有一个窗口卖票。
当 while 代码块在 synchronized 代码块内部时,同一时刻只有一个线程访问该代码块,也就是这个循环,一直被当前线程执行
注意:
临界区的选择
无 bug
关键: 要在 synchronized 代码块的里面加判断
package que;
public class T2 implements Runnable {
public final int COUNT=100;
private int ticket=1;
@Override
public void run() {
while(true){
synchronized(this) {//有多个线程等待执行此代码块,只有一个线程在执行
if(ticket>COUNT) {//在单线程环境中进行判断
break;
}
System.out.println(Thread.currentThread().getName() + "-售出票号:" + ticket);
ticket++;
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
}
public static void main(String[] args) {
Runnable r=new T2();
Thread t1=new Thread(r,"窗口1");
Thread t2=new Thread(r,"窗口2");
Thread t3=new Thread(r,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
修饰类
通过 修饰代码块 实现。要修饰 当前类名.class
特点:可以创建多个接口对象
无bug
public class T4 implements Runnable {
public final int COUNT=100;
private static int ticket=1;
@Override
public void run() {
while(true){
synchronized(T4.class) {
if(ticket>COUNT){
break;
}
System.out.println(Thread.currentThread().getName() + "-售出票号:" + ticket);
ticket++;
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
}
public static void main(String[] args) {
//创建多个Runnable对象,但执行结果仍然正确,是因为 synchronized 修饰类,该类创建的 所有对象,**只有一个线程执行该代码块**
Runnable r=new T4();
Runnable r2=new T4();
Runnable r3=new T4();
Thread t1=new Thread(r,"窗口1");
Thread t2=new Thread(r2,"窗口2");
Thread t3=new Thread(r3,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
有bug代码
public class RunnableImpl5 implements Runnable {
public final int COUNT=100;
private static int ticket=1;
@Override
public void run() {
while(ticket<=COUNT){
synchronized(RunnableImpl5.class) {
System.out.println(Thread.currentThread().getName() + "-售出票号:" + ticket);
ticket++;
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
}
public static void main(String[] args) {
Runnable r=new T4();
Runnable r2=new T4();
Runnable r3=new T4();
Thread t1=new Thread(r,"窗口1");
Thread t2=new Thread(r2,"窗口2");
Thread t3=new Thread(r3,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
修饰静态方法
无bug
public class T5 implements Runnable {
public static final int COUNT = 100;
private static int ticket = 1;
@Override
public void run() {
while (ticket <= COUNT) {
handle();
}
}
public synchronized static void handle() {
if(ticket <= COUNT) {
System.out.println(Thread.currentThread().getName() + "-售出票号:" + ticket);
ticket++;
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
//
创建多个Runnable对象,但执行结果仍然正确,是因为 synchronized 修饰静态方法,该类创建的 所有对象,**只有一个线程执行该静态方法**
Runnable r=new T5();
Runnable r2=new T5();
Runnable r3=new T5();
Thread t1=new Thread(r,"窗口1");
Thread t2=new Thread(r2,"窗口2");
Thread t3=new Thread(r3,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
有bug代码
public class RunnableImpl6 implements Runnable {
public final int COUNT = 100;
private static int ticket = 1;
@Override
public void run() {
while (ticket <= COUNT) {
handle();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized static void handle() {
System.out.println(Thread.currentThread().getName() + "-售出票号:" + ticket);
ticket++;
}
public static void main(String[] args) {
Runnable r=new RunnableImpl5();
Runnable r2=new RunnableImpl5();
Thread t1=new Thread(r,"窗口1");
Thread t2=new Thread(r2,"窗口2");
t1.start();
t2.start();
}
}
感谢:https://blog.csdn.net/qq_38011415/article/details/89047812