Java多线程同步
多线程同步
多线程的安全问题
线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写
操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,
否则的话就可能影响线程安全。
比如有这样的一个例子:
模拟电影院卖票,有100张票,有3个窗口同时卖票
开启3个线程对卖这100张票
代码:
public class Ticket implements Runnable {
private int ticket = 100;
/*
* 执行卖票操作
*/
@Override
public void run() {
//每个窗口卖票的操作
//窗口 永远开启
while (true) {
if (ticket > 0) {//有票 可以卖
//出票操作
//使用sleep模拟一下出票时间
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//获取当前线程对象的名字
String name = Thread.currentThread().getName();
System.out.println(name + "正在卖:" + ticket);
ticket--;
}else{
break;
}
}
}
}
卖票:
public class Test {
public static void main(String[] args) throws InterruptedException {
//创建线程任务对象
Ticket ticket = new Ticket();
//创建三个窗口对象
Thread t1 = new Thread(ticket, "窗口1");
Thread t2 = new Thread(ticket, "窗口2");
Thread t3 = new Thread(ticket, "窗口3");
//同时卖票
t1.start();
t2.start();
t3.start();
}
}
最后产生的结果:
有两个问题:
- 相同的票数,比如5这张票被卖了两回。
- 不存在的票,比如0票与-1票,是不存在的。
这种问题,几个窗口(线程)票数不同步了,就是线程的安全问题
解决方法就是使用多线程同步
那么什么是多线程同步呢?
根据例子来简述:
-
窗口1线程进入操作的时候,窗口2和窗口3线程只能在外等着,窗口1操作结束,窗口1和窗口2和窗口3才有机会进入代码去执行。也就是说在某个线程修改共享资源的时候,其他线程不能修改该资源,等待修改完毕同步之后,才能去抢夺CPU资源,完成对应的操作,保证了数据的同步性,解决了线程不安全的现象。
3种方式完成同步操作
第一种方式:同步代码块
synchronized(同步锁){
需要同步操作的代码
}
同步锁:
对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.
- 锁对象 可以是任意类型。
- 多个线程对象 要使用同一把锁。
注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着
代码:
public class Ticket implements Runnable {
private int ticket = 100;
//创建一个锁对象
Object lock = new Object();
/*
* 执行卖票操作
*/
@Override
public void run() {
//每个窗口卖票的操作
//窗口 永远开启
while (true) {
synchronized (lock){
if (ticket > 0) {//有票 可以卖
//出票操作
//使用sleep模拟一下出票时间
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//获取当前线程对象的名字
String name = Thread.currentThread().getName();
System.out.println(name + "正在卖:" + ticket);
ticket--;
}else{
break;
}
}
}
}
}
最后结果:
解决了线程的安全问题
第二种方法:同步方法
使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外
等着。
public synchronized void method(){
可能会产生线程安全问题的代码
}
这里的同步锁是谁?
对于非static方法,同步锁就是this。
对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。
使用代码:
public class Ticket implements Runnable {
private int ticket = 100;
//创建锁对象
//Object lock = new Object();
/*
* 执行卖票操作
*/
@Override
public void run() {
//每个窗口卖票的操作
//窗口 永远开启
while (true) {
sellTicket();
if (ticket<=0){
break;
}
}
}
public synchronized void sellTicket(){
if (ticket > 0) {//有票 可以卖
//出票操作
//使用sleep模拟一下出票时间
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//获取当前线程对象的名字
String name = Thread.currentThread().getName();
System.out.println(name + "正在卖:" + ticket);
ticket--;
}
}
}
结果:
也解决了线程线程安全问题
有可能整个方法中所有的代码都是需要加同步的, 这个时候用同步代码块就不是特别合适了. 用同步方法会更简单一些.
第三种方法:Lock锁:
java.util.concurrent.locks.Lock
机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,
同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。
有以下两个方法,分别是加锁与释放锁:
public void lock()//加同步锁
public void unlock()//释放同步锁
注:Lock锁是接口,所以要用它的实现类ReentrantLock
来获取锁对象
使用代码:
public class Ticket implements Runnable {
private int ticket = 100;
//创建锁对象
//Object lock = new Object();
//创建lock锁对象
Lock lock = new ReentrantLock();
/*
* 执行卖票操作
*/
@Override
public void run() {
//每个窗口卖票的操作
//窗口 永远开启
while (true) {
lock.lock();
if (ticket > 0) {//有票 可以卖
//出票操作
//使用sleep模拟一下出票时间
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//获取当前线程对象的名字
String name = Thread.currentThread().getName();
System.out.println(name + "正在卖:" + ticket);
ticket--;
}else{
break;
}
lock.unlock();
}
}
}
结果:
也解决了线程安全问题。
以上就是解决安全问题的3中方式
上一篇: 春秋时期最后一位霸主是谁?为何说他成为霸主是应当的?
下一篇: 刘邦为什么会那么看重周勃?原因是什么