1:什么叫线程安全?
* 如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。
* 程序每次运行结果和单线程运行的结果是一样 的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
2:线程安全的原理
1:多线程操作单一对象的逻辑
jvm中有且仅有一个某个对象 的 main memory对象,
每一个线程也有自己的working memory
1:一个线程对于一个变量variable进行操作的时候,
2:需要在自己的working memory里创建这个变量variable 一个copy值
3:操作完这个一个copy值 之后再写入main memory。
4:当多个线程操作同一个变量variable,就可能出现不可预知的结果。
银行卡转钱到钱包:重复点击转钱
线程1: 0元+10元 =10元
线程2: 0元+10元 =10元
最后:银行卡少20 自己钱包多了10块钱
2:而用synchronized的关键是建立一个监控monitor,这个monitor可以是要修改的变量,也可以是其他自己认为合适的对象(方法),然后通过给这个monitor加锁来实现线程安全,
3:每个线程在获得这个锁之后,要执行完后面的逻辑:
1:加载(load)到working memory
2:使用(user)
3: 重新指派(assign) 存储(store) 到 main memory的过程。
才会释放它得到的锁。这样就实现了所谓的线程安全。
3:常见的线程安全的类?
StringBuffer Vector Hashtable
4:解决线程安全的方法?
1.同步代码块
2.同步方法锁
3.同步锁
好处:解决了多线程的数据安全问题
弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率
public class TicketDemo {
public static void main(String[] args) {
// 创建线程任务对象
Ticket ticket = new Ticket();
//同步代码块
// TicketSynchronizedBlockDemo ticket = new TicketSynchronizedBlockDemo();
//同步方法锁
// TicketSynchronizedMethodDemo ticket = new TicketSynchronizedMethodDemo();
//同步锁
// TicketLockDemo ticket = new TicketLockDemo();
// 创建三个窗口对象
Thread t1 = new Thread(ticket, "窗口1");
Thread t2 = new Thread(ticket, "窗口2");
Thread t3 = new Thread(ticket, "窗口3");
// 同时卖票
t1.start();
t2.start();
t3.start();
}
}
package com.example.demo.thread.concurrencyLockThreadDemo;
//不加锁
public class Ticket implements Runnable{
private int ticket = 100;
@Override
public void run() {
// 每个窗口卖票的操作
// 窗口 永远开启
while (true) {
if (ticket > 0) {
// 有票 可以卖
// 出票操作
// 使用sleep模拟一下出票时间
try {
//1000(1秒)与 100(0.1秒)哪个重复的可能性比较大
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 获取当前线程对象的名字
String name = Thread.currentThread().getName();
System.out.println(name + "正在卖:" + ticket--);
}
}
}
}
//同步代码块
public class TicketSynchronizedBlockDemo implements Runnable {
private int ticket = 100;
Object lock = new Object();
@Override
public void run() {
// 每个窗口卖票的操作
// 窗口 永远开启
while (true) {
//同步代码块
synchronized (lock) {
if (ticket > 0) {
// 有票 可以卖
// 出票操作
// 使用sleep模拟一下出票时间
try {
//Sleep 的 1000(1秒)与 100(0.1秒)哪个重复的可能性比较大
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 获取当前线程对象的名字
String name = Thread.currentThread().getName();
System.out.println(name + "正在卖:" + ticket--);
}
}
}
}
}
//同步方法锁
public class TicketSynchronizedMethodDemo implements Runnable{
private int ticket = 100;
@Override
public void run() {
// 每个窗口卖票的操作
// 窗口 永远开启
while (true) {
sellTicket();
}
}
//同步方法锁
//隐含锁对象 谁调用该方法 就是该对象(this) 作为锁
public synchronized void sellTicket(){
if (ticket > 0) {
// 有票 可以卖
// 出票操作
// 使用sleep模拟一下出票时间
try {
//1000(1秒)与 100(0.1秒)哪个重复的可能性比较大
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 获取当前线程对象的名字
String name = Thread.currentThread().getName();
System.out.println(name + "正在卖:" + ticket--);
}
}
}
//同步锁
public class TicketLockDemo implements Runnable {
private int ticket = 100;
// Lock锁也称同步锁,加锁与释放锁简单化了
Lock lock = new ReentrantLock();
@Override
public void run() {
// 每个窗口卖票的操作
// 窗口 永远开启
while (true) {
lock.lock();
try {
if (ticket > 0) {
// 有票 可以卖
// 出票操作
// 使用sleep模拟一下出票时间
try {
//1000(1秒)与 100(0.1秒)哪个重复的可能性比较大
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 获取当前线程对象的名字
String name = Thread.currentThread().getName();
System.out.println(name + "正在卖:" + ticket--);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
项目连接
请配合项目代码食用效果更佳:
项目地址:
https://github.com/hesuijin/hesuijin-study-project
Git下载地址:
https://github.com.cnpmjs.org/hesuijin/hesuijin-study-project.git
import-code-demo项目模块下 thread包
可阅读concurrencyLockThreadDemo包下代码