线程安全
1.线程安全讲解
如果有多个线程在同时运行,而这些线程可能会同时运行这段代码.程序每次运行结果和单线程运行的结果是一样的,而且他的变量的值也和预期的是一样的,就是线程安全.
我们可以通过一个案例,来演示线程安全问题:
电影院要买票,我们模拟电影院买票过程.假设此次电影提供100个座位.
模拟票:
public class Ticket implements Runnable {
static int ticket = 100;
@Override
public void run() {
while (true){
//判断是否还有票可以卖
if (ticket < 1){
break;
}
//模拟出票时间
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//模拟出票
System.out.println(Thread.currentThread().getName()+"正在出售第"+ticket+"张票");
ticket--;
}
}
}
测试类:
public class TicketTest {
public static void main(String[] args) {
//创建线程任务对象
Ticket ticket = new Ticket();
//创建四个窗口对象
Thread t1 = new Thread(ticket, "窗口1");
Thread t2 = new Thread(ticket, "窗口2");
Thread t3 = new Thread(ticket, "窗口3");
Thread t4 = new Thread(ticket, "窗口4");
//同时卖票
t1.start();
t2.start();
t3.start();
t4.start();
}
}
结果中有这样一部分现象:
发现程序出现两个问题:
- 出售重复票.
- 出售不存在的票.
这种问题,几个窗口(线程)票数不同步,这种问题称为线程不安全.
2.线程同步
当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写操作,就容易出现线程安全问题.
要解决上述多线程并发访问一个资源的安全性问题:也就是解决重复票与不存在的票问题,java中提供了(synchronized)来解决.
根据案例简述:
窗口1线程进入操作的时候,窗口2和窗口3窗口4线程只能在外等着,
窗口1操作结束,窗口1和窗口2和窗口3窗口4才有机会进入代码去执行。
也就是说在某个线程修改共享资源的时候,其他线程不能修改该资源,
等待修改完毕同步之后,才能去抢夺CPU资源,完成对应的操作,
保证了数据的同步性,解决了线程不安全的现象。
为了保证每个线程都能正常执行原有操作,java引入了线程同步机制.
3线程安全解决
3.1方式一 -----同步代码块
- 同步代码块:
synchronized
关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问.
格式:
synchronized(同步锁){
需要同步操作的代码
}
同步锁:
对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.
- 锁对象可以是任意类型对象.
- 多个线程对象要使用同一个锁对象.
使用同步代码块解决代码:
public class Ticket implements Runnable {
static int ticket = 100;
static Object obj = new Object();
@Override
public void run() {
while (true) {
synchronized (obj) {
//判断是否还有票可以卖
if (ticket < 1) {
break;
}
//模拟出票时间
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//模拟出票
System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket + "张票");
ticket--;
}
}
}
}
结果:
使用了同步代码块后,上述线程安全问题,解决了.
-
证明同步锁法法锁对象
-
/** * @author:donjor */ public class Demo { public void method1(){// abcdefg // 同步代码块 synchronized (this){ System.out.print("a"); System.out.print("b"); System.out.print("c"); System.out.print("d"); System.out.print("e"); System.out.print("f"); System.out.print("g");// 线程B抢走了 System.out.println(); } } // 非静态同步方法 public synchronized void method2(){// 123456 System.out.print("1"); System.out.print("2"); System.out.print("3"); System.out.print("4"); System.out.print("5"); System.out.print("6"); System.out.println(); } public static void method3(){// abcdefg // 同步代码块 synchronized (Demo.class){ System.out.print("a"); System.out.print("b"); System.out.print("c"); System.out.print("d"); System.out.print("e"); System.out.print("f"); System.out.print("g");// 线程B抢走了 System.out.println(); } } // 静态同步方法 public synchronized static void method4(){// 123456 System.out.print("1"); System.out.print("2"); System.out.print("3"); System.out.print("4"); System.out.print("5"); System.out.print("6"); System.out.println(); } } /** * @author:donjor */ public class Test1Demo { public static void main(String[] args) { /* 证明同步方法的锁对象: 静态同步方法: 该同步方法所在的类的字节码文件对象 即类名.class 非静态同步方法: 锁对象this 多线程保证数据安全条件: 1.对执行相同的代码进行加锁 2.多个线程的锁对象要一致 场景: 一个线程使用的是同步代码块对资源加锁 一个线程使用的是同步方法对资源加锁 同步代码块与同步方法的锁对象要一致,才能锁的住 */ // 创建一个Demo对象 Demo demo = new Demo(); /* // 创建一个线程A执行method1方法 new Thread(new Runnable() { @Override public void run() { // 调用method1方法 while (true){ demo.method1(); } } },"线程A").start(); // 创建一个线程B执行method2方法 new Thread(new Runnable() { @Override public void run() { // 调用method2方法 while (true) { demo.method2(); } } },"线程B").start();*/ // 创建一个线程A执行method1方法 new Thread(new Runnable() { @Override public void run() { // 调用method3方法 while (true){ demo.method3(); } } },"线程A").start(); // 创建一个线程B执行method2方法 new Thread(new Runnable() { @Override public void run() { // 调用method4方法 while (true) { demo.method4(); } } },"线程B").start(); } }
3.2方式二 -----同步方法
- 同步方法:使用
synchronized
修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在外等待.
格式:
public synchronized void method(){
可能会产生线程安全问题的代码
}
使用同步方法代码如下:
public class Ticket implements Runnable {
static int ticket = 100;
static Object obj = new Object();
@Override
public void run() {
while (true) {
if (sellTicket()) break;
}
}
private synchronized boolean sellTicket() {
//判断是否还有票可以卖
if (ticket < 1) {
return true;
}
//模拟出票时间
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//模拟出票
System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket + "张票");
ticket--;
return false;
}
}
注意:
- 静态同步方法锁对象: 类名.class
- 非静态同步方法锁对象:this
3.3方式三 -----Lock锁
java.util.concurrent.locks.Lock机制提供了比synchronized代码块和synchronized
方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。
Lock锁也称同步锁,加锁与释放锁方法化了,如下:
public void lock()
:加同步锁。public void unlock()
:释放同步锁。
使用如下:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Ticket implements Runnable {
static int ticket = 100;
Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
lock.lock();
//判断是否还有票可以卖
if (ticket > 0) {
break;
}
//模拟出票时间
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//模拟出票
System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket + "张票");
ticket--;
lock.unlock();
if (ticket < 1) {
break;
}
}
}
}
总结
同步机制:
-
同步代码块
-
应用场景:当需要对方法中部分代码进行加锁,那么就可以使用同步代码块
-
格式
-
synchronized(锁对象){ }
-
锁对象:
- 可以是任意对象
- 多条线程执行相同的代码,要保证数据安全的话,就需要保证所有线程的锁对象一致
-
-
-
同步方法
- 应用场景:当需要对一个方法中所有的代码进行加锁,那么就使用同步方法
- 格式: 在返回值类型前面加 synchronized
- 锁对象
- 静态同步方法锁对象: 类名.class
- 非静态同步方法锁对象:this
-
Lock锁
public void lock()
:加同步锁。public void unlock()
:释放同步锁。
,那么就可以使用同步代码块
-
格式
-
synchronized(锁对象){ }
-
锁对象:
- 可以是任意对象
- 多条线程执行相同的代码,要保证数据安全的话,就需要保证所有线程的锁对象一致
-
-
同步方法
- 应用场景:当需要对一个方法中所有的代码进行加锁,那么就使用同步方法
- 格式: 在返回值类型前面加 synchronized
- 锁对象
- 静态同步方法锁对象: 类名.class
- 非静态同步方法锁对象:this
-
Lock锁
public void lock()
:加同步锁。public void unlock()
:释放同步锁。