Java多线程-线程同步机制详解

目录

一、线程安全问题概述

二、线程安全问题的代码实现

多线程类:

测试类:

运行结果(截取部分):

三、线程安全问题产生的原理

四、解决线程安全问题

1、引入线程同步机制的三种方法

2、同步代码块

格式:

锁对象的含义:

注意:

代码示例:

3、同步代码块的原理

4、同步方法

使用步骤:

格式

代码示例:

改进后的代码:

备注:

5、静态同步方法

概述:

注意:

6、锁机制(Lock锁)

概述:

使用步骤:

代码示例:

代码示例更好的写法:

7、死锁

概述:

代码演示:

运行结果:

修改代码,使得双方都愿意先让出自己的玩具:

运行结果:


一、线程安全问题概述

多个线程操作同一个数据的情况下,线程不安全了!

 

二、线程安全问题的代码实现

多线程类:

package study.thread;

public class ThreadSafeImpl implements Runnable {
    private int ticket = 100;
    @Override
    public void run() {
        while (true){
            if(ticket>0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("售票员"+Thread.currentThread().getName()+"正在卖第"+ticket+"张票……");
                ticket--;
            }else {
                break;
            }
        }
    }
}

测试类:

package study.thread;

public class ThreadSafeTest {
    public static void main(String[] args) {
        ThreadSafeImpl threadSafe = new ThreadSafeImpl();
        //多线程干一件事
        new Thread(threadSafe).start();
        new Thread(threadSafe).start();
        new Thread(threadSafe).start();
    }
}

运行结果(截取部分):

(发现票卖重了,而且卖了不存在的票,这就有问题了!)
售票员Thread-2正在卖第100张票……
售票员Thread-0正在卖第100张票……
售票员Thread-1正在卖第100张票……
...
售票员Thread-2正在卖第1张票……
售票员Thread-0正在卖第1张票……
售票员Thread-1正在卖第-1张票……

 

三、线程安全问题产生的原理

 

四、解决线程安全问题

1、引入线程同步机制的三种方法

①同步代码块;

②同步方法;

③锁机制;

 

2、同步代码块

格式:

synchronized(锁对象){
    可能出现线程安全问题的代码(从访问到共享数据的代码开始)
}

锁对象的含义:

前面的线程开始执行后回去拿取堆内存中的锁对象,后面的线程开始执行后再去拿锁对象就拿不到了,所以无法继续执行,需要等待前面的线程执行完毕,归还锁对象,后面的线程才能拿到锁对象,继续执行;

注意:

①通过代码块中的锁对象,可以使用任意的对象;

②但必须保证多个线程使用的所对象是同一个;

③所对象的作用:把同步代码块锁住,只让一个线程在同步代码块中执行;

代码示例:

加入同步代码块后的多线程类:

package study.thread;

public class ThreadSafeImpl implements Runnable {
    private int ticket = 100;
    private final Object object = new Object();
    @Override
    public void run() {
        while (true){
            synchronized (object){
                if(ticket>0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("售票员"+Thread.currentThread().getName()+"正在卖第"+ticket+"张票……");
                    ticket--;
                }else {
                    break;
                }
            }
        }
    }
}

测试类:

package study.thread;

public class ThreadSafeTest {
    public static void main(String[] args) {
        ThreadSafeImpl threadSafe = new ThreadSafeImpl();
        //多线程干一件事
        new Thread(threadSafe).start();
        new Thread(threadSafe).start();
        new Thread(threadSafe).start();
    }
}

运行结果(截取部分):

售票员Thread-0正在卖第100张票……
售票员Thread-0正在卖第99张票……
售票员Thread-2正在卖第98张票……
售票员Thread-1正在卖第97张票……
售票员Thread-1正在卖第96张票……
售票员Thread-1正在卖第95张票……

 

3、同步代码块的原理

 

4、同步方法

使用步骤:

①把访问了共享数据的代码抽取出来,放到一个方法中;

②在方法上添加synchronized修饰符;

格式

访问修饰符 synchronized 返回值类型 方法名(参数列表){
    //方法体
}

代码示例:

package study.thread;

public class ThreadSafeImpl implements Runnable {
    private int ticket = 100;
    @Override
    public void run() {
        printMessage();
    }
    private synchronized void printMessage(){
        while (true){
            if(ticket>0){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("售票员"+Thread.currentThread().getName()+"正在卖第"+ticket+"张票……");
                ticket--;
            }else {
                break;
            }
        }
    }
}

但经测试发现,此方法一个会出现一个线程将票卖完的情况,我自己猜测也许是因为while循环放进了锁住的方法中。

改进后的代码:

package study.thread;

public class ThreadSafeImpl implements Runnable {
    private int ticket = 100;
    @Override
    public void run() {
        while (true){
            printMessage();
            if(ticket==0){
                break;
            }
        }
    }
    private synchronized void printMessage(){
        if(ticket>0){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("售票员"+Thread.currentThread().getName()+"正在卖第"+ticket+"张票……");
            ticket--;
        }
    }
}

这样写确实避免了上述情况。

备注:

同步方法也会把方法内部的代码锁住,只让一个线程执行,实际上所的对象是new RunnableImpl(),也就是this(自身);

 

5、静态同步方法

概述:

静态同步方法就是在一般的同步方法synchronized前加上static;

注意:

此时的锁对象不是this(本身),而是本类的class属性-->class文件对象(反射);

 

6、锁机制(Lock锁)

概述:

Lock接口实现提供了比使用synchronized方法和语句可获得的更广泛的锁定操作;

 

使用步骤:

①在成员位置创建一个ReentrantLock(可重入锁);

②在有可能出现安全问题的代码前,调用获取锁的方法(闭锁);

③在有可能出现安全问题的代码后,调用释放锁的方法(开锁);

 

代码示例:

package study.thread;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ThreadSafeImpl implements Runnable {
    private int ticket = 100;
    //1、在成员位置创建一个
    Lock lock = new ReentrantLock();
    @Override
    public void run() {
        while (true){
            //2、在有可能出现安全问题的代码前,调用获取锁的方法(闭锁)
            lock.lock();
            if(ticket>0){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("售票员"+Thread.currentThread().getName()+"正在卖第"+ticket+"张票……");
                ticket--;
            }else {
                break;
            }
            //3、在有可能出现安全问题的代码后,调用释放锁的方法(开锁)
            lock.unlock();
        }
    }
}

代码示例更好的写法:

package study.thread;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ThreadSafeImpl implements Runnable {
    private int ticket = 100;
    //1、在成员位置创建一个
    Lock lock = new ReentrantLock();
    @Override
    public void run() {
        while (true){
            //2、在有可能出现安全问题的代码前,调用获取锁的方法(闭锁)
            lock.lock();
            if(ticket>0){
                try {
                    Thread.sleep(10);
                    System.out.println("售票员"+Thread.currentThread().getName()+"正在卖第"+ticket+"张票……");
                    ticket--;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    //3、在有可能出现安全问题的代码后,调用释放锁的方法(开锁)
                    lock.unlock();
                }
            }else {
                break;
            }
        }
    }
}

 

7、死锁

概述:

多个线程同时站有一些共享资源,并且相互等待其他线程占有的资源才能运行,而这导致两个或多个线程都在等待对方释放资源,都停止运行的情形;

某一个同步代码块同时拥有“两个以上对象的锁”时,就可能发生“死锁”;

又两个小朋友,小明和小强,小明有玩具汽车,但是小明想要小强的玩具枪,相反小强有玩具枪却想要小明的玩具汽车,双方都说如果你先把你的玩具给我,我就给你,所以两个小朋友都在等对方把玩具给自己,这就僵持住了,在程序中也就是“死锁”;

 

代码演示:

package com.zb.thread;

import lombok.SneakyThrows;

//测试死锁
public class TestDeadlock {
    public static void main(String[] args) {
        Object gan = new Object();
        Object car = new Object();
        new Thread(new Play("小明",gan,car)).start();
        new Thread(new Play("小强",gan,car)).start();
    }
}
class Play implements Runnable{

    private final String name;
    private final Object gan;
    private final Object car;

    public Play(String name, Object gan, Object car) {
        this.name = name;
        this.gan = gan;
        this.car = car;
    }

    @SneakyThrows
    @Override
    public void run() {
        if("小明".equals(name)){
            synchronized (car){//小明持有玩具车
                System.out.println("小明持有玩具车!");
                System.out.println("小明想要玩具枪!");
                synchronized (gan){
                    System.out.println("小明获得了玩具枪!");
                }
            }
        }else if("小强".equals(name)){
            synchronized (gan){//小强持有玩具枪
                System.out.println("小强持有玩具枪!");
                System.out.println("小强想要玩具车!");
                synchronized (car){
                    System.out.println("小明获得了玩具枪!");
                }
            }
        }
    }
}

 

运行结果:

小明持有玩具车!
小明想要玩具枪!
小强持有玩具枪!
小强想要玩具车!

 

修改代码,使得双方都愿意先让出自己的玩具:

package com.zb.thread;

import lombok.SneakyThrows;

//测试死锁
public class TestDeadlock {
    public static void main(String[] args) {
        Object gan = new Object();
        Object car = new Object();
        new Thread(new Play("小明",gan,car)).start();
        new Thread(new Play("小强",gan,car)).start();
    }
}
class Play implements Runnable{

    private final String name;
    private final Object gan;
    private final Object car;

    public Play(String name, Object gan, Object car) {
        this.name = name;
        this.gan = gan;
        this.car = car;
    }

    @SneakyThrows
    @Override
    public void run() {
        if("小明".equals(name)){
            synchronized (car){//小明持有玩具车
                System.out.println("小明持有玩具车!");
                System.out.println("小明想要玩具枪!");
            }
            Thread.sleep(1000);
            synchronized (gan){
                System.out.println("小明获得了玩具枪!");
            }
        }else if("小强".equals(name)){
            synchronized (gan){//小强持有玩具枪
                System.out.println("小强持有玩具枪!");
                System.out.println("小强想要玩具车!");
            }
            synchronized (car){
                System.out.println("小明获得了玩具枪!");
            }
        }
    }
}

 

运行结果:

小明持有玩具车!
小明想要玩具枪!
小强持有玩具枪!
小强想要玩具车!
小明获得了玩具枪!
小明获得了玩具枪!

 

 

 

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值