既然一个进程中可以有多个线程,并且多个线程共享资源,那么,由谁来管理这些个共享的资源,以至于不被各个线程争抢呢?
文章目录
什么是多线程安全
线程安全的定义:
- 对全局变量或者静态变量,同时都有读、写操作,从而造成变量数据紊乱,产生错误。
发生线程安全的原因:
- 线程安全问题都是由全局变量及静态变量引起的。
- 若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;
- 若有
多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全
。
解决线程安全的方案:
- Java 提供了两种锁机制来控制多个线程对共享资源的互斥访问,
- 第一个是 JVM 实现的 synchronized(内部锁),
- 而另一个是 JDK1.5 实现的 ReentrantLock(重入锁)。
synchronized实现多线程互斥
对synchronized的理解:
- 是Java 中的关键字,synchronized
是内置的语言实现
; - synchronized 在发生异常时,会
自动释放线程占有的锁
,因此不会导致死锁现象发生;
synchronized特性:
- synchronized:
同步(锁,又称为内部锁,因为它是用Java关键字实现的)
,可以修饰代码块和方法,能够保证在同一时刻最多只有一个线程执行该段代码
。 - 一旦使用synchronized,多线程执行起来就
慢
了。因为每次执行“锁中的代码时”,都要检查
有没有线程正在占用这段代码。 - 如果被占用了,则要等待这个占用线程执行完毕,才
有机会
执行该线程。
synchronized使用的三种方式:
修饰方法
:对当前对象加锁,进入同步代码前要获得当前对象的锁。修饰静态方法
:给当前类加锁,会作用于类的所有对象实例。
- 因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管new了多少个对象,只有一份)。
- 所以如果一个线程A调用一个实例对象的非静态 synchronized 方法,而线程B需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁。
修饰代码块
:指定加锁对象。
总结: synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。synchronized 关键字加到实例方法上是给对象实例上锁。
同步代码块
同步代码块的格式:
synchronized(锁对象){ //锁对象是所有对象都能共享的对象
……
}
注意: 它只作用于同一个对象,如果调用两个对象上的同步代码块,就不会进行同步。
理解synchronized如何加锁对象
public class ThreadDemo {
public static void main(String[] args) {
MyMultiThread mmt = new MyMultiThread();
Thread t1 = new Thread(mmt);
Thread t2 = new Thread(mmt);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
class MyMultiThread implements Runnable {
private Object obj = new Object();; //synchronized加锁的对象时obj
private int num = 100;
@Override
public void run() {
while(true) {
synchronized(obj) { //加锁obj对象,一旦有线程获得这个对象,其他线程试图获取它时将阻塞!
if(num>0) {
System.out.println(Thread.currentThread().getName()+"执行减法!"+num--);
} else {
break;
}
}
}
}
}
同步代码块实例
问题: 实现火车站窗口卖票功能
1, 火车站卖票窗口有3个窗口,同时卖票;
2, 火车站有一定数量的火车票,每售出一张数量减一;当火车票的数量小于1的时候,停止售票;
用同步代码块实现卖火车票:
public class Main {
public static void main(String[] args) {
// 创建线程对象
TicketThread tt = new TicketThread();
Thread t = new Thread(tt);
t.setName("窗口1");
Thread t2 = new Thread(tt);
t2.setName("窗口2");
Thread t3 = new Thread(tt);
t3.setName("窗口3");
// 启动线程对象
t.start();
t2.start();
t3.start();
}
}
class TicketThread implements Runnable {
int tickets = 100; // 火车票
Object obj = new Object();
@Override
public void run() {
//出售火车票
while(true) {
synchronized (obj) {
if(tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":出售" +tickets--);
}
}
}
}
}
同步方法
同步方法的格式:
修饰符 synchronized 返回类型 方法名(参数列表) {
方法体
}
**同步方法: **
- 使用关键字synchronized修饰的方法,一旦被一个线程访问,则整个方法全部锁住,其他线程则无法访问。
- 普通方法中的锁是this对象;
- 它和同步代码块一样,作用于同一个对象。
用同步方法实现卖火车票
public class Main {
public static void main(String[] args) {
// 创建线程对象
TicketThread tt = new TicketThread();
Thread t = new Thread(tt);
t.setName("窗口1");
Thread t2 = new Thread(tt);
t2.setName("窗口2");
Thread t3 = new Thread(tt);
t3.setName("窗口3");
// 启动线程对象
t.start();
t2.start();
t3.start();
}
}
class TicketThread implements Runnable {
int tickets = 100; // 火车票
Object obj = new Object();
@Override
public void run() {
//出售火车票
while(true) {
method2();
}
}
private synchronized void method2() { //同步方法的锁对象就是this对象
if (tickets > 0) {
try {
Thread.sleep(100); //线程休眠
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + tickets--);
}
}
}
同步静态方法
同步静态方法的格式:
//同步一个静态方法:
修饰符 synchronized static 返回类型 方法名(参数列表) {
方法体
}
特性:
- static方法中的锁是类本身的字节码文件,即“类名.class”;
- 作用于整个类。
- 也就是说,这个类的所有实例都被加上同一把锁。
用同步static方法实现卖火车票
public class Main {
public static void main(String[] args) {
// 创建线程对象
TicketThread tt = new TicketThread();
Thread t = new Thread(tt);
t.setName("窗口1");
Thread t2 = new Thread(tt);
t2.setName("窗口2");
Thread t3 = new Thread(tt);
t3.setName("窗口3");
// 启动线程对象
t.start();
t2.start();
t3.start();
}
}
class TicketThread implements Runnable {
static int tickets = 100; // 火车票
Object obj = new Object();
@Override
public void run() {
while(true) {//出售火车票
method2();
}
}
private static synchronized void method2() { //静态方法中,锁是本类自己,即“本类名.class”
if (tickets > 0) {
try {
Thread.sleep(100); //线程休眠
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + tickets--);
}
}
}
JDK1.6之后对锁的优化
由于synchronized关键字对于多线程的同步的支持性不是很友好。所以,在JDK1.6 对锁的实现引入了大量的优化,如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销。
具体JDK1.6对锁做了哪些优化和升级,请关注本站的后续文章!