Java synchronized关键字

一、多线程给编码带来的影响

下面我们看一段代码,这段代码的含义是模拟现实生活中的卖票:

/**
 * 
 * 类描述
 * Copyright © 2017永乐科技. All rights reserved.
 * <p>@Title: ThreadTicket.java <p>
 * <p>@Package: demo.com.test.thread <p>
 * <p>@author: keep_trying <p>
 * <p>@date: 2017年12月15日 下午4:24:25 <p>
 * <p>@version: V1.0 <p>
 */
public class ThreadTicket implements Runnable{

    //共享资源
    private int ticketNums = 10;

    public static void main(String[] args) {
        ThreadTicket t = new  ThreadTicket();
        new Thread(t).start();//窗口1
        new Thread(t).start();//窗口2
        new Thread(t).start();//窗口3
        new Thread(t).start();//窗口4
    }


    @Override
    public void run() {
        //为什么要一个while循环呢? 因为每一个窗口都在不停的卖票
         while (true) {
               try {
                   //休眠10毫秒是等待两个线程同时获取ticketNums总张数
                   Thread.sleep(10);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
               //如果票的剩余张数为0的话停止卖禁票
               if (ticketNums==0) {
                     break;
               }
               System.out.println(Thread.currentThread().getName() + "剩余票数为" +ticketNums--);
        }
    }
}

运行结果1:

Thread-2剩余票数为7
Thread-1剩余票数为8
Thread-3剩余票数为9
Thread-0剩余票数为10
Thread-2剩余票数为6
Thread-1剩余票数为5
Thread-3剩余票数为4
Thread-0剩余票数为3
Thread-2剩余票数为2
Thread-3剩余票数为0
Thread-0剩余票数为1
Thread-1剩余票数为0
Thread-2剩余票数为-1

我们发现票竟然卖出了负数,这是不符合现实场景的。

二、分析原因

看一下线程的执行流程:

这里写图片描述

T1时间线程1和线程2同时得到ticketNums = 1 ,线程1获得了cpu的执行权(此时线程2等待)执行ticketNums -1操作,此时ticketNums =0,线程1把工作内存中的值更新到主内存,此时ticketNums =0。线程1执行完操作,线程2 获得了cpu的执行权,之前他已经判断了ticketNums =1,所以他从主内存中获取ticketNums 的值为0,并且执行ticketNums -1操作,此时ticketNums 变成了-1。此过程是并发执行的,即线程1 和线程2 在同时在执行。

三、如何解决线程并发问题

synchronized 关键字可以保证多线程并发执行的顺序性,下面看改进后的代码:

/**
 * 
 * 类描述
 * Copyright © 2017永乐科技. All rights reserved.
 * <p>@Title: ThreadTicket.java <p>
 * <p>@Package: demo.com.test.thread <p>
 * <p>@author: keep_trying <p>
 * <p>@date: 2017年12月15日 下午4:24:25 <p>
 * <p>@version: V1.0 <p>
 */
public class ThreadTicket implements Runnable{

    //共享资源
    private  int ticketNums = 10;

    public static void main(String[] args) {
        ThreadTicket t = new  ThreadTicket();
        new Thread(t).start();//窗口1
        new Thread(t).start();//窗口2
        new Thread(t).start();//窗口3
        new Thread(t).start();//窗口4
    }


    @Override
    public void run() {
        //为什么要一个while循环呢? 因为每一个窗口都在不停的卖票
         while (true) {
             //加入synchronized关键字
             synchronized (this) {
                 try {
                       //休眠10毫秒是等待两个线程同时获取ticketNums总张数
                       Thread.sleep(10);
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
                   //如果票的剩余张数为0的话停止卖禁票
                   if (ticketNums<=0) {
                         break;
                   }
                   System.out.println(Thread.currentThread().getName() + "剩余票数为" +ticketNums--);
             }

        }
    }
}

运行结果2:

Thread-0剩余票数为10
Thread-0剩余票数为9
Thread-0剩余票数为8
Thread-0剩余票数为7
Thread-0剩余票数为6
Thread-0剩余票数为5
Thread-0剩余票数为4
Thread-0剩余票数为3
Thread-0剩余票数为2
Thread-0剩余票数为1

运行结果没有出现-1,是我们想要的结果,那在来看下线程的执行流程:

这里写图片描述

在T1时间 线程1获取cpu的执行权 将ticketNums-1,此时ticketNums=0,在执行过程中线程2一会处于阻塞状态。线程2发现ticketNums=0 直接退出。此操作是串行执行的,即线程1执行完之后线程2才开始执行。

四、synchronized

synchronized:Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。

synchronized原理

在java中,每一个对象有且仅有一个同步锁。这也意味着,同步锁是依赖于对象而存在。
当我们调用某对象的synchronized方法时,就获取了该对象的同步锁。例如,synchronized(obj)就获取了“obj这个对象”的同步锁。
不同线程对同步锁的访问是互斥的。也就是说,某时间点,对象的同步锁只能被一个线程获取到!通过同步锁,我们就能在多线程中,实现对“对象/方法”的互斥访问。 例如,现在有两个线程A和线程B,它们都会访问“对象obj的同步锁”。假设,在某一时刻,线程A获取到“obj的同步锁”并在执行一些操作;而此时,线程B也企图获取“obj的同步锁” —— 线程B会获取失败,它必须等待,直到线程A释放了“该对象的同步锁”之后线程B才能获取到“obj的同步锁”从而才可以运行。

可以使用的地方

● 实例方法【1】
● 实例方法中代码块【2】
● 静态方法【3】
● 静态方法中代码块【4】

修饰实例方法

public class Synchronized {
    private int x=100;

    public synchronized void add(){
        x++;
    }
}

实例方法中代码块

public class Synchronized {
    private int x=100;

    public void add(){
        synchronized(this){
            x++;
        }
    }
}

修饰静态方法

public class Synchronized {
    private static int x=100;

    public static synchronized void add(){
            x++;
    }
}

静态方法中代码块

public class Synchronized {
    private static int x=100;

    public static synchronized void add(){
        synchronized(Synchronized.class){
            x++;
        }
    }
}

synchronized使用总结

先看几段代码

//代码1

public class SynchronizedTest implements Runnable {

    public static void main(String[] args) {
         SynchronizedTest  sync = new SynchronizedTest();
         new Thread(sync).start();;
         new Thread(sync).start();;
    }

    @Override
    public void run() {
        print();
    }
    public synchronized void print(){
        System.out.println(Thread.currentThread().getName()+"获得了锁,sleep 5 秒");
        System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果:
Thread-0获得了锁,sleep 52017-12-20 14:30:58
Thread-1获得了锁,sleep 5//5秒后打印
2017-12-20 14:31:03        //5秒后打印
//代码2

public class SynchronizedTest implements Runnable {

    public static void main(String[] args) {
         SynchronizedTest  sync1 = new SynchronizedTest();
         SynchronizedTest  sync2 = new SynchronizedTest();
         new Thread(sync1).start();;
         new Thread(sync2).start();;
    }

    @Override
    public void run() {
        print();
    }
    public synchronized void print(){
        System.out.println(Thread.currentThread().getName()+"获得了锁,sleep 5 秒");
        System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果:
Thread-1获得了锁,sleep 5//同时打印
Thread-0获得了锁,sleep 5//同时打印
2017-12-20 14:38:24
2017-12-20 14:38:24
//代码3

public class SynchronizedStaticTest implements Runnable {

    public static void main(String[] args) {
         SynchronizedStaticTest  sync1 = new SynchronizedStaticTest();
         SynchronizedStaticTest  sync2 = new SynchronizedStaticTest();
         new Thread(sync1).start();
         new Thread(sync2).start();
    }

    @Override
    public void run() {
        print();
    }
    public static synchronized void print(){
        System.out.println(Thread.currentThread().getName()+"获得了锁,sleep 5 秒");
        System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果:
Thread-0获得了锁,sleep 52017-12-20 14:57:23
Thread-1获得了锁,sleep 5//5秒后打印
2017-12-20 14:57:28        //5秒后打印
//代码4

public class SynchronizedTest implements Runnable {

    public static void main(String[] args) {
         SynchronizedTest  sync = new SynchronizedTest();
         new Thread(sync).start();;
         new Thread(sync).start();;
    }

    @Override
    public void run() {
        print();
    }
    public synchronized void print(){
        System.out.println(Thread.currentThread().getName()+"获得了锁,sleep 5 秒  print方法");
        System.out.println(Thread.currentThread().getName()+"时间"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        output();
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public synchronized void output(){
        System.out.println(Thread.currentThread().getName()+"获得了锁,sleep 5 秒  output方法");
    }
}

运行结果:
Thread-1获得了锁,sleep 5 秒  print方法
Thread-1时间2017-12-20 15:08:42
Thread-1获得了锁,sleep 5 秒  output方法
Thread-0获得了锁,sleep 5 秒  print方法   //5秒后打印
Thread-0时间2017-12-20 15:08:47          //5秒后打印
Thread-0获得了锁,sleep 5 秒  output方法  //5秒后打印

通过代码1、代码2 我们可以得出:synchronized修饰实例方法是对类的当前实例进行加锁,防止其他线程同时访问该类的该实例的所有synchronized块,注意这里是“类的当前实例”, 类的两个不同实例就没有这种约束了。

通过代码3 我们可以得出:synchronized修饰静态方法对类进行加锁,限制多线程同时访问该类的所有实例。代码3创建了两个对象,线程1获得了SynchronizedStaticTest类的锁(可以理解为全局锁),此时线程2只能等待。

通过代码4 我们可以得出:synchronized修饰实例方法,如果一个线程获得和对象锁,线程在调用其它用synchronized修饰的方法时会直接执行,不会等待继续获取锁 ,这是重入锁,此时对象锁的计数器为2。

实际上,在类中某方法或某代码块中有 synchronized,那么在生成一个该类实例后,改类也就有一个监视块,而static synchronized则是所有该类的实例公用一个监视快了,这也就是两个的区别了,也就是synchronized相当于 this.synchronized,而static synchronized相当于Class.synchronized.

总结:当synchronized作用在方法上时,锁住的便是对象实例(this);当作用在静态方法时锁住的便是对象对应的Class,静态方法锁相当于该类的一个全局锁;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值