1 同步函数
2 同步代码块
3 锁
另外,这篇文章尝试了一种新的行文风格,轻喷。
1、多线程存在的一个问题:当多个线程存在时,可能会出现同步问题。
例如,在某一个线程休眠的时候,可能其他线程会进入if语句继续执行,而当这个线程苏醒继续执行剩余的代码,程序的状态已经发生巨大变化,继续执行就会导致无法预计的后果。
例如,常见的卖票程序,总共100张票,在某个线程休眠之后,后进来的接着执行,会出现-1,-2号票。
//创建Ticket类,实现Runnable方法
public class Ticket implements Runnable{
private int ticketCount = 100;
public void run(){
while(true){
if(ticketCount>0){//该语句未生效,结果中出现了0,-1,-2号票
try{
Thread.sleep(10);//先进来的线程休眠,后进来的接着执行,可能出现异常
}catch(InterruptException){
e.printStack();
}
System.out.println(Thread.currentThread().getName()+"------"+ticketCount--);
}
}
}
}
public static void main(String args[]){
Ticket t = new Ticket();
//启动四个线程,共享Ticket
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
}
image.png
2、解决这个问题
常见的方法是使用Java提供的同步代码块,语法如下:
synchronized(对象){
}
修改后代码如下:
//创建Ticket类,实现Runnable方法
public class Ticket implements Runnable{
private int ticketCount = 100;
Object obj = new Object();//必须有对象,代表锁,必须放在执行代码的外层,保证其唯一性
public void run(){
while(true){
synchronized(obj){
if(ticketCount>0){
try{
Thread.sleep(10);//可能出现异常
}catch(InterruptException){
e.printStack();
}
System.out.println(Thread.currentThread().getName()+"------"+ticketCount--);
}
}
}
}
}
通过上述的锁即可解决多线程的安全问题。
对象就如同锁。持有锁的线程可以在同步中执行。
没有持有锁的线程即使获取到了CPU的执行权,也无法进入上锁的代码块。
同步的前提:
1、必须要有两个或者两个以上的线程;
2、必须是多个线程同时使用一个锁;
同步的好处:解决了多线程安全问题。
弊端:多个线程需要判断,较为消耗资源。
3、同步函数
除了同步代码块之外,也可以用同步函数来来实现代码同步的功能。
例如:
//创建Ticket类,实现Runnable方法
public class Ticket implements Runnable{
private int ticketCount = 100;
public synchronized void run(){
while(true){
if(ticketCount>0){
try{
Thread.sleep(10);//可能出现异常
}catch(InterruptException){
e.printStack();
}
System.out.println(Thread.currentThread().getName()+"------"+ticketCount--);
}
}
}
}
但是,上述代码中同步函数的使用会导致一个问题,如图所示:
image.png原因是:在0线程未执行完毕之前,其他的线程无法获得执行权,进入到该方法中来。
所以,在使用同步代码时:
必须要搞清楚“哪些些代码需要被同步,哪些则不能被同步!”
那么在改方法中需要同步的代码只涉及,ticket的操作,故可以将该部分代码抽取出来,使用一个同步函数表示,如下:
private int ticketCount = 100;
public void run(){
while(true){
show();
}
}
//抽取出需要同步的代码
public synchronized void show(){
if(ticketCount>0){
try{
Thread.sleep(10);
}catch(Exception e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"**********"+ticketCount--);
}
}
如此,即可实现多线程安全的功能
image.png
4 、新的问题:在上述代码中,似乎并没有新创建一个锁对象
Object obj = new Object();//被删除了
那么,在这个时候,同步函数使用的锁是哪个呢?
结论:同步函数使用的是this锁。
验证:
写一个小程序来测试一下,如下:
功能:声明两种加锁的方式来实现同步,一种是函数块,一种是代码块;
假设1:如果说两种方式使用的是同一个锁。如果是这样,下面的程序应该可以很好的完成同步功能,不会出现异常。
代码如下:
public class Ticket implements Runnable{
private int ticketCount = 100;
Object obj = new Object();
boolean flag = true;
public void run(){
if(flag){
while(true){
show();
}
}else{
while(true){
synchronized (obj) {
if(ticketCount>0){
try{
Thread.sleep(10);
}catch(Exception e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"*****NOT-SHOW*****"+ticketCount--);
}
}
}
}
}
public synchronized void show(){
if(ticketCount>0){
try{
Thread.sleep(10);
}catch(Exception e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"****SHOW******"+ticketCount--);
}
}
}
主函数如下:
public static void main(String args[]){
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
try {
Thread.sleep(10);//必须让主线程休眠一会儿,否则flag总是false
} catch (InterruptedException e) {
e.printStackTrace();
}
t.flag = false;
t2.start();
}
运行结果如下
image.png
通过上图中0号票的存在,
假设1被推翻,即两种同步代码用的并不是同一个锁。
假设2:show()同步函数使用的是this锁,代码块使用的是new的Object锁(已知)。
验证代码如下:将obj换成this,若未出现安全问题,则说明show()使用的是this锁。
public class Ticket implements Runnable{
private int ticketCount = 100;
Object obj = new Object();
boolean flag = true;
public void run(){
if(flag){
while(true){
show();
}
}else{
while(true){
synchronized (this) {
if(ticketCount>0){
try{
Thread.sleep(10);
}catch(Exception e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"*****NOT-SHOW*****"+ticketCount--);
}
}
}
}
}
public synchronized void show(){
if(ticketCount>0){
try{
Thread.sleep(10);
}catch(Exception e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"****SHOW******"+ticketCount--);
}
}
}
运行结果如下:
image.png
结果中并未出现0号异常票,所以,假设得证!
结论:同步函数使用的是this锁。
5 再说一个要注意的地方:
如果同步函数被静态所修饰,使用的锁是该方法所在类的字节码文件对象,可通过类名称.class获得。
代码例子:
public class Ticket implements Runnable{
private static int ticketCount = 100;
Object obj = new Object();
boolean flag = true;
public void run(){
if(flag){
while(true){
show();
}
}else{
while(true){
synchronized (Ticket.class) {
if(ticketCount>0){
try{
Thread.sleep(10);
}catch(Exception e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"*****NOT-SHOW*****"+ticketCount--);
}
}
}
}
}
public static synchronized void show(){
if(ticketCount>0){
try{
Thread.sleep(10);
}catch(Exception e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"****SHOW******"+ticketCount--);
}
}
}
结果如下:
image.png
结果正常,无0号票,但是如果不使用Ticket.class,使用this就会出现0号票。
END
P.S.第一次采用这种行文方式来写,感觉不错,更加面向浏览者,是个好的转变!O(∩_∩)O哈哈~
网友评论