Java多线程之线程安全与线程同步(锁)

在上一篇博客 Java多线程之概述与三种创建方式 演示了Java创建多线程的三种方式,在此篇博客将讲述一下线程安全与线程同步相关概念,以及Java是如何处理的。

一、线程安全

什么是线程安全

当多个线程同时共享同一个全局变量静态变量,做 \color{red}写操作 时,可能会发生 \color{red}数据冲突 问题,也就是线程安全问题。但是做 \color{red}读操作 是不会发生数据冲突问题。

举个栗子:两个线程同时出售50张火车票,可能会卖出第51张火车票,也可能两个线程同时卖出同一张票

package cn.hestyle.demo;

/**
 * 实现Runnable接口
 */
class SellTicketThread implements Runnable{
    /**全局变量,一共50张票*/
    private int count = 50;
    @Override
    public void run() {
        while (count > 0) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //卖出一张票后,睡眠50毫秒
            System.out.println(Thread.currentThread().getName() + "卖出 第 " + (51 - count) + " 张票" + ", 剩余 " + (count - 1) + " 张");
            count -= 1;
        }
    }
}

/**
 * description: demo_01
 *
 * @author hestyle
 * @version 1.0
 * @className multi_thread_project_01->MultiThreadDemo01
 * @date 2020-02-09 15:35
 **/
public class MultiThreadDemo01 {
    public static void main(String[] args) {
        SellTicketThread sellTicketThread = new SellTicketThread();
        //创建两个线程
        Thread thread_1 = new Thread(sellTicketThread, "线程一");
        Thread thread_2 = new Thread(sellTicketThread, "线程二");
        //启动两个线程,注意:启动线程用的start方法,run方法直接运行
        thread_1.start();
        thread_2.start();
    }
}

控制台输出信息:
在这里插入图片描述
受CPU执行速度、线程个数、延时大小,每次执行的结果可能都不一样,因此有时可能模拟不出效果,但是这种可能确确实实是存在的。

那么如何解决多线程不安全的问题呢?

二、线程同步

什么是线程同步

当有一个线程A在对内存进行 \color{red}写操作 时,其他线程都不可以对这个内存地址进行 \color{red}读、写操作 ,直到线程A完成操作, 其他线程才能对该内存地址进行操作,这就是线程同步

实现线程同步的方法有很多,常见的有锁,Java就是用锁的方式实现。(说到锁,数据库中的锁也是这个概念,放置多个客户端同时修改同一个数据)

Java实现线程同步有三种方式,分别是同步代码块同步函数静态同步函数,不过这三种方式都使用了锁的机制来实现。

1、同步代码块实现线程同步

//注意:锁必须是对象,不能是基本数据类型
synchronized (lockObj) {
	.....
    //可能引起线程不安全的代码
}
package cn.hestyle.demo;

/**
 * 实现Runnable接口
 */
class SellTicketThread implements Runnable{
    /**全局变量,一共50张票*/
    private Integer count = 50;
    private Object lockObj = new Object();
    @Override
    public void run() {
        while (count > 0) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            sellTicket();
        }
    }

    /**
     * 售票
     */
    private void sellTicket() {
        //注意锁必须为对象,不能是基本数据类型
        synchronized (lockObj) {
            if (count > 0) {
                //卖出一张票后,睡眠50毫秒
                System.out.println(Thread.currentThread().getName() + "卖出 第 " + (51 - count) + " 张票" + ", 剩余 " + (count - 1) + " 张");
                count -= 1;
            }
        }
    }
}

/**
 * description: demo_01
 *
 * @author hestyle
 * @version 1.0
 * @className multi_thread_project_01->MultiThreadDemo01
 * @date 2020-02-09 15:35
 **/
public class MultiThreadDemo01 {
    public static void main(String[] args) {
        SellTicketThread sellTicketThread = new SellTicketThread();
        //创建两个线程
        Thread thread_1 = new Thread(sellTicketThread, "线程一");
        Thread thread_2 = new Thread(sellTicketThread, "线程二");
        //启动两个线程,注意:启动线程用的start方法,run方法直接运行
        thread_1.start();
        thread_2.start();
    }
}

在这里插入图片描述

2、同步函数实现线程同步(this锁)

//同步函数,默认使用的锁是this,也就是对象本省
synchronized  function() {
	....
	//可能引起线程不安全的代码
}
package cn.hestyle.demo;

/**
 * 实现Runnable接口
 */
class SellTicketThread implements Runnable{
    /**全局变量,一共50张票*/
    private int count = 50;
    @Override
    public void run() {
        while (count > 0) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            sellTicket();
        }
    }

    /**
     * 售票
     * 同步函数,默认使用的锁是this,也就是对象本省
     */
    synchronized private void sellTicket() {
        if (count > 0) {
            //卖出一张票后,睡眠50毫秒
            System.out.println(Thread.currentThread().getName() + "卖出 第 " + (51 - count) + " 张票" + ", 剩余 " + (count - 1) + " 张");
            count -= 1;
        }
    }
}

/**
 * description: demo_01
 *
 * @author hestyle
 * @version 1.0
 * @className multi_thread_project_01->MultiThreadDemo01
 * @date 2020-02-09 15:35
 **/
public class MultiThreadDemo01 {
    public static void main(String[] args) {
        SellTicketThread sellTicketThread = new SellTicketThread();
        //创建两个线程
        Thread thread_1 = new Thread(sellTicketThread, "线程一");
        Thread thread_2 = new Thread(sellTicketThread, "线程二");
        //启动两个线程,注意:启动线程用的start方法,run方法直接运行
        thread_1.start();
        thread_2.start();
    }
}

在这里插入图片描述

3、静态同步函数实现线程同步(class锁)

//静态同步函数,默认使用的锁是class对象,也就是自己所属类的字节码文件对象
synchronized static function() {
   	....
	//可能引起线程不安全的代码
}
package cn.hestyle.demo;

/**
 * 实现Runnable接口
 */
class SellTicketThread implements Runnable{
    /**全局变量,一共50张票*/
    private static int count = 50;
    @Override
    public void run() {
        while (count > 0) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            sellTicket();
        }
    }

    /**
     * 售票
     * 静态同步函数,默认使用的锁是class对象,也就是自己所属类的字节码文件对象
     */
    synchronized static private void sellTicket() {
        //使用lockObj作为锁,注意锁必须为对象,不能是基本数据类型
            if (count > 0) {
                //卖出一张票后,睡眠50毫秒
                System.out.println(Thread.currentThread().getName() + "卖出 第 " + (51 - count) + " 张票" + ", 剩余 " + (count - 1) + " 张");
                count -= 1;
            }
    }
}

/**
 * description: demo_01
 *
 * @author hestyle
 * @version 1.0
 * @className multi_thread_project_01->MultiThreadDemo01
 * @date 2020-02-09 15:35
 **/
public class MultiThreadDemo01 {
    public static void main(String[] args) {
        SellTicketThread sellTicketThread = new SellTicketThread();
        //创建两个线程
        Thread thread_1 = new Thread(sellTicketThread, "线程一");
        Thread thread_2 = new Thread(sellTicketThread, "线程二");
        //启动两个线程,注意:启动线程用的start方法,run方法直接运行
        thread_1.start();
        thread_2.start();
    }
}

在这里插入图片描述

扫描二维码关注公众号,回复: 9414562 查看本文章

4、如何证明同步函数用的this锁、静态同步函数用的class锁?

①、首先证明同步函数用的锁不是lockObj
package cn.hestyle.demo;

/**
 * 实现Runnable接口
 */
class SellTicketThread implements Runnable{
    /**全局变量,一共50张票*/
    private static int count = 50;
    private static Object lockObj = new Object();
    private static boolean flag = true;
    @Override
    public void run() {
        if (flag) {
        	//第一个线程执行时flag==true,使用lockObj锁,然后flag = false
            flag = false;
            while (count > 0) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //使用lockObj锁
                synchronized (lockObj) {
                    if (count > 0) {
                        //卖出一张票后,睡眠50毫秒
                        System.out.println(Thread.currentThread().getName() + "卖出 第 " + (51 - count) + " 张票" + ", 剩余 " + (count - 1) + " 张");
                        count -= 1;
                    }
                }
            }
        } else {
        	//第二个线程进入时,flag==false,此时使用同步函数,默认是this锁
            while (count > 0) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //同步函数,默认是this锁
                sellTicket();
            }
        }
    }

    /**
     * 售票
     * 静态同步函数,默认使用的锁是class对象,也就是自己所属类的字节码文件对象
     */
    synchronized private void sellTicket() {
        if (count > 0) {
            //卖出一张票后,睡眠50毫秒
            System.out.println(Thread.currentThread().getName() + "卖出 第 " + (51 - count) + " 张票" + ", 剩余 " + (count - 1) + " 张");
            count -= 1;
        }
    }
}

证明了同步函数默认使用的锁不是lockObj对象
在这里插入图片描述

②、然后将lockObj锁修改为this
package cn.hestyle.demo;

/**
 * 实现Runnable接口
 */
class SellTicketThread implements Runnable{
    /**全局变量,一共50张票*/
    private static int count = 50;
    private static boolean flag = true;
    @Override
    public void run() {
        if (flag) {
            // 第个线程执行时flag==true,使用this锁,然后flag = false
            flag = false;
            while (count > 0) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //使用this锁
                synchronized (this) {
                    if (count > 0) {
                        //卖出一张票后,睡眠50毫秒
                        System.out.println(Thread.currentThread().getName() + "卖出 第 " + (51 - count) + " 张票" + ", 剩余 " + (count - 1) + " 张");
                        count -= 1;
                    }
                }
            }
        } else {
            // 第二个线程进入时,flag==false,此时使用同步函数,默认是this锁
            while (count > 0) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //同步函数
                sellTicket();
            }
        }

    }

    /**
     * 售票
     * 静态同步函数,默认使用的锁是class对象,也就是自己所属类的字节码文件对象
     */
    synchronized private void sellTicket() {
        if (count > 0) {
            //卖出一张票后,睡眠50毫秒
            System.out.println(Thread.currentThread().getName() + "卖出 第 " + (51 - count) + " 张票" + ", 剩余 " + (count - 1) + " 张");
            count -= 1;
        }
    }
}

/**
 * description: demo_01
 *
 * @author hestyle
 * @version 1.0
 * @className multi_thread_project_01->MultiThreadDemo01
 * @date 2020-02-09 15:35
 **/
public class MultiThreadDemo01 {
    public static void main(String[] args) {
        SellTicketThread sellTicketThread = new SellTicketThread();
        //创建两个线程
        Thread thread_1 = new Thread(sellTicketThread, "线程一");
        Thread thread_2 = new Thread(sellTicketThread, "线程二");
        //启动两个线程,注意:启动线程用的start方法,run方法直接运行
        thread_1.start();
        thread_2.start();
    }
}

在这里插入图片描述
因此可证明同步函数使用的this锁。

静态同步函数使用的class锁证明也是类似的思路,将lockObj修改this.getClass()即可。

线 \color{red}注意:锁也不是乱用的,搞不好会出现线程死锁。

5、线程死锁

所谓线程死锁,就是某个时刻,多个线程都拥有部分锁,它们不开自己手中的锁,并且都想拥有对方的锁。

(举个栗子:小明的妈妈说,小明回来我再去做饭,而小明说,妈妈做好饭我再回去。)

package cn.hestyle.demo;

/**
 * 实现Runnable接口
 */
class SellTicketThread implements Runnable{
    /**全局变量,一共50张票*/
    private static int count = 50;
    private Object lockObject_1 = new Object();
    private Object lockObject_2 = new Object();
    private static boolean flag = true;
    @Override
    public void run() {
        if (flag) {
            // 第个线程执行时flag==true,然后flag = false,先lockObject_1锁,在lockObject_2
            flag = false;
            while (count > 0) {
                synchronized (lockObject_1) {
                    synchronized (lockObject_2) {
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        if (count > 0) {
                            //卖出一张票后,睡眠50毫秒
                            System.out.println(Thread.currentThread().getName() + "卖出 第 " + (51 - count) + " 张票" + ", 剩余 " + (count - 1) + " 张");
                            count -= 1;
                        }
                    }
                }
            }
        } else {
            // 第二个线程进入时,flag==false,先lockObject_2锁,再lockObject_1锁
            synchronized (lockObject_2) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lockObject_1) {
                    if (count > 0) {
                        //卖出一张票后,睡眠50毫秒
                        System.out.println(Thread.currentThread().getName() + "卖出 第 " + (51 - count) + " 张票" + ", 剩余 " + (count - 1) + " 张");
                        count -= 1;
                    }
                }
            }
        }

    }
}

/**
 * description: demo_01
 *
 * @author hestyle
 * @version 1.0
 * @className multi_thread_project_01->MultiThreadDemo01
 * @date 2020-02-09 15:35
 **/
public class MultiThreadDemo01 {
    public static void main(String[] args) {
        SellTicketThread sellTicketThread = new SellTicketThread();
        //创建两个线程
        Thread thread_1 = new Thread(sellTicketThread, "线程一");
        Thread thread_2 = new Thread(sellTicketThread, "线程二");
        //启动两个线程,注意:启动线程用的start方法,run方法直接运行
        thread_1.start();
        thread_2.start();
    }
}

某个时刻可能出现线程一拥有lockObject_1,等待着lockObject_2,但此时lockObject_2被线程二拥有,并且它还等着lockObject_1。所以两个线程就进入了死锁状态,互相要对方的锁,不放开已经拥有的锁。
在这里插入图片描述

以上就是Java多线程之线程安全与线程同步(锁)主要内容,如果你之前把数据库事务中的锁概念理解了,那么线程同步中的锁概念是相通的。

发布了976 篇原创文章 · 获赞 230 · 访问量 20万+

猜你喜欢

转载自blog.csdn.net/qq_41855420/article/details/104237522