文章目录
- 一、同步方法
- (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;
@Override
public 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); //最开始票号为100
ticket--;
}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); //最开始票号为100
ticket--;
}else{
isFlag=false;
}
}
@Override
public 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); //最开始票号为100
ticket--;
}else{
isFlag=false;
}
}
@Override
public 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() —>将此线程要执行的操作,声明在此方法体中
@Override
public void run() {
while (true){
if(ticket>0){ //如果票数大于0就可以售票
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//哪个窗口卖票了,票卖了多少
System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket); //最开始票号为100
ticket--;
}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() —>将此线程要执行的操作,声明在此方法体中
@Override
public 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); //最开始票号为100
ticket--;
}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() —>将此线程要执行的操作,声明在此方法体中
@Override
public void run() {
while (isFlag1){
show1();
}
}
public synchronized void show1(){ //非静态同步方法,此时同步监视器就是this,此问题中的this有:w1,w2,w3
if(ticket>0){ //如果票数大于0就可以售票
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//哪个窗口卖票了,票卖了多少
System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket); //最开始票号为100
ticket--;
}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() —>将此线程要执行的操作,声明在此方法体中
@Override
public void run() {
while (isFlag1) {
show1();
}
}
public static synchronized void show1() { //静态方法的同步监视器是:当前类,就是Window.class
if (ticket > 0) { //如果票数大于0就可以售票
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//哪个窗口卖票了,票卖了多少
System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket); //最开始票号为100
ticket--;
} 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); //最开始票号为100
ticket--;
}else{
isFlag=false;
}
}
@Override
public 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() —>将此线程要执行的操作,声明在此方法体中
@Override
public void run() {
while (isFlag1) {
show1();
}
}
public static synchronized void show1() { //非静态同步方法,此时同步监视器就是this,此问题中的this有:w1,w2,w3,线程仍然不安全,加一个static
if (ticket > 0) { //如果票数大于0就可以售票
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//哪个窗口卖票了,票卖了多少
System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket); //最开始票号为100
ticket--;
} else {
isFlag1 = false;
}
}
}
(2)重要说明
【同步方法】
🗳️格式:
public synchronized void method(){
可能会产生线程安全问题的代码
}
🚗说明
- 如果操作共享数据的代码(需要被同步的代码)完整的声明在了一个方法中,那么我们就可以将此方法声明为
同步方法
即可。 - 非静态的同步方法,默认同步监视器是
this
;静态的同步方法,默认同步监视器是当前类本身
。
☕注意
现在咱们线程一共说了这么几件事情,如下:
下面来看一下这个关键字:synchronized
(同步的)
- 好处:解决了线程的安全问题。
- 弊端:在操作共享数据时,多线程其实是串行执行的,意味着性能低。
卖票:三个线程来做,交互去执行。
当一个线程还没有操作完,其他线程也过来了,就会出现安全问题。
执行前面代码的时候,三个线程没有共享数据,同时执行也没有问题。
但是在执行共享数据的代码的时候,只能让一个线程进去,其他线程在外面等着。
也就是说,执行前面代码的时候,三个线程可以并发执行,但是在操作共享数据的时候,一定是串行的去执行,也就是只有一个线程可以进去执行。所以性能会差一点。