Java多线程之二(Synchronized)

常用API

method 注释
run() run()方法是我们创建线程时必须要实现的方法,但是实际上该方法只是一个普通方法,直接调用并没有开启线程的作用。
start() start()方法作用为使该线程开始执行;Java虚拟机调用该线程的 run 方法。 但是该方法只能调用一次,如果线程已经启动将会抛出IllegalThreadStateException异常。
yield() yield()方法让出CPU并且不会释放锁,让当前线程变为可运行状态,所以CPU下一次选择的线程仍可能是当前线程。
wait() wait()方法使得当前线程挂起,放弃CPU的同时也放弃同步资源(释放锁),让其他等待这些资源的线程能继续执行,只有当使用notify()/notifyAll()方法是才会使得等待的线程被唤醒,使用此方法的前提是已经获得锁。
notify()/notifyAll() notify()/notifyAll()方法将唤醒当前锁上的一个(全部)线程,需要注意的事一般都是使用的notifyAll()方法,因为notify()方法的唤醒是随机的,我们没有办法控制。

同步

上面已经介绍了比较常用的api,现在我们可以了解一下在多线程中占据着重要地位的锁了。

为什么会出现线程不安全

在上一篇文章中有提到在现在操作系统中进程是作为资源分配的基本单位,而线程是作为调度的基本单位,一般而言,线程自己不拥有系统资源,但它可以访问其隶属进程的资源,即一个进程的代码段、数据段及所拥有的系统资源,如已打开的文件、I/O设备等,可以供该进程中的所有线程所共享,一旦有多个线程在操作同样的资源就可能造成线程安全的问题。

在我们熟悉的Java中存在着局部变量和类变量,其中局部变量是存放在栈帧中的,随着方法调用而产生,方法结束就被释放掉,而栈帧是独属于当前线程的,所以不会有线程安全的问题。而类变量是被存放在堆内存中,可以被所有线程共享,所以也会存在线程安全的问题。

synchronized

在Java中我们见得最多的同步的方法应该就是使用synchronized关键字了。实际上synchronized就是一个互斥锁,当一个线程运行到使用了synchronized的代码段时,首先检查当前资源是否已经被其他线程所占用,如果已经被占用,那么该线程则阻塞在这里,直到拥有资源的线程释放锁,其他线程才可以继续申请资源。

实现简单理解

public static void test(){
    synchronized (SyncDemo.class){
    }
}

//编译后的代码
  public static void test();
    Code:
       0: ldc           #3                  //将一个常量加载到栈中这里既是class com/learn/set/mutilthread/sync/SyncDemo
       2: dup               //复制栈顶元素(SyncDemo.class)
       3: astore_0          //将栈顶元素存储到局部变量表
       4: monitorenter      //以字节码对象(SyncDemo.class)为锁开始同步操作
       5: aload_0           //将局部变量表slot_0入栈(SyncDemo.class)
       6: monitorexit       //退出同步
       7: goto          15  //到这里程序跳转到return语句正常结束,下面代码是异常路径
      10: astore_1
      11: aload_0
      12: monitorexit
      13: aload_1
      14: athrow
      15: return

到这里就差不多了,详细的原理后面再谈,这里主要是谈谈synchronized的使用。

synchronized的使用

在Java语言中,synchronized关键字可以用来修饰方法以及代码块:

修饰方法
    //修饰普通方法
    public synchronized void say(){

    }
    //修饰静态方法
    public synchronized static void fun(){

    }
修饰代码块
    public void fun1(){
        //使用当前对象为锁
        synchronized (this){
            //statement
        }
    }

    public void fun2(){
        //使用当前类字节码对象为锁
        synchronized (SyncDemo.class){
            //statement
        }
    }

synchronized在不同场景下的区别

实体类:

public class User {
    private static int age = 20;

    public synchronized void say(String user) throws InterruptedException {
//        synchronized (User.class){
        System.out.println(age + ":" + Thread.currentThread().getName() + ":" + user);
        //当前线程休眠,判断别的线程是否还能调用
        Thread.sleep(1000);
        System.out.println(age + ":" + Thread.currentThread().getName() + ":" + user);
//        }
    }

    public synchronized void say1(String user) throws InterruptedException {
//        synchronized (User.class){
        System.out.println(age + ":" + Thread.currentThread().getName() + ":" + user);
        Thread.sleep(1000);
        age = 15;
        System.out.println(age + ":" + Thread.currentThread().getName() + ":" + user);
//        }
    }
}

测试类:

public class SyncTest{
    private static User user1 = new User();
    private static User user2 = new User();

    private static class Sync1 extends Thread{
        @Override
        public void run() {
            try {
                user1.say("user1");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private static class Sync2 extends Thread{
        @Override
        public void run() {
            try {
                user2.say("user2");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Sync1 sync1 = new Sync1();
        Sync2 sync2 = new Sync2();
        sync1.start();
        sync2.start();
    }
}
运行结果:
第一次运行:
20:Thread-1:user2
20:Thread-0:user1
20:Thread-0:user1
20:Thread-1:user2
第二次运行:
20:Thread-1:user2
20:Thread-0:user1
20:Thread-1:user2
20:Thread-0:user1

运行结果表示在普通方法上加synchronized关键字实际上是锁的当前对象,所以不同线程操作不同对象结果可能出现不一致。修改实体类User的say(...)方法为静态方法:

public synchronized void say(String user) throws InterruptedException {
        System.out.println(age + ":" + Thread.currentThread().getName() + ":" + user);
        Thread.sleep(1000);
        System.out.println(age + ":" + Thread.currentThread().getName() + ":" + user);
    }

运行结果始终按照顺序来:

20:Thread-0:user1
20:Thread-0:user1
20:Thread-1:user2
20:Thread-1:user2

说明在静态(类)方法上加synchronized关键字实际上是锁的当前类的字节码对象,因为在JVM中任何类的字节码对象都只有一个,所以只要对该字节码对象加锁那么任何对该类的操作也都是同步的。

在最初类的基础上修改类Sync2,使得两个线程操作统一对象:

private static class Sync2 extends Thread{
        @Override
        public void run() {
            try {
                user1.say("user2");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

运行结果始终按照顺序来:

20:Thread-0:user1
20:Thread-0:user1
20:Thread-1:user2
20:Thread-1:user2

同理可测试在使用synchronized修饰代码块的作用,可得结果使用this对象实际是锁当前对象,与synchronized修饰普通方法类似,使用User.class字节码对象实际是锁User类的字节码对象,与synchronized修饰静态方法类似。需要说明的事锁代码块实际上并不是必须使用当前类的this对象和字节码对象,而可以是任意的对象。而实际效果和使用当前类的对象一致。

原文地址:https://www.cnblogs.com/liyus/p/9979270.html

时间: 2024-08-29 15:05:23

Java多线程之二(Synchronized)的相关文章

Java多线程(二)、线程的生命周期和状态控制(转)

Java多线程(二).线程的生命周期和状态控制 分类: javaSE综合知识点 2012-09-10 16:11 15937人阅读 评论(3) 收藏 举报 一.线程的生命周期 线程状态转换图: 1.新建状态 用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新生状态.处于新生状态的线程有自己的内存空间,通过调用start方法进入就绪状态(runnable). 注意:不能对已经启动的线程再次调用start()方法,否则会出现java.lang.IllegalThreadSt

java多线程系列(二)

对象变量的并发访问 前言:本系列将从零开始讲解java多线程相关的技术,内容参考于<java多线程核心技术>与<java并发编程实战>等相关资料,希望站在巨人的肩膀上,再通过我的理解能让知识更加简单易懂. 目录 认识cpu.核心与线程 java多线程系列(一)之java多线程技能 java多线程系列(二)之对象变量的并发访问 java多线程系列(三)之等待通知机制 java多线程系列(四)之ReentrantLock的使用 线程安全 线程安全就是多线程访问时,采用了加锁机制,当一个

Java多线程感悟二

写在前面 这篇是Java多线程感悟的第二篇博客,主要讲述的JAVA层面对并发的一些支持.第一篇博客地址为:http://zhangfengzhe.blog.51cto.com/8855103/1607712  下一篇博客将介绍线程池和一些同步工具类. 目录 9.  并发内存模型及并发问题概述 10. volatile和synchronized原理分析 11. ThreadLocal原理及其在Struts/Spring中的应用 12. Atomic 13. Lock 并发内存模型及并发问题概述 首

Java多线程(二)

本文承接上一篇文章<Java多线程(一)>. 四.Java多线程的阻塞状态与线程控制 上文已经提到Java阻塞的几种具体类型.下面分别看下引起Java线程阻塞的主要方法. 1.join() join -- 让一个线程等待另一个线程完成才继续执行.如A线程线程执行体中调用B线程的join()方法,则A线程被阻塞,知道B线程执行完为止,A才能得以继续执行. 1 public class ThreadTest { 2 3 public static void main(String[] args)

Java总结篇系列:Java多线程(二)

四.Java多线程的阻塞状态与线程控制 上文已经提到Java阻塞的几种具体类型.下面分别看下引起Java线程阻塞的主要方法. 1.join() join -- 让一个线程等待另一个线程完成才继续执行.如A线程线程执行体中调用B线程的join()方法,则A线程被阻塞,知道B线程执行完为止,A才能得以继续执行. 1 public class ThreadTest { 2 3 public static void main(String[] args) { 4 5 MyRunnable myRunna

【搞懂Java多线程之二】多线程调度及守护进程

在前一篇文章中说到,所有处在就绪状态中的线程,操作系统会选择优先级最高的优先进行调度,那么是不是优先级高的线程就一定比优先级低的线程先执行呢?线程的优先级又是怎么划分的呢?这篇文章,楼楼就要来说说这个问题啦!欢迎关注我的个人博客主页www.anycodex.com 1.线程的优先级 在Java中,线程优先级的范围为0-10,整数值越大,说明优先级更高. 几个相关的宏定义: MAX_PRIORITY 10,最高优先级 MIN_PRIORITY 1,最低优先级 NORM_PRIORITY 5,默认优

java多线程——同步块synchronized详解

Java 同步块(synchronized block)用来标记方法或者代码块是同步的.Java同步块用来避免竞争.本文介绍以下内容: Java同步关键字(synchronzied) 实例方法同步 静态方法同步 实例方法中同步块 静态方法中同步块 Java同步示例 Java 同步关键字(synchronized) Java中的同步块用synchronized标记.同步块在Java中是同步在某个对象上.所有同步在一个对象上的同步块在同时只能被一个线程进入并执行操作.所有其他等待进入该同步块的线程将

Java多线程(二) synchronized 抛出异常锁自动解除

当一个线程执行的代码出现异常时,其所持有的锁会自动释放 public class MyObject { private int i = 1; synchronized public void methodA() throws InterruptedException { System.out.println("begin methodA threadName=" + Thread.currentThread().getName()); if(i==1){ throw new Inter

Java多线程(二) —— 线程安全、线程同步、线程间通信(含面试题集)

一.线程安全 多个线程在执行同一段代码的时候,每次的执行结果和单线程执行的结果都是一样的,不存在执行结果的二义性,就可以称作是线程安全的. 讲到线程安全问题,其实是指多线程环境下对共享资源的访问可能会引起此共享资源的不一致性.因此,为避免线程安全问题,应该避免多线程环境下对此共享资源的并发访问. 线程安全问题多是由全局变量和静态变量引起的,当多个线程对共享数据只执行读操作,不执行写操作时,一般是线程安全的:当多个线程都执行写操作时,需要考虑线程同步来解决线程安全问题. 二.线程同步(synchr