Artigo Diretório
Prefácio
Primeiro usamos o conhecimento adquirido de multi-threading para simular o processo de venda de ingressos de cinema, levando a problemas de segurança de thread.
/*
需求:某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口
请设计一个程序模拟该电影院售票过程。
使用Thread类实现
分析: 1.三个窗口相当于三个线程
2.100张票属于共享资源
*/
- Código 1: implementação do método 1 de implementação multi-threaded
public class 案例演示1 {
public static void main(String[] args) {
CellThread th1 = new CellThread("窗口1");
CellThread th2 = new CellThread("窗口2");
CellThread th3 = new CellThread("窗口3");
th1.start();
th2.start();
th3.start();
}
}
class CellThread extends Thread{
//设置票为静态变量,即共享资源
static int piao = 100;
//提供无参构造
public CellThread() {
}
//创建有参构造,不再调用setName()方法给线程起名字
public CellThread(String name) {
super(name);
}
@Override
public void run() {
while (true){
if(piao>0){
System.out.println(Thread.currentThread().getName()+"正在售卖"+(piao--)+"张票");
}
}
}
}
- Código 2: Implementação usando o método 2 de multithreading
public class 案例演示2 {
public static void main(String[] args) {
Myrunable myrunable = new Myrunable();
//我们只创建了一个任务,所以是共同售卖100张票。
Thread th1 = new Thread(myrunable, "窗口1");
Thread th2 = new Thread(myrunable, "窗口2");
Thread th3 = new Thread(myrunable, "窗口3");
th1.start();
th2.start();
th3.start();
}
}
class Myrunable implements Runnable{
static int piao=1000;
@Override
public void run() {
while (true) {
if (piao > 0) {
//模拟了一下网络延迟,发现出现不合理的数据,即出现了线程安全问题。
//1.出现了0票和负数票,是因为线程并发执行导致的
//2,出现了重复票,是因为原子性所导致的。(自行科普)
try {
Thread.sleep(50);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName() + "正在售卖" + (piao--) + "张票");
}
}
}
}
O código acima tem problemas de segurança de thread
- Condições para problemas de segurança de thread:
- É um ambiente multithread
- Existem recursos compartilhados entre vários threads
- Existem várias instruções operando em recursos compartilhados e as operações em recursos compartilhados não são operações atômicas
1. Sincronize blocos de código para resolver problemas de segurança de thread
- Use o formato do bloco de código de sincronização:
/*
我们使用同步代码块,将可能出现线程安全问题的代码包裹起来。
synchronized (锁对象){
需要同步的代码
}
锁对象:可以是java中的任意一个对象,常new Object()。注意不可以在括号内new对象,这样每个线程持有的不是同一把锁,没有效果。需要将该对象定义为静态成员变量,被线程共享。
需要同步的代码:可能出现线程安全的问题。(千万不可以出现死循环,这样线程会出现阻塞)
*/
public class 使用同步代码块解决线程安全问题 {
public static void main(String[] args) {
Myrunable1 myrunable = new Myrunable1();
Thread th1 = new Thread(myrunable, "窗口1");
Thread th2 = new Thread(myrunable, "窗口2");
Thread th3 = new Thread(myrunable, "窗口3");
th1.start();
th2.start();
th3.start();
}
}
class Myrunable1 implements Runnable {
static int piao = 100;
//注意:多个线程要使用同一把锁
static Object obj=new Object();
@Override
public void run() {
while (true) {
//同步代码块,包裹可能出现线程安全问题的代码块
//锁对象:可以是java中的任意一个对象,常new Object()
synchronized (obj){
if (piao > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName() + "正在售卖" + (piao--) + "张票");
}
}
}
}
}
/*
为什么加了同步代码块就能解决线程安全问题?
某个线程一旦抢占cpu时间片,进入同步代码块,就会持有该锁。
其他线程没有该锁,只能等待,无法并发执行。
当持有锁的线程出了同步代码块,就会释放锁,然后所有线程再次争抢时间片。
但是加锁,数据安全,效率会降低。
*/
Em segundo lugar, o método de sincronização resolve problemas de segurança de thread
- Podemos extrair o código thread-safe em um método e adicionar synchronized ao método para transformar o método em um método sincronizado, portanto, não há necessidade de definir um objeto de bloqueio.
public class 同步方法解决线程安全问题 {
public static void main(String[] args) {
Myrunable2 myrunable = new Myrunable2();
Thread th1 = new Thread(myrunable, "窗口1");
Thread th2 = new Thread(myrunable, "窗口2");
Thread th3 = new Thread(myrunable, "窗口3");
th1.start();
th2.start();
th3.start();
}
}
class Myrunable2 implements Runnable {
static int piao = 100;
@Override
public void run() {
//1.将可能出现线程安全问题的代码抽成方法
maipiao();
}
//2.在方法上加上synchronized,将该方法变成个同步方法
public synchronized void maipiao(){
while (true){
if (piao > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName() + "正在售卖" + (piao--) + "张票");
}
}
}
}
/*
我们单独使用同步代码块或同步方法都可以解决线程安全问题,
但是我们混合使用同步代码块和同步方法时候,可能不能解决线程安全问题。
那么是因为同步代码块和同步方法使用的不是同一把锁。
同步方法使用的默认锁对象是this(当将同步方法定义成静态方法时,锁对象是当前类对象的字节码对象)
同步代码块使用的锁对象是任意java对象
解决方法:将同步代码块的锁对象与同步方法保持一致。
*/
- Observe o objeto de bloqueio
- O objeto de bloqueio do bloco de código de sincronização é qualquer objeto java
- O objeto de bloqueio do método de sincronização é este
- O objeto de bloqueio do método de sincronização estático é o objeto bytecode atual
Não misture o bloco de código de sincronização e o método de sincronização. Se você usá-lo, mantenha o objeto de bloqueio do bloco de código de sincronização consistente com o objeto de bloqueio do método de sincronização .
Três, bloqueios de bloqueio resolvem problemas de segurança de thread
- Após o JDK5, um novo objeto de bloqueio Lock é fornecido.A implementação de Lock fornece uma gama mais ampla de operações de bloqueio do que o uso de métodos e instruções sincronizados.
- ReentrantLock é uma classe de implementação de sua interface
- void lock () adicionar bloqueio void unlock () liberar bloqueio
- formato:
/*
加锁
lock.lock();
try{
可能出现线程安全的代码
}catch (Exception e){
e.printStackTrace();
}finally {
//释放锁
lock.unlock();
}
*/
public class jdk15之后lock锁 {
public static void main(String[] args) {
Myrunable3 myrunable = new Myrunable3();
Thread th1 = new Thread(myrunable, "窗口1");
Thread th2 = new Thread(myrunable, "窗口2");
Thread th3 = new Thread(myrunable, "窗口3");
th1.start();
th2.start();
th3.start();
}
}
class Myrunable3 implements Runnable {
static int piao = 100;
//1.创建lock实现类的一个对象
static Lock lock =new ReentrantLock();
@Override
public void run() {
while (true) {
//2.加锁
lock.lock();
try{
if (piao > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName() + "正在售卖" + (piao--) + "张票");
}
}catch (Exception e){
e.printStackTrace();
}finally {
//3.释放锁
lock.unlock();
}
}
}
}