[Java][华为云Java编程创造营][学习笔记][第三阶段][05_Java多线程实战][03_线程同步和安全]

举报
John2021 发表于 2021/12/17 06:08:58 2021/12/17
【摘要】 3.1,线程的同步安全线程安全问题设计并发编程的目的是为了使程序获得更高的执行效率,但绝不能出现数据一致性(数据准确)问题。如果并发程序连最基本的执行结果准确性都无法保证,那并发编程就没有任何意义。为什么会出现数据不正确:如果一个资源(变量、对象、文件、数据库)可以同时被很多线程使用就会出现数据不一致问题,也就是我们说的线程安全问题。这样的资源被称为共享资源或临界区。public clas...

3.1,线程的同步安全

  • 线程安全问题

    • 设计并发编程的目的是为了使程序获得更高的执行效率,但绝不能出现数据一致性(数据准确)问题。
    • 如果并发程序连最基本的执行结果准确性都无法保证,那并发编程就没有任何意义。
  • 为什么会出现数据不正确:

    • 如果一个资源(变量、对象、文件、数据库)可以同时被很多线程使用就会出现数据不一致问题,也就是我们说的线程安全问题。这样的资源被称为共享资源或临界区。
public class Demo24 implements Runnable
{
    private int m = 0;

    @Override
    public void run()
    {
        for (int i = 0; i < 10000; i++)
        {
            m++;
        }
    }

    public static void main(String[] args)
    {
        Demo24 run = new Demo24();
        Thread thread1 = new Thread(run);
        Thread thread2 = new Thread(run);
        thread1.start();
        thread2.start();
        try
        {
            //join()使main线程等待这两个线程执行结束后继续执行下面
            thread1.join();
            thread2.join();
        } catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        System.out.println("m的最终值为:" + run.m);
        /*
         * 输出结果
         * m的最终值为:20000
         * */
    }
}
  • 互斥访问之synchronized:
    • 互斥锁:顾名思义,就是互斥访问目的的锁。
    • 如果对临界资源加上互斥锁,当一个线程在访问该临界资源时,其他线程便只能等待。
    • 在Java中,每一个对象都拥有一个锁标记(monitor),也称为监视器,多线程同时访问某个对象时,只有拥有该对象锁的线程才能访问。

3.2,线程的同步方法和同步块

  • 同步代码块
    • 同步块的根本的目的,是控制竞争资源的正确的安全访问,因此只要在访问竞争资源的时候保证同一时刻只能一个线程访问即可,所以Java引入了同步代码块的策略,以提高性能。
synchronized(obj)
{
    同步代码块
}
  • obj叫做同步监视器(即锁对象),任何线程进入下面同步代码块之前必须先获得对obj的锁;其他线程无法获得锁。

  • 锁对象可以是任意对象,但必须保证是同一对象任何时刻只能有一个线程可以获得对同步监视器的锁定。当同步代码块执行完成后该线程会释放对该同步监视器的锁定。

  • 同步方法和同步代码块的区别

/*同步方法*/
class UserRunn implements Runnable
{
    @Override
    public synchronized void run()
    {
        System.out.println("欢迎执行");
        for (int i = 0; i < 3; i++)
        {
            System.out.println(Thread.currentThread().getName() + "," + i);
        }
        System.out.println("结束了,谢谢");
    }
}

/*
 * 同步块
 * */
class UserRunn1 implements Runnable
{
    @Override
    public void run()
    {
        System.out.println("欢迎执行");
        synchronized (this)
        {
            for (int i = 0; i < 3; i++)
            {
                System.out.println(Thread.currentThread().getName() + "," + i);
            }
        }
        System.out.println("结束了,谢谢");
    }
}

public class Demo25
{
    public static void main(String[] args)
    {
        //UserRunn ur = new UserRunn();
        UserRunn1 ur = new UserRunn1();
        Thread t1 = new Thread(ur);
        Thread t2 = new Thread(ur);
        t1.start();
        t2.start();

    }
    /*
    * 同步方法输出结果
    *   欢迎执行
        Thread-0,0
        Thread-0,1
        Thread-0,2
        结束了,谢谢
        欢迎执行
        Thread-1,0
        Thread-1,1
        Thread-1,2
        结束了,谢谢
    * */
    /*
    * 同步块输出结果
    *   欢迎执行
        Thread-0,0
        Thread-0,1
        Thread-0,2
        欢迎执行
        结束了,谢谢
        Thread-1,0
        Thread-1,1
        Thread-1,2
        结束了,谢谢
    * */
}

3.3,线程的死锁

  • 当一个线程永远地持有一个锁,并且其他线程都尝试去获得这个锁时,那么它们将永远被阻塞。
  • 如果线程A持有锁L并且想获得锁M,线程B持有锁M并且想获得锁L,那么这两个线程将永远等待下去,这种情况就是最简单的死锁形式。
  • 死锁的案例
class DeadLock
{
    private final Object left = new Object();
    private final Object right = new Object();

    public void leftRight() throws Exception
    {
        synchronized (left)
        {
            //一定要有"Thread.sleep(2000)"让线程休眠,不然一个线程运行了,另一个线程还没有运行
            //先运行的线程很有可能就已经连续获得两个锁了
            Thread.sleep(2000);
            synchronized (right)
            {
                System.out.println("leftRight end!");
            }
        }
    }

    public void rightLeft() throws Exception
    {
        synchronized (left)
        {
            //一定要有"Thread.sleep(2000)"让线程休眠,不然一个线程运行了,另一个线程还没有运行
            //先运行的线程很有可能就已经连续获得两个锁了
            Thread.sleep(2000);
            synchronized (left)
            {
                System.out.println("rightLeft end!");
            }
        }
    }
}

class Thread0 extends Thread
{
    private DeadLock d1;

    public Thread0(DeadLock d1)
    {
        this.d1 = d1;
    }

    @Override
    public void run()
    {
        try
        {
            d1.leftRight();
        } catch (Exception e)
        {
            e.printStackTrace();
        }
    }
}

class Thread1 extends Thread
{
    private DeadLock d1;

    public Thread1(DeadLock d1)
    {
        this.d1 = d1;
    }

    @Override
    public void run()
    {
        try
        {
            d1.rightLeft();
        } catch (Exception e)
        {
            e.printStackTrace();
        }
    }
}

public class Test
{
    public static void main(String[] args)
    {
        DeadLock d1 = new DeadLock();
        Thread0 t0 = new Thread0(d1);
        Thread1 t1 = new Thread1(d1);
        t0.start();
        t1.start();
    }
}

3.4,线程的明锁

  • 在Java5种,专门提供了锁对象Lock,利用锁可以方便的实现资源的封锁,用来对竞争资源并发访问的控制。

  • Lock所有加锁和解锁的方法都是显式的。

    • Lock.lock():获取锁
    • Lock.unlock():释放锁
  • Lock可以构建公平锁和非公平锁,默认是非公平锁。

  • Lock与synchronized对比:

    • Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问。
    • synchronized不需要手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。

3.5,线程的公平锁和非公平锁

  • Java的ReenTrantLock也就是用队列实现的公平锁和非公平锁:
    • 在公平的锁中,如果有另一个线程持有锁或者有其他线程在等待队列中等待这个锁,那么新发出的请求的线程将被放入到队列中。
    • 而非公平锁上,只有当锁被某个线程持有时,新发出请求的线程才会被放入队列中(此时和公平锁是一样的)。所以,它们的差别在于非公平锁会有更多的机会去抢占锁。
  • Java的ReenTrantLock的ReenTrantLock(true),表示公平锁:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class UserRunn implements Runnable
{
    //长途汽车票是10
    private int count = 10;

    //线程运行退出标识位
    private boolean flag = true;

    private Lock lock;

    public UserRunn(Lock lock)
    {
        this.lock = lock;
    }

    @Override
    public void run()
    {
        System.out.println(Thread.currentThread().getName() + ",欢迎订票");
        while (flag)
        {
            lock.lock();
            System.out.println(Thread.currentThread().getName() +
                    ",获取到了第" + count-- + "票,恭喜你获取票成功");
            if (count <= 1)
            {
                flag = false;
            }
            lock.unlock();
        }
        System.out.println(Thread.currentThread().getName() + ",谢谢你");
    }
}

/*
 * 公平锁
 * */
public class Test
{
    public static void main(String[] args)
    {
        //true:公平锁 false:非公平锁
        ReentrantLock lock = new ReentrantLock(true);

        UserRunn ur = new UserRunn(lock);
        Thread t1 = new Thread(ur, "北门汽车站");
        Thread t2 = new Thread(ur, "南门汽车站");
        Thread t3 = new Thread(ur, "东门汽车站");
        t1.start();
        t2.start();
        t3.start();
        /*
        * 获取锁的线程顺序正是线程启动的顺序
        *   北门汽车站,欢迎订票
            东门汽车站,欢迎订票
            南门汽车站,欢迎订票
            北门汽车站,获取到了第10票,恭喜你获取票成功
            东门汽车站,获取到了第9票,恭喜你获取票成功
            南门汽车站,获取到了第8票,恭喜你获取票成功
            北门汽车站,获取到了第7票,恭喜你获取票成功
            东门汽车站,获取到了第6票,恭喜你获取票成功
            南门汽车站,获取到了第5票,恭喜你获取票成功
            北门汽车站,获取到了第4票,恭喜你获取票成功
            东门汽车站,获取到了第3票,恭喜你获取票成功
            南门汽车站,获取到了第2票,恭喜你获取票成功
            南门汽车站,谢谢你
            北门汽车站,获取到了第1票,恭喜你获取票成功
            北门汽车站,谢谢你
            东门汽车站,获取到了第0票,恭喜你获取票成功
            东门汽车站,谢谢你
        * */
    }
}
  • Java的ReenTrantLock的ReenTrantLock(false),表示非公平锁:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class UserRunn implements Runnable
{
    //长途汽车票是10
    private int count = 10;

    //线程运行退出标识位
    private boolean flag = true;

    private Lock lock;

    public UserRunn(Lock lock)
    {
        this.lock = lock;
    }

    @Override
    public void run()
    {
        System.out.println(Thread.currentThread().getName() + ",欢迎订票");
        while (flag)
        {
            lock.lock();
            System.out.println(Thread.currentThread().getName() +
                    ",获取到了第" + count-- + "票,恭喜你购票成功");
            if (count <= 1)
            {
                flag = false;
            }
            lock.unlock();
        }
        System.out.println(Thread.currentThread().getName() + ",谢谢你");
    }
}

/*
 * 非公平锁,对锁的获取是乱序的既有一个抢占锁的过程
 * */
public class Test
{
    public static void main(String[] args)
    {
        //true:公平锁 false:非公平锁
        ReentrantLock lock = new ReentrantLock(false);
        UserRunn ur = new UserRunn(lock);
        Thread t1 = new Thread(ur, "北门汽车站");
        Thread t2 = new Thread(ur, "南门汽车站");
        Thread t3 = new Thread(ur, "东门汽车站");
        t1.start();
        t2.start();
        t3.start();
        /*
        * 输出结果
        *   北门汽车站,欢迎订票
            南门汽车站,欢迎订票
            北门汽车站,获取到了第10票,恭喜你购票成功
            东门汽车站,欢迎订票
            北门汽车站,获取到了第9票,恭喜你购票成功
            北门汽车站,获取到了第8票,恭喜你购票成功
            北门汽车站,获取到了第7票,恭喜你购票成功
            北门汽车站,获取到了第6票,恭喜你购票成功
            北门汽车站,获取到了第5票,恭喜你购票成功
            北门汽车站,获取到了第4票,恭喜你购票成功
            北门汽车站,获取到了第3票,恭喜你购票成功
            北门汽车站,获取到了第2票,恭喜你购票成功
            北门汽车站,谢谢你
            南门汽车站,获取到了第1票,恭喜你购票成功
            南门汽车站,谢谢你
            东门汽车站,获取到了第0票,恭喜你购票成功
            东门汽车站,谢谢你
        * */
    }
}
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。