在JAVA中,线程与线程之间的数据是共享的,因此,当多个线程同时改变相同的对象,线程会相互倾轧。根据线程访问数据的不同线性,会产生被腐蚀的对象。
为了避免这种现象,我们需要对需要保护的对象上锁,这样在同一时间只能有一个线程访问这个数据,而其他的线程进入等待队列,知道这个线程访问完数据并释放锁,等待队列中的线程才能获得锁。
上锁的方法有多种,这里只写出常用的三种
1.synchronized代码块
synchronized (object)
{
}
例如:
public class TraditionalThreadSynchronized {
public static void main(String[] args) {
final Outputter output = new Outputter();
new Thread() {
public void run() {
output.output("zhangsan");
};
}.start();
new Thread() {
public void run() {
output.output("lisi");
};
}.start();
}
}
class Outputter {
public void output(String name) {
// TODO 为了保证对name的输出不是一个原子操作,这里逐个输出name的每个字符
synchronized (this)
{
for(int i = 0; i < name.length(); i++) {
System.out.print(name.charAt(i));
// Thread.sleep(10);
}
}
}
}
object 必须是需要互斥的多个线程间的共享对象。
因此下面这段代码的锁是无效的
public class TraditionalThreadSynchronized {
public static void main(String[] args) {
final Outputter output = new Outputter();
new Thread() {
public void run() {
output.output("zhangsan");
};
}.start();
new Thread() {
public void run() {
output.output("lisi");
};
}.start();
}
}
class Outputter {
public void output(String name) {
// TODO 为了保证对name的输出不是一个原子操作,这里逐个输出name的每个字符
Object object=new Object();
synchronized (object)
{
for(int i = 0; i < name.length(); i++) {
System.out.print(name.charAt(i));
// Thread.sleep(10);
}
}
}
}
2.synchronized 方法
public synchronized void output(String name) {
// TODO 线程输出方法
for(int i = 0; i < name.length(); i++) {
System.out.print(name.charAt(i));
}
}
这种方式就相当于用this锁住整个方法内的代码块,如果用synchronized加在静态方法上,就相当于用××××.class锁住整个方法内的代码块。使用synchronized在某些情况下会造成死锁,死锁问题以后会说明。使用synchronized修饰的方法或者代码块可以看成是一个原子操作。
每个锁对都有两个队列,一个是就绪队列,一个是阻塞队列,就绪队列存储了将要获得锁的线程,阻塞队列存储了被阻塞的线 程,当一个线程被唤醒(notify)后,才会进入到就绪队列,等待CPU的调度,反之,当一个线程被wait后,就会进入阻塞队列,等待下一次被唤醒, 这个涉及到线程间的通信,一个线程执行互斥代码过程如下:
1. 获得同步锁;
2. 清空工作内存;
3. 从主内存拷贝对象副本到工作内存;
4. 执行代码(计算或者输出等);
5. 刷新主内存数据;
6. 释放同步锁。
所以,synchronized既保证了多线程的并发有序性,又保证了多线程的内存可见性。
3.lock方法
Lock lc=new ReentrantLock();
lc.lock();
// do work
lc.unlock();
这种方法一般不推荐使用