在上一篇博客 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实现线程同步有三种方式,分别是同步代码块
、同步函数
、静态同步函数
,不过这三种方式都使用了锁的机制来实现。
同步代码块
实现线程同步//注意:锁必须是对象,不能是基本数据类型
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();
}
}
同步函数
实现线程同步(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();
}
}
静态同步函数
实现线程同步(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();
}
}
同步函数
用的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
锁修改为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();
}
}
静态同步函数
使用的class锁证明也是类似的思路,将lockObj
修改this.getClass()
即可。
注 意 : 锁 也 不 是 乱 用 的 , 搞 不 好 会 出 现 线 程 死 锁 。 \color{red}注意:锁也不是乱用的,搞不好会出现线程死锁。 注意:锁也不是乱用的,搞不好会出现线程死锁。
所谓线程死锁
,就是某个时刻,多个线程都拥有部分锁,它们不开自己手中的锁,并且都想拥有对方的锁。
(举个栗子:小明的妈妈说,小明回来我再去做饭,而小明说,妈妈做好饭我再回去。)
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多线程之线程安全与线程同步(锁)主要内容,如果你之前把数据库事务中的锁概念理解了,那么线程同步中的锁概念是相通的。