一、什么时候会出现线程安全问题?
当多个线程同时访问一个资源(共享资源)时会出现线程安全。资源可以是一个变量、一个对象、一个文件、一个数据库表等。需要注意的是如果多个程序同时访问一个方法,定义在方法内部的局部变量并不是临界资源(共享资源),因为方法是在栈中执行的,而栈是线程私有的,因此不会出现线程安全问题。
二、如何解决线程安全问题?
通常来说在通过对访问共享资源代码加锁,当一个线程来访问时加锁,其他线程等待这个线程释放锁,当前线程执行完后加锁代码后释放锁资源。其他线程就可以访问共享资源了。在Java中,每一个对象都拥有一个锁标记(monitor),也称为监视器,多线程同时访问某个对象时,线程只有获取了该对象的锁才能访问。
Java提供了两种方式来实现同步互斥访问:synchronized和Lock。
三、synchronized同步代码块和同步方法代码示例
package sync;
public class TestSynchronized {
static TestSynchronized ts = new TestSynchronized();//所有线程共用这一个锁
public static void main(String[] args) {
new TestSynchronized().new Thread1().start();
new TestSynchronized().new Thread2().start();
}
private void invoke() {
try {
synchronized (ts) {//同步代码块
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ":" + "写数据库" + i);
Thread.sleep(100);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
class Thread1 extends Thread{
public void run(){
ts.invoke();
}
}
class Thread2 extends Thread{
public void run(){
ts.invoke();
}
}
}
运行结果:
Thread-0和Thread-1依次执行,没有出现相互竞争执行。
Thread-0:写数据库0
Thread-0:写数据库1
Thread-0:写数据库2
Thread-0:写数据库3
Thread-0:写数据库4
Thread-1:写数据库0
Thread-1:写数据库1
Thread-1:写数据库2
Thread-1:写数据库3
Thread-1:写数据库4
如果将锁加在方法上执行,结果依然一样:
private synchronized void invoke() {
try {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ":" + "写数据库" + i);
Thread.sleep(100);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
运行结果:
Thread-0:写数据库0
Thread-0:写数据库1
Thread-0:写数据库2
Thread-0:写数据库3
Thread-0:写数据库4
Thread-1:写数据库0
Thread-1:写数据库1
Thread-1:写数据库2
Thread-1:写数据库3
Thread-1:写数据库4
四、同步方法和同步代码块的区别。
synchronized代码块使用起来比synchronized方法要灵活得多。因为也许一个方法中只有一部分代码只需要同步,如果此时对整个方法用synchronized进行同步,会影响程序执行效率。而使用synchronized代码块就可以避免这个问题,synchronized代码块可以实现只对需要同步的地方进行同步。
五、synchronized需要注意的几点。
1)当一个线程访问一个对象的synchronized方法func1其他线程不能访问这个对象方法func1,因为一个对象只有一个锁。只有释放了锁才能访问func1。
2)当一个线程访问一个对象的synchronized方法func1其他线程可以访问这个对象的非synchronized方法,因为其他方法不需要该对象的锁。
3)如果一个类有static synchronized方法f1和synchronized方法f2,当两个线程分别访问f1和f2时是不会发生互斥的问题,因为一个是对象锁一个是类锁,不是同一个锁。
4)如果synchronized方法或者synchronized代码块发生异常,是不会出现由于异常导致死锁,因为发生异常后JVM会释放锁,这个Lock不一样,lock需要手动释放锁。