活动地址:CSDN21天学习挑战赛
目录
一、线程安全
1.1 线程安全问题
①在多线程编程时,由于CPU对线程的调度具有一定的随机性,当多个线程访问同一个资源时,如果控制不好,也会造成数据的不正确性;
②线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
举例:通过模拟火车票的抢票过程,具象化演示线程安全问题
package Synchronized;
//线程不安全案例——不安全买票(有负数)
public class UnsafeBuyTicket{
public static void main(String[] args) {
BuyTicket station = new BuyTicket();
new Thread(station,"购票人1").start();
new Thread(station,"购票人2").start();
new Thread(station,"抢票软件").start();
}
}
class BuyTicket implements Runnable{
//票
private int ticketNums = 10;
boolean flag = true;//外部停止方式
@Override
public void run() { //重写run函数,执行买票操作
while (flag) {
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void buy() throws InterruptedException {
//判断是否有票
if(ticketNums <= 0) {
flag = false;
return;
}
//模拟延时
Thread.sleep(100);
//买票
System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);
}
}
由于线程的调度室随机的,每次运行结果差异较大。第一次运行结果如下:
第二次运行结果:
发现运行结果出现了两个问题:
1)相同的票数,比如7这张票被买了三回。
2)不存在的票,比如0票与-1票,是不存在的。
这种问题,几个购买者的购买操作(线程)同时进行了,且争夺同一个资源,这种问题称为线程不安全。
二、线程同步
要解决上述多线程并发访问一个资源的安全性问题,Java中提供了同步机制(Synchronized)来解决。
同步机制等价于一个“等待机制”:
当多个线程要访问一个资源对象,他们就会进入这个对象的等待池形成队列,前面的线程访问该对象时获得“锁”独占资源,资源使用结束后才释放“锁”,下一个线程再去访问该资源对象(整个“队列+锁”的同步机制 类似于 洗手间的排队等待上厕所)。
同步机制的使用有三种方式:
1)同步代码块
2)同步方法
3)锁机制
三、同步代码块
格式:
synchronized( Obj ){
需要同步操作的代码;
}
Obj为同步锁:1.)锁对象 可以是任意类型;
2)多个线程对象要使用同一把锁(在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等待(BLOCKED阻塞状态))
使用同步代码块解决代码:
package Synchronized;
//线程不安全案例——不安全买票
public class UnsafeBuyTicket{
public static void main(String[] args) {
BuyTicket station = new BuyTicket();
new Thread(station,"购票人1").start();
new Thread(station,"购票人2").start();
new Thread(station,"抢票软件").start();
}
}
class BuyTicket implements Runnable{
//票
private int ticketNums = 10;
boolean flag = true;//外部停止方式
Object lock = new Object(); //定义一个同步锁(Object对象)
@Override
public void run() {
synchronized(lock) { //run函数运行过程中使用synchronized同步锁
while (flag) {
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
private void buy() throws InterruptedException {
//判断是否有票
if(ticketNums <= 0) {
flag = false;
return;
}
//模拟延时
Thread.sleep(100);
//买票
System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);
}
}
运行结果:
可以看到,不存在买到相同的票 ,或是票号不存在的情况,此时线程安全。
四、同步方法
同步方法 :使用synchronized关键字修饰的方法。保证A线程执行该方法的时候,其他线程只能在方法外等待。
格式:
public synchronized void method(){
可能会产生线程安全问题的代码 ;//方法的定义
}
使用同步方法解决代码:
package Synchronized;
//线程不安全案例——不安全买票
public class UnsafeBuyTicket{
public static void main(String[] args) {
BuyTicket station = new BuyTicket();
new Thread(station,"购票人1").start();
new Thread(station,"购票人2").start();
new Thread(station,"抢票软件").start();
}
}
class BuyTicket implements Runnable{
//票
private int ticketNums = 10;
boolean flag = true;//外部停止方式
@Override
public void run() {
while (flag) {
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//synchronized同步方法,锁的是this
private synchronized void buy() throws InterruptedException {
//判断是否有票
if(ticketNums <= 0) {
flag = false;
return;
}
//模拟延时
Thread.sleep(100);
//买票
System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);
}
}
运行结果:
同样地,不存在买到相同的票 ,或是票号不存在的情况,此时线程安全,并且线程的调度更加随机。
五、锁机制
Lock锁(又称同步锁),通过JUC模块的java.util.concurrent.locks.Lock 接口,显式定义同步锁对象,将加锁与释放锁方法化,即调用“lock.lock(); //加锁”和“lock.unlock();//解锁”的操作。
格式如下:
public class T{
...... ; //定义变量
Lock lock = new ReentrantLock(); //创建一个锁对象
/*
* 重写run()函数,执行方法动作
*/
@Override
public void run() {
// try{
lock.lock(); //加锁
try {
Thread.sleep(...);...... ; //保证线程安全的代码
} catch (InterruptedException e) {
e.printStackTrace();
}// }
// finally{ //如果同步代码有异常,要将unlock()写入fianally块,保证锁的释放
lock.unlock(); //释放锁
// }
}
}
代码如下:
package Synchronized;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//线程不安全案例——不安全买票(有负数)
public class UnsafeBuyTicket{
public static void main(String[] args) {
BuyTicket station = new BuyTicket();
new Thread(station,"购票人1").start();
new Thread(station,"购票人2").start();
new Thread(station,"抢票软件").start();
}
}
class BuyTicket implements Runnable{
//票
private int ticketNums = 10;
boolean flag = true;//外部停止方式
//同步锁Lock解决线程安全问题
Lock lock = new ReentrantLock();
@Override
public void run() {
while (flag) {
lock.lock(); //由lock方法显式加锁
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.unlock(); //由unlock方法显式释放锁
}
}
private void buy() throws InterruptedException {
//判断是否有票
if(ticketNums <= 0) {
flag = false;
return;
}
//模拟延时
Thread.sleep(100);
//买票
System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);
}
}
运行结果如下,此时线程安全。