Java多线程–synchronized
文章主要讲解我对synchronized了解的一些特性和基本用法,如有错误欢迎留言指出。
首先我们先了解一下多线程和原子性
多线程:是指一个应用程序同时执行多个任务,一般来说一个任务就是一个线程 ,而一个应用程序有一个以上的线程我们称之为多线程。
那么问题就来了,当多个线程同时调用或处理共享数据时,如果共享的数据的原子性被破坏,线程同步的安全隐患也就暴露出来了。
原子性:是指一个操作或多个操作要么全部执行,且执行的过程不会被任何因素打断,要么就都不执行。
-
举个线程同步安全隐患的例子
- 如果多个处理器同时对共享变量进行读改写操作(如:i++),共享变量就会被多个处理器同时进行操作,这样读改写操作就不是原子的
-
synchronized 关键字:当它用来修饰类、方法或者代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。
可用来给对象和方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码。
![这里写图片描述](https://i-blog.csdnimg.cn/blog_migrate/c5d1d308ec7a2b625fcbef42a4ac6d84.png)
首先,先说一下synchronized作用的基本原理
我们暂且用原点代表每一个线程,假设现在线程A正在调用synchronized修饰的方法或代码块时,由于同一时刻最多只有一个线程执行这段代码(每个对象只有一个锁,谁拥有锁谁就可以运行该段代码),其余的线程如果想调用改方法,只能进入锁池等待锁的释放,当线程A执行完该段代码释放后,在锁池的所有进程将通过竞争的方式来获得该对象锁,成功获得对象锁的线程将进入方法,其余的继续留在锁池等待下一次锁的释放。
synchronized的基本用法及加锁对象
synchronized修饰用法有4种类型:类、非静态方法、静态方法以及代码块。他们的加锁对象和声明如图
接下来我通过一个简单的买票例子来了解synchronized
public class synTest{
public static void main(String[] args) {
//例子1 相同对象
Ticket ticket=new Ticket(10);
Thread t1=new Thread(ticket);
t1.start();
Thread t2=new Thread(ticket);
t2.start();
Thread t3=new Thread(ticket);
t3.start();
//例子2 不同对象
//Ticket tk1=new Ticket(5);
//Ticket tk2=new Ticket(5);
//Thread t1=new Thread(tk1);
//t1.start();
//Thread t2=new Thread(tk2);
//t2.start();
}
}
//车站购票
class Ticket implements Runnable
{
//车票总数
int sum;
//当前票数
int n=1;
public Ticket(int x)
{
this.sum=x;
}
@Override
public void run() {
//TODO Auto-generated method stub
while(true)
{
System.out.print("线程"+Thread.currentThread().getName()+"等待中 \t");
synchronized(this)
{
System.out.print("线程"+Thread.currentThread().getName()+"竞争成功");
if(n<=sum)
{
System.out.println("当前是线程"+Thread.currentThread().getName()+"出售第"+n+"张票 ");
n++;
}else{
System.out.println("票已售完,线程"+Thread.currentThread().getName()+"结束");
break;
}
}
}
}
}
例子一的运行结果是
例子二的运行结果是
从这两个例子可以看出来
- 当多个线程运行到被synchronized修饰的代码块时,线程间通过竞争决出一个线程获得锁并执行该段代码,而其余的将在被synchronized修饰的代码块外面继续等候。
- 同一对象的不同进程只能相互争取一把锁(例1),而不同对象的进程调用被修饰的方法时,由于一个对象只有一把锁,不同对象的进程争取相对对象的锁。即不同对象的线程不会相互竞争。
-
补充
-
当我们把synchronized修饰在不同地方(如方法),synchronized的作用域也随着改变。
-
无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁。
-
java中每个对象都可以作为锁,这是synchronized实现同步的基础
-
每个对象只有一个锁,谁拥有锁谁就可以运行该段代码
-
synchronized锁住的是对象,而不是代码块
-
synchronized的缺陷:
当某个线程进入同步方法获得对象锁,那么其他线程访问这里对象的同步方法时,必须等待或者阻塞,这对高并发的系统很容易导致系统的崩溃。如果某个线程在同步方法里面发生了死循环,那么它就永远不会释放这个对象锁,那么其他线程就要永远的等待。
同步块的情况与同步方法类似,只是同步块将同步控制的粒度缩小,这样能够更好的发挥多线程并行执行的效率。这也是为什么用synchronized时,能用修饰代码块解决的问题就绝不修饰方法。