解决线程安全问题
上面说过三个窗口卖票的问题,这是由于线程的安全出现的,下面我们来解决这种问题
共享数据发生的原因:
有共享数据
之前遇到的问题:
在卖票的过程中出现了 重票---->出现了线程安全
问题出现的原因: 当某个线程操作车票的时候,尚未完成,其他线程也参与进来操作车票
如何解决:当一个线程A操作ticket的时候 直到操作线程A操作完毕才能操作ticket 即使线程A阻塞也不能被改变
解决线程安全的两种方式:
一: 同步代码块
synchronized(同步监视器/锁){
共享代码块
}
1: 操作共享数据的代码就是共享代码块
2: 共享数据,多个线程共同操作的变量, 比如卖票代码中的ticket就是共享数据
3: 同步监视器就是锁🔐,任何一个类的对象都可以充当锁
要求多个线程公用一个锁(必须满足),
再实现Runnable接口创建现成的时候 我们可对synchronized中传递this来代替锁,在继承Thread中我们慎用this几乎是不能用的,继承Thread中可以使用当前类来代替
锁时什么?
任意对象都可以作为同步锁。所有对象都自动含有单一的锁(监视器)。 同步方法的锁:静态方法(类名.class)、非静态方法(this) 同步代码块:自己指定,很多时候也是指定为this或类名.class
Notice
必须确保使用同一个资源的多个线程共用一把锁,这个非常重要,否则就 无法保证共享资源的安全 一个线程类中的所有静态方法共用同一把锁(类名.class),所有非静态方 法共用同一把锁(this),同步代码块(指定需谨慎)
4 : 同步的好处解决了线程安全问题,---- 好处
操作同步代码时只能有一个线程运算,相当于单线程过程,效率低 --- 局限性
同步的范围
如何找问题,即代码是否存在线程安全?(非常重要) (1)明确哪些代码是多线程运行的代码 (2)明确多个线程是否有共享数据 (3)明确多线程运行代码中是否有多条语句操作共享数据 如何解决呢?(非常重要) 对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其 他线程不可以参与执行。 即所有操作共享数据的这些语句都要放在同步范围中
Synchronized 修饰的范围:
synchronized可以修饰范围的包括:方法级别,代码块级别;而实际加锁的目标包括:对象锁(普通变量,静态变量),类锁。
Runnable接口实现的同步代码块
class ThreeRunnable implements Runnable{ int ticket = 100; Object object = new Object(); @Override public void run() { while (true){
// 同步代码块开始 synchronized(this){ // 锁也可以是当前类本身 // synchronized(object){ // 锁可以是任意对象 if(ticket > 0){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+" : "+ticket); ticket --; }else{ break; } } } } }
Thread类实现同步代码块
class ThreadT implements Runnable{ int ticket = 100; Object object = new Object(); @Override public void run() { while (true){ synchronized(ThreadT.class){ // Class // synchronized(object){ // 因为多个对象线程必须使用一个锁, 此处不可以使用this不然就是 很多个锁了不是公用一个锁,因为你继承Thread最后开启多线程的的时候是要实现多个类对象,那么this指的就是当时实现类的对象
如果你实现三个类对象那么this就是三个不同的锁了,使用当前类本身可以因为类只是一个 是不过可以有多个对象
if(ticket > 0){ try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " : "+ticket); ticket --; }else{ break; } } } } }
也就是同步代码块 的sysnchronized(锁)中的锁 如果时实现Runnnable可以使用this如果时继承Thread不可以使用this因为继承Thread时会创造多个不同的对象的线程
二: 同步方法
我们将操作共享数控的代码声明再一个方法中,我们将此方法称为同步方法
synchronized还可以放在方法声明中,表示整个方法为同步方法。
例如: public synchronized void show (String name){ …. } //实现接口Runnable
而在继承Thread类后 这个同步方法必须时静态的因为继承Thread是要创建多个实现对象的
Runnable接口声明同步方法
class ThreadTOne implements Runnable{ private int ticket = 100; // 需要私有化 @Override public void run() { while (true){ show(); // 同步方法来保护线程安全 } } public static synchronized void show(){ // 同步监视器 返回的类型必须是synchronized类型的 if(ticket > 0){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+" : "+ticket); ticket --; } } }
Thread类声明同步方法
public class testSeven { public static void main(String[] args) { testS t1 = new testS(); testS t2 = new testS(); t1.start(); t2.start(); } } class testS extends Thread{ private static int ticket = 100; // 既然下面的静态方法要用此属性也需要时sttaic @Override public void run() { while (true){ show(); } } private static synchronized void show(){ // 同步监视器: 是当前类本身testS.class if(ticket > 0){ System.out.println(Thread.currentThread().getName()+" : "+ticket); ticket --; } } }
同步方法总结:
1: 同步方法需要涉及到同步监视器,只是他们的声明不同 2: 非静态的同步方法,同步监视器: this 静态的同步方法:同步监视器是当前类本身
继承Thread类的同步方法必须时static的因为继承Thread的类创建多个线程是要创建多个实现对象的 所以要限制调用的是同一个方法 而实现Runnable接口的类的同步方法不需要是static的因为它开启
多线程只需要创建一个实现类对象
synchronized解决单例模式的懒汉式线程安全问题
class BankTOne{ private BankTOne(){} private static BankTOne bankTOne = null; //方式一 /** public static synchronized BankTOne getBankTOne(){ // 此时 if(bankTOne == null){ bankTOne = new BankTOne(); } return bankTOne; } //上面的方法等于这种方法 public static BankTOne getBankTOne(){ synchronized(BankTOne.class){ if(bankTOne == null){ bankTOne = new BankTOne(); } return bankTOne; } } */ // 方式二 效率高, 这样第一个线程拿走最后一个的时候下面的线程就不用再进入其中了 public static synchronized BankTOne getBankTOne(){ if(bankTOne == null){ synchronized(BankTOne.class){ if(bankTOne == null){ bankTOne = new BankTOne(); } } } return bankTOne; } }
方式三:
解决线程的安全问题三: Local锁----- jdk5.0新增
使用 ReentrantLock 造锁
首先我们要使用ReetrantLock造锁 然后手动上锁手动解锁
import java.util.concurrent.locks.ReentrantLock; public class LocalTest { public static void main(String[] args) { localt l1 = new localt(); Thread t1 = new Thread(l1); Thread t2 = new Thread(l1); t1.setName("老子是窗口一"); t1.start(); t2.start(); } } class localt implements Runnable{ private int ticket = 100; // 实例化 ReentrantLock 创建一个锁 private ReentrantLock lock = new ReentrantLock(true); // 先进先出 @Override public void run() { while (true){ try{ // 锁住 lock.lock(); if(ticket > 0){ System.out.println(Thread.currentThread().getName()+" : "+ticket); ticket --; }else{ break; } }finally{ //解锁 lock.unlock(); } } } }
synchronized 和ReentrantLock 的异同点
相同: 二者都可以解决线程安全问题
不同: synchronized 执行完响应的代码之后自动释放同步监视器, 不需要手动解锁
local需要手动上锁 需要手动unlock来解锁
.
、如何找问题,即代码是否存在线程安全?(非常重要)