Java 多线程安全问题与synchronized同步锁
先用多线程来模拟一个卖票的过程。
public class ThreadDem3 {
public static void main(String[] args) {
Ticket t=new Ticket();//创建票对象
Thread t1=new Thread(t);//创建线程
Thread t2=new Thread(t);//创建线程
Thread t3=new Thread(t);//创建线程
t1.start();//开启线程 卖票
t2.start();//开启线程 卖票
t3.start();//开启线程 卖票
}
}
class Ticket implements Runnable{
private int tickets=30;//票总数:30
@Override
public void run() {//覆写run方法 该方法卖票
while (true){
if (tickets>0){
try{ Thread.sleep(20);//线程睡眠20毫秒
}catch (InterruptedException e){
System.out.println(e);
}
System.out.println("Thread:"+Thread.currentThread().getName()+" tickets:"+tickets--);//输出线程对象名和票数
}
}
}
}
执行结果:
上述卖票的例子,继续进行分析发现,当3个线程都开启之后,CPU会在这3个线程之间随机切换。当四个线程把票卖的只剩下最后一张时,发生了上图的情况,这时会发现输出的票出现0号票、-1号票,对于多线程操作最怕的就是出现这种线程安全问题。
线程安全问题发生的原因:
- 线程任务中在操作共享的数据。
- 线程任务操作共享数据的代码有多条(运算有多个)。
解决(共享的资源):
只要让一个线程在执行线程任务时将多条操作共享数据的代码执行完,在执行过程中,不要让其他线程参与运算,就可以解决这个问题。
解决这个问题Java中给我们提供相应的独立代码块,这段代码块需要使用关键字synchronized
来标识其为一个同步代码块。synchronized
关键字在使用时需要一个对象作为标记,当任何线程进入synchronized标识的这段代码时,首先都会先判断目前有没有线程正在使用synchronized标记对象,若有线程正在使用这个标记对象, 那么当前这个线程就在synchronized标识的外面等待,直到获取到这个标记对象后,这个线程才能执行同步代码块。
给上面的代码加上同步:
public class ThreadDem3 {
public static void main(String[] args) {
Ticket t=new Ticket();//创建票对象
Thread t1=new Thread(t);//创建线程
Thread t2=new Thread(t);//创建线程
Thread t3=new Thread(t);//创建线程
t1.start();//开启线程 卖票
t2.start();//开启线程 卖票
t3.start();//开启线程 卖票
}
}
class Ticket implements Runnable{
private int tickets=30;//总票数 :30
Object obj=new Object();//加锁的对象
@Override
public void run() {
while (true){
synchronized (obj){//同步代码块
if (tickets>0){
try{ Thread.sleep(20);//线程睡眠20毫秒
}catch (InterruptedException e){
System.out.println(e);
}
System.out.println("Thread:"+Thread.currentThread().getName()+" tickets:"+tickets--);//输出线程对象名和票数
}
}
}
}
}
执行结果:
加入同步锁后,线程安全问题解决。
同步的好处和弊端
同步好处:解决多线程安全问题。这里举例(火车上的卫生间)说明同步锁机制。
同步弊端:降低了程序的性能。每个线程都要去判断锁机制,那么会增加程序运行的负担,同时只要做判断,CPU都要处理,那么也会消耗CPU的资源。即就是加同步会降低程序的性能。
同步的前提:必须保证多个线程在同步中使用的是同一个锁。
同步代码块和同步函数的区别
同步函数使用的锁是固定的this。当线程任务只需要一个同步时完全可以使用同步函数。
同步代码块使用的锁可以是任意对象。当线程任务中需要多个同步时,必须通过锁来区分,这时必须使用同步代码块。同步代码块较为常用。
静态同步函数使用的锁
静态同步函数使用的锁不是this,而是字节码文件对象, 类名.class。