Java 多线程二——synchronized

一、synchronized 同步方法

synchronized方法的缺点,假如有两个线程一个线程A调用同步方法后获得锁,那么另一个线程B就需要等待A执行完,但是如果说A执行的是一个很费时间的任务的话这样就会很耗时

1、非线程安全

如果两个线程同时操作对象中的实例变量,则会出现“非线程安全”

2、synchronized方法与锁对象

synchronized取得的锁都是对象锁,而不是把一段代码或方法当做锁,如果多个线程访问的是同一个对象,哪个线程先执行带synchronized关键字的方法,则哪个线程就持有该方法,那么其他线程只能呈等待状态。如果多个线程访问的是多个对象则不一定,因为多个对象会产生多个锁。

当多个线程访问的是同一个对象中的非synchronized类型方法会发生脏读的情况

3、synchronized锁重入

(1)、“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。

public class Service {
    synchronized public void service1() {
        System.out.println("service1");
        service2();
    }
    synchronized public void service2() {
        System.out.println("service2");
        service3();
    }
    synchronized public void service3() {
        System.out.println("service3");
    }
}

public class MyThread implements Runnable {
    @Override
    public void run() {
        Service service = new Service();
        service.service1();
    }
}
public class Run {
    public static void main(String[] args) {
        MyThread myThread =new MyThread ();
        Thread thread1 = new Thread(myThread ,"thread-1");
        t.start();
    }
}

(2)可重入锁也支持在父子类继承的环境中

public class Father{
    public int i = 10;
    synchronized public void operateIFatherMethod() {
        try {
            i--;
            System.out.println("main print i=" + i);
            Thread.sleep(100);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
public class Sub extends Father{
    synchronized public void operateISubMethod() {
        try {
            while (i > 0) {
                i--;
                System.out.println("sub print i=" + i);
                Thread.sleep(100);
                this.operateIFatherMethod();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class MyThread implements Runnable {
    @Override
    public void run() {
        Sub sub = new Sub();
        sub.operateISubMethod();
    }
}
public class Run {
    public static void main(String[] args) {
        MyThread myThread =new MyThread ();
        Thread thread1 = new Thread(myThread ,"thread-1");
        t.start();
    }
}

4、同步不具有继承性

如果父类有一个带synchronized关键字的方法,子类继承并重写了这个方法。但是同步不能继承,所以还是需要在子类方法中添加synchronized关键字。

5、synchronized 同步方法实例

public class SyncMethod {
    public static void main(String[] args) {
        /**
         * 同步代码注意点:
         * 1、不跟随线程变化的预处理和后护理放在同步代码的外面,是同步的代码尽量简短
         * 2、不要阻塞线程,如inputStream.read()
         * 3、在持有锁的时候,不要对其他对象调用同步方法(即不要调用别的对象的同步方法)。
         */
//      案例:假设某个景点有 4个售票员在同时售票
//      实例化2个线程, 用2个线程来模拟2个售票员
        TicketCenterMethod ticketCenterMethod =new TicketCenterMethod();
        Thread thread1 = new Thread(ticketCenterMethod,"thread-1");
        Thread thread2 = new Thread(ticketCenterMethod,"thread-2");
        thread1.start();
        thread2.start();
    }
}
class  TicketCenterMethod implements Runnable {
    //     剩余的票的数量
    private static int restTicketCount = 100;
    @Override
    public void run() {
        for (int i = 0; i <300 ; i++) {
            saleTicketMethod();
        }
    }

    //    同步方法,this表示锁,可以是任意对象,但是不同线程之间此处必须要使用同一个对象
    public synchronized void  saleTicketMethod(){
        if (TicketCenterMethod.restTicketCount > 0){
            System.out.println(Thread.currentThread().getName() + " 卖出第 "
                    +TicketCenterMethod.restTicketCount+" 张票,剩余票数 = "+
                    --TicketCenterMethod.restTicketCount+" 张 ");
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

二、synchronized 同步代码块

对于synchronized同步方法可能存在非常耗时的问题。可以使用synchronized同步块来解决。但是要注意synchronized同步块的使用方式,如果synchronized同步块使用不好的话并不会带来效率的提升。

1、synchronized(this)同步代码块实例


/**临界资源问题
 * 1、 多线程共享数据时,会发横线程不安全的情况
 * 2、 多线程共享数据必须使用同步
 * */
public class SyncCodeblock {
    public static void main(String[] args) {
        /**
         * 同步代码注意点:
         * 1、不跟随线程变化的预处理和后护理放在同步代码的外面,是同步的代码尽量简短
         * 2、不要阻塞线程,如inputStream.read()
         * 3、在持有锁的时候,不要对其他对象调用同步方法(即不要调用别的对象的同步方法)。
         */
//      案例:假设某个景点有 4个售票员在同时售票
//      实例化2个线程, 用2个线程来模拟2个售票员
        TicketCenterThread tickitCenterThread =new TicketCenterThread();
        Thread t1 = new Thread(tickitCenterThread,"thread-1");
        Thread t2 = new Thread(tickitCenterThread,"thread-2");
        t1.start();
        t2.start();
    }
}
class TicketCenterThread implements Runnable {
    //     剩余的票的数量
    public static  int restTicketCount = 100;
    private Object object = new Object();
    @Override
    public void run() {
        while (TicketCenterThread.restTicketCount>0){
//               同步代码块,this表示锁,可以是任意对象,但是不同线程之间此处必须要使用同一个对象
            synchronized (object){
                System.out.println(Thread.currentThread().getName() + " 卖出第 "
                        +TicketCenterThread.restTicketCount+" 张票,剩余票数 = "+
                        --TicketCenterThread.restTicketCount+" 张 ");
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

2、synchronized代码块间的同步性

  • 当一个对象访问synchronized(this)代码块时,其他线程对同一个对象中所有其他synchronized(this)代码块代码块的访问将被阻塞,这说明synchronized(this)代码块使用的“对象监视器”是一个。
  • 也就是说和synchronized方法一样,synchronized(this)代码块也是锁定当前对象的。
  • 其他线程执行对象中synchronized同步方法(上一节我们介绍过,需要回顾的可以看上一节的文章)和synchronized(this)代码块时呈现同步效果;
  • 如果两个线程使用了同一个“对象监视器”,运行结果同步,否则不同步。
     

3、静态synchronized同步方法与synchronized(class)代码块

synchronized关键字加到static静态方法和synchronized(class)代码块上都是是给Class类上锁,而synchronized关键字加到非static静态方法上是给对象上锁

下面例子说明,静态同步synchronized方法与synchronized(class)代码块持有的锁一样,都是Class锁,Class锁对对象的所有实例起作用。synchronized关键字加到非static静态方法上持有的是对象锁。

线程A,B和线程C持有的锁不一样,所以A和B运行同步,但是和C运行不同步。

public class Service {
    public static void printA() {
        synchronized (Service.class) {
            try {
                System.out.println(
                        "线程名称为:" + Thread.currentThread().getName() + 
"在" + System.currentTimeMillis() + "进入printA");
                Thread.sleep(3000);
                System.out.println(
                        "线程名称为:" + Thread.currentThread().getName() + 
"在" + System.currentTimeMillis() + "离开printA");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    synchronized public static void printB() {
        System.out.println("线程名称为:" + Thread.currentThread().getName() + 
"在" + System.currentTimeMillis() + "进入printB");
        System.out.println("线程名称为:" + Thread.currentThread().getName() + 
"在" + System.currentTimeMillis() + "离开printB");
    }
    synchronized public void printC() {
        System.out.println("线程名称为:" + Thread.currentThread().getName() + 
"在" + System.currentTimeMillis() + "进入printC");
        System.out.println("线程名称为:" + Thread.currentThread().getName() + 
"在" + System.currentTimeMillis() + "离开printC");
    }
}
public class ThreadA extends Thread {
    private Service service;
    public ThreadA(Service service) {
        super();
        this.service = service;
    }
    @Override
    public void run() {
        service.printA();
    }
}
public class ThreadB extends Thread {
    private Service service;
    public ThreadB(Service service) {
        super();
        this.service = service;
    }
    @Override
    public void run() {
        service.printB();
    }
}
public class ThreadC extends Thread {
    private Service service;
    public ThreadC(Service service) {
        super();
        this.service = service;
    }
    @Override
    public void run() {
        service.printC();
    }
}
public class Run {
    public static void main(String[] args) {
        Service service = new Service();
        ThreadA a = new ThreadA(service);
        a.setName("A");
        a.start();
        ThreadB b = new ThreadB(service);
        b.setName("B");
        b.start();
        ThreadC c = new ThreadC(service);
        c.setName("C");
        c.start();
    }
}

4、数据类型String的常量池属性

在Jvm中具有String常量池缓存的功能

字符串常量池中的字符串只存在一份! 即执行完第一行代码后,常量池中已存在 “a”,那么s2不会在常量池中申请新的空间,而是直接把已存在的字符串内存地址返回给s2。

//因为数据类型String的常量池属性,所以synchronized(string)在使用时某些情况下会出现一些问题,比如//两个线程运行
synchronized(“abc”){
}和
synchronized(“abc”){
}
//修饰的方法时,这两个线程就会持有相同的锁,导致某一时刻只有一个线程能运行。所以尽量不要使用
//synchronized(string)而使用synchronized(object)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值