文章目录
- 一、同步方法
- (1)同步方法--案例1
- 1、案例1
- 2、案例1之同步监视器
- (2)同步方法--案例2
- 1、案例2之同步监视器的问题
- 2、案例2的补充说明
- 二、代码及重要说明
- (1)代码
- (2)重要说明
一、同步方法
同步方法:synchronized
关键字直接修饰方法,表示同一时刻只有一个线程能进入这个方法,其他线程在外面等着。
🗳️格式:
public synchronized void method(){可能会产生线程安全问题的代码
}
(1)同步方法–案例1
1、案例1
还是拿这个例子来说,方式一实现Runnable接口
,如下:
🌱代码
package yuyi02;/*** ClassName: WindowTset2* Package: yuyi02* Description:* 使用同步方法解决实现Runnable接口的线程安全问题* @Author 雨翼轻尘* @Create 2024/1/30 0030 9:52*/
public class WindowTest2 {public static void main(String[] args) {//3.创建当前实现类的对象SaleTicket2 s=new SaleTicket2();//4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的实例Thread t1 = new Thread(s);Thread t2 = new Thread(s);Thread t3 = new Thread(s);//给三个线程起名字t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");//5.通过Thread类的实例调用start():1.启动线程 2.调用当前线程的run()。t1.start();t2.start();t3.start();}
}class SaleTicket2 implements Runnable{ //卖票 1.创建一个实现Runnable接口的类(实现类)int ticket=100;@Overridepublic void run() { //2.实现接口中的抽象方法run()方法while (true){if(ticket>0){ //如果票数大于0就可以售票try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}//哪个窗口卖票了,票卖了多少System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket); //最开始票号为100ticket--;}else{break;}}}
}
🍺输出结果(部分)
可以看到,出现了重票和错票。
现在来解决这个安全问题。
操作ticket
的代码:
现在将他们完全声明在一个方法show()
当中,然后在while里面调用show()
方法。比如:
我们可以将while
里面的show()
用synchronized
包裹,就是同步代码块的方式,如下:
public void run() { //2.实现接口中的抽象方法run()方法while (true){synchronized (this) {show();}}
}
当然也可以将show
方法声明为同步方法。
现在这里有点错误,就是break
的问题。之前是在while里面写的,现在将if-else
从while里面抽出来了,所以break就不行了。
将break直接删掉吗?不行,这样的话程序就不能自己结束了。如下:
我们可以声明一个变量isFlag
,初始化为true。如下:
🌱代码
public class WindowTest2 {public static void main(String[] args) {//3.创建当前实现类的对象SaleTicket2 s=new SaleTicket2();//4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的实例Thread t1 = new Thread(s);Thread t2 = new Thread(s);Thread t3 = new Thread(s);//给三个线程起名字t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");//5.通过Thread类的实例调用start():1.启动线程 2.调用当前线程的run()。t1.start();t2.start();t3.start();}
}class SaleTicket2 implements Runnable{ //卖票 1.创建一个实现Runnable接口的类(实现类)int ticket=100;boolean isFlag=true;public void show(){if(ticket>0){ //如果票数大于0就可以售票try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}//哪个窗口卖票了,票卖了多少System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket); //最开始票号为100ticket--;}else{isFlag=false;}}@Overridepublic void run() { //2.实现接口中的抽象方法run()方法while (isFlag){synchronized (this) {show();}}}
}
🍺输出结果(部分)
但是现在还是用的“同步代码块”来解决问题。
现在操作ticket的代码完全写在了show()
方法里面,那么将这个show
方法加一个同步
即可,就是直接加一个synchronized
,如下:
🌱代码
public class WindowTest2 {public static void main(String[] args) {//3.创建当前实现类的对象SaleTicket2 s=new SaleTicket2();//4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的实例Thread t1 = new Thread(s);Thread t2 = new Thread(s);Thread t3 = new Thread(s);//给三个线程起名字t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");//5.通过Thread类的实例调用start():1.启动线程 2.调用当前线程的run()。t1.start();t2.start();t3.start();}
}class SaleTicket2 implements Runnable{ //卖票 1.创建一个实现Runnable接口的类(实现类)int ticket=100;boolean isFlag=true;public synchronized void show(){ //此时的同步监视器就是:this 。此题目中是s,是唯一的,线程安全if(ticket>0){ //如果票数大于0就可以售票try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}//哪个窗口卖票了,票卖了多少System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket); //最开始票号为100ticket--;}else{isFlag=false;}}@Overridepublic void run() { //2.实现接口中的抽象方法run()方法while (isFlag){show();}}
}
🍺输出结果(部分)
可以。
2、案例1之同步监视器
上面的show()方法中,我们没有显示得去写同步监视器,其实它是默认的。
针对于这个同步方法,若这个方法是非静态的,那么这个同步监视器默认的就是this
。
这个this
是改不了的,我们只能考虑这个this是不是唯一的。
🎲此时this
是唯一的吗?
是唯一的。因为现在实在当前实现方式里面写的,类SaleTicket2
的对象只造了一个,并且被多个线程所共用。所以调用方法的时候,只有唯一的对象s。
如下:
所以线程是安全的,没有问题。
(2)同步方法–案例2
1、案例2之同步监视器的问题
还是拿这个例子来说,方式二继承Thread类
,如下:
🌱代码
package yuyi02;/*** ClassName: WindowTest3* Package: yuyi02* Description:* 使用同步方法解决继承Thread类的线程安全问题* @Author 雨翼轻尘* @Create 2024/1/30 0030 11:03*/
public class WindowTest3 {public static void main(String[] args) {//3.创建3个窗口 创建当前Thread的子类的对象Window w1=new Window();Window w2=new Window();Window w3=new Window();//命名w1.setName("窗口1");w2.setName("窗口2");w3.setName("窗口3");//4.通过对象调用start(): 1.启动线程 2.调用当前线程的run()方法w1.start();w2.start();w3.start();}
}class Window extends Thread{ //卖票 1.创建一个继承于Thread类的子类//票static int ticket=100;//2.重写Thread类的run() —>将此线程要执行的操作,声明在此方法体中@Overridepublic void run() {while (true){if(ticket>0){ //如果票数大于0就可以售票try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}//哪个窗口卖票了,票卖了多少System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket); //最开始票号为100ticket--;}else{break;}}}
}
🍺输出结果(部分)
出现了重票的问题。
现在来解决这个安全问题。
操作ticket
的代码:
将上述操作ticket
的代码放在方法show1()
中,如下:
跟上一个案例类似,将break去掉,加一个isFlag1
,如下:
当然,isFlag都要共用一个,所以需要加上static
,如下:
🌱代码
public class WindowTest3 {public static void main(String[] args) {//3.创建3个窗口 创建当前Thread的子类的对象Window w1=new Window();Window w2=new Window();Window w3=new Window();//命名w1.setName("窗口1");w2.setName("窗口2");w3.setName("窗口3");//4.通过对象调用start(): 1.启动线程 2.调用当前线程的run()方法w1.start();w2.start();w3.start();}
}class Window extends Thread{ //卖票 1.创建一个继承于Thread类的子类//票static int ticket=100;static boolean isFlag1=true;//2.重写Thread类的run() —>将此线程要执行的操作,声明在此方法体中@Overridepublic void run() {while (isFlag1){show1();}}public void show1(){if(ticket>0){ //如果票数大于0就可以售票try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}//哪个窗口卖票了,票卖了多少System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket); //最开始票号为100ticket--;}else{isFlag1=false;}}
}
🍺输出结果(部分)
现在还没有解决线程安全问题,所以输出结果还是有重票的,如下:
现在我们直接给show1()
方法加上synchronized
,可以吗?
如下:
🌱代码
public class WindowTest3 {public static void main(String[] args) {//3.创建3个窗口 创建当前Thread的子类的对象Window w1=new Window();Window w2=new Window();Window w3=new Window();//命名w1.setName("窗口1");w2.setName("窗口2");w3.setName("窗口3");//4.通过对象调用start(): 1.启动线程 2.调用当前线程的run()方法w1.start();w2.start();w3.start();}
}class Window extends Thread{ //卖票 1.创建一个继承于Thread类的子类//票static int ticket=100;static boolean isFlag1=true;//2.重写Thread类的run() —>将此线程要执行的操作,声明在此方法体中@Overridepublic void run() {while (isFlag1){show1();}}public synchronized void show1(){ //非静态同步方法,此时同步监视器就是this,此问题中的this有:w1,w2,w3if(ticket>0){ //如果票数大于0就可以售票try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}//哪个窗口卖票了,票卖了多少System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket); //最开始票号为100ticket--;}else{isFlag1=false;}}
}
🍺输出结果(部分)
此时是非静态同步方法,同步监视器就是this
,此问题中的this有:w1,w2,w3(当前类的对象造了三个)。所以肯定不行。
2、案例2的补充说明
上面那个既然不行,那该怎么办呢?
把show()
方法改成静态的吗?如下:
🌱代码
public class WindowTest3 {public static void main(String[] args) {//3.创建3个窗口 创建当前Thread的子类的对象Window w1 = new Window();Window w2 = new Window();Window w3 = new Window();//命名w1.setName("窗口1");w2.setName("窗口2");w3.setName("窗口3");//4.通过对象调用start(): 1.启动线程 2.调用当前线程的run()方法w1.start();w2.start();w3.start();}
}class Window extends Thread { //卖票 1.创建一个继承于Thread类的子类//票static int ticket = 100;static boolean isFlag1 = true;//2.重写Thread类的run() —>将此线程要执行的操作,声明在此方法体中@Overridepublic void run() {while (isFlag1) {show1();}}public static synchronized void show1() { //静态方法的同步监视器是:当前类,就是Window.classif (ticket > 0) { //如果票数大于0就可以售票try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}//哪个窗口卖票了,票卖了多少System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket); //最开始票号为100ticket--;} else {isFlag1 = false;}}}
🍺输出结果(部分)
现在这个案例加上static
是可行的。
静态方法的同步监视器是当前类
本身,就是Window.class
(这里是一个对象,一个值,不是类),是唯一的。所以现在是安全的。
🍰说明
这个方法能不能改成静态的,需要看具体的问题。适合就可以改,不适合就不要改了。
若有的方法就是一个实例方法
,里面要用实例变量,那就不适合改,同步方法就不靠谱了。
所以这里不要刻意去满足同步方法让它去达到我们的要求(不要为了线程安全,去特意将方法改为静态的)。
有的时候这个方法就不适合加上静态,同步方法就不适合去做了,就不要使用同步方法了。
那我们就主动将操作ticket
的代码用synchronized
包裹一下,然后指定一个同步监视器即可。
二、代码及重要说明
(1)代码
①【使用同步方法解决实现Runnable接口的线程安全问题】
🌱代码
package yuyi02;/*** ClassName: WindowTset2* Package: yuyi02* Description:* 使用同步方法解决实现Runnable接口的线程安全问题* @Author 雨翼轻尘* @Create 2024/1/30 0030 9:52*/
public class WindowTest2 {public static void main(String[] args) {//3.创建当前实现类的对象SaleTicket2 s=new SaleTicket2();//4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的实例Thread t1 = new Thread(s);Thread t2 = new Thread(s);Thread t3 = new Thread(s);//给三个线程起名字t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");//5.通过Thread类的实例调用start():1.启动线程 2.调用当前线程的run()。t1.start();t2.start();t3.start();}
}class SaleTicket2 implements Runnable{ //卖票 1.创建一个实现Runnable接口的类(实现类)int ticket=100;boolean isFlag=true;public synchronized void show(){ //此时的同步监视器就是:this 。此题目中是s,是唯一的,线程安全if(ticket>0){ //如果票数大于0就可以售票try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}//哪个窗口卖票了,票卖了多少System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket); //最开始票号为100ticket--;}else{isFlag=false;}}@Overridepublic void run() { //2.实现接口中的抽象方法run()方法while (isFlag){show();}}
}
②【使用同步方法解决继承Thread类
的线程安全问题】
🌱代码
package yuyi02;/*** ClassName: WindowTest3* Package: yuyi02* Description:* 使用同步方法解决继承Thread类的线程安全问题* @Author 雨翼轻尘* @Create 2024/1/30 0030 11:03*/
public class WindowTest3 {public static void main(String[] args) {//3.创建3个窗口 创建当前Thread的子类的对象Window w1 = new Window();Window w2 = new Window();Window w3 = new Window();//命名w1.setName("窗口1");w2.setName("窗口2");w3.setName("窗口3");//4.通过对象调用start(): 1.启动线程 2.调用当前线程的run()方法w1.start();w2.start();w3.start();}
}class Window extends Thread { //卖票 1.创建一个继承于Thread类的子类//票static int ticket = 100;static boolean isFlag1 = true;//2.重写Thread类的run() —>将此线程要执行的操作,声明在此方法体中@Overridepublic void run() {while (isFlag1) {show1();}}public static synchronized void show1() { //非静态同步方法,此时同步监视器就是this,此问题中的this有:w1,w2,w3,线程仍然不安全,加一个staticif (ticket > 0) { //如果票数大于0就可以售票try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}//哪个窗口卖票了,票卖了多少System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket); //最开始票号为100ticket--;} else {isFlag1 = false;}}}
(2)重要说明
【同步方法】
🗳️格式:
public synchronized void method(){可能会产生线程安全问题的代码
}
🚗说明
- 如果操作共享数据的代码(需要被同步的代码)完整的声明在了一个方法中,那么我们就可以将此方法声明为
同步方法
即可。 - 非静态的同步方法,默认同步监视器是
this
;静态的同步方法,默认同步监视器是当前类本身
。
☕注意
现在咱们线程一共说了这么几件事情,如下:
下面来看一下这个关键字:synchronized
(同步的)
- 好处:解决了线程的安全问题。
- 弊端:在操作共享数据时,多线程其实是串行执行的,意味着性能低。
卖票:三个线程来做,交互去执行。
当一个线程还没有操作完,其他线程也过来了,就会出现安全问题。
执行前面代码的时候,三个线程没有共享数据,同时执行也没有问题。
但是在执行共享数据的代码的时候,只能让一个线程进去,其他线程在外面等着。
也就是说,执行前面代码的时候,三个线程可以并发执行,但是在操作共享数据的时候,一定是串行的去执行,也就是只有一个线程可以进去执行。所以性能会差一点。