线程安全的定义:当多个线程访问某个类(对象或方法)时,这个类始终都能表现出正确的行为,那么这个类(对象或方法)就是 线程安全的。
synchronized:可以在任意对象或方法上加锁,而加锁的这段代码称为“互斥区”或“临界区”。
简单synchronized例子:
public class FirstThread extends Thread {
private int count = 5;
@Override
public synchronized void run() {
count--;
System.out.println(this.currentThread().getName()+"count = "+count);
}
public static void main(String[] args) {
FirstThread ft = new FirstThread();
Thread t1 = new Thread(ft,"t1");
Thread t2 = new Thread(ft,"t2");
Thread t3 = new Thread(ft,"t3");
Thread t4 = new Thread(ft,"t4");
Thread t5 = new Thread(ft,"t5");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
运行结果:
t3count = 4
t5count = 3
t1count = 2
t2count = 1
t4count = 0
总结:当多个线程访问run方法时,以排队的方式进行处理(这里排队指的是按照CPU分配的先后顺序而定的),一个线程要执行synchronized修饰的方法时,首先去尝试获得锁,若拿到锁,则执行synchronized代码体内容;拿不到锁,这个线程就会不断地尝试获得这把锁,直至拿到为止,而且是多个线程同时去竞争这把锁。(也就是会有锁竞争的问题)。
多个线程多个锁:多个线程,每个线程都可以拿到自己指定的锁,分别获得锁之后,执行synchronized方法体的内容。
代码例子:
public class SecondThread {
private static int count = 0;
public static synchronized void printCount(String st) {
try{
if (st.equals("a")){
count = 100;
System.out.println(" a ,set count end !!!");
Thread.sleep(1000);
}else{
count = 200;
System.out.println(" b ,set count end !!!");
Thread.sleep(1000);
}
System.out.println("st = "+st+",count = "+count);
}catch (InterruptedException e){
e.printStackTrace();
}
}
public static void main(String[] args) {
//两个对象都要在线程的run方法中调用,所以用final修饰
final SecondThread st1 = new SecondThread();
final SecondThread st2 = new SecondThread();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
st1.printCount("a");
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
st2.printCount("b");
}
});
t1.start();
t2.start();
}
}
运行结果:
a ,set count end !!!
st = a,count = 100
b ,set count end !!!
st = b,count = 200
总结:synchronized关键字取得的锁都是对象锁,而不是把一段代码(方法)当作锁,所以代码中只加synchronized关键字时,两个不同的对象会同时去访问synchronized修饰的方法,即两个对象获得的是两个不同的锁,并且互不影响。而在前面加上static关键字后,获得的锁是类级别的锁,即两个对象可以获得相同的锁。
同步:synchronized。同步的概念就是共享。如果不是共享的资源,就没有必要进行同步。
异步:asynchronized。异步的概念就是独立,相互之间不受到任何制约。有点类似于ajax异步请求。
同步的目的是为了线程安全。对于线程安全,需要满足两个特性:
原子性(同步)和可见性
public class ThirdThread {
public synchronized void method1() {
try{
System.out.println(Thread.currentThread().getName());
Thread.sleep(2000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
public synchronized void method2() {
System.out.println(Thread.currentThread().getName());
}
public static void main(String[] args) {
final ThirdThread tt = new ThirdThread();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
tt.method1();
}
},"t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
tt.method2();
}
},"t2");
t1.start();
t2.start();
}
}
总结:两个线程取的是同一个对象,及tt的锁,当t1获得锁之后,t2会处于等待状态,即method1执行完成之后method2才执行。
脏读:对于对象的同步和异步的方法,在设计程序的时候,一定要考虑问题的整体,不然就会出现数据不一致的错误,很经典的错误就是脏读(dirtyread)
例子:
public class FourthThread {
private String userName = "name";
private String passWord = "pw";
public synchronized void setValue(String userName,String passWord){
this.userName = userName;
try{
Thread.sleep(2000);
}catch (InterruptedException e){
e.printStackTrace();
}
this.passWord = passWord;
System.out.println("setValue结果:userName = "+userName+",passWord = "+passWord);
}
public synchronized void getValue(){
System.out.println("getValue结果:userName = "+this.userName+",passWord = "+this.passWord);
}
public static void main(String[] args) throws InterruptedException {
final FourthThread ft = new FourthThread();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
ft.setValue("username2","password2");
}
});
t1.start();
Thread.sleep(1000);
ft.getValue();
}
}
运行结果:
setValue结果:userName = username2,passWord = password2
getValue结果:userName = username2,passWord = password2
总结:在对一个对象的方法加锁的时候,需要考虑业务的整体性,即为setValue/getValue方法同时加锁synchronized关键字,保证业务(service)的原子性,不然会出现业务错误(也从侧面保证业务的一致性)。
synchronized锁重入:关键字synchronized拥有锁重入的功能,也就是在使用synchronized时,当一个线程得到了一个对象的锁后,再次请求此对象时是可以再次得到该对象的锁。
例子:
public class FifthThread {
public synchronized void method1(){
System.out.println("method1......");
method2();
}
public synchronized void method2(){
System.out.println("method2......");
method3();
}
public synchronized void method3(){
System.out.println("method3......");
}
public static void main(String[] args) throws InterruptedException {
final FifthThread ft = new FifthThread();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
ft.method1();
}
});
t1.start();
}
}
运行结果:
method1......
method2......
method3......
例子:
public class SixthThread {
static class Main{
public int i = 10;
public synchronized void operationSup(){
try {
i--;
System.out.println("Main print i = "+i);
Thread.sleep(100);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
static class Sub extends Main{
public synchronized void operationSub(){
try {
while(i>0){
i--;
System.out.println("Sub print i = "+i);
Thread.sleep(100);
this.operationSup();
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
Sub sub = new Sub();
sub.operationSub();
}
});
t1.start();
}
}
运行结果:
Sub print i = 9
Main print i = 8
Sub print i = 7
Main print i = 6
Sub print i = 5
Main print i = 4
Sub print i = 3
Main print i = 2
Sub print i = 1
Main print i = 0
出现异常,锁自动释放。
例子:
public class SeventhThread {
private int i=0;
public synchronized void operation(){
while (true){
try {
i++;
Thread.sleep(200);
System.out.println(Thread.currentThread().getName()+" , i = "+i);
if (i == 10){
Integer.parseInt("a");
}
}catch (Exception e){
e.printStackTrace();
System.out.println(" log info i = "+i);
}
}
}
public static void main(String[] args) {
final SeventhThread st = new SeventhThread();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
st.operation();
}
},"t1");
t1.start();
}
}
运行结果:
t1 , i = 1
t1 , i = 2
t1 , i = 3
t1 , i = 4
t1 , i = 5
t1 , i = 6
t1 , i = 7
t1 , i = 8
t1 , i = 9
t1 , i = 10
java.lang.NumberFormatException: For input string: "a"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Integer.parseInt(Integer.java:580)
at java.lang.Integer.parseInt(Integer.java:615)
log info i = 10
at com.SeventhThread.operation(SeventhThread.java:13)
at com.SeventhThread$1.run(SeventhThread.java:27)
at java.lang.Thread.run(Thread.java:745)
t1 , i = 11
t1 , i = 12
总结:对于web应用程序,异常释放锁的情况,如果不及时处理,很可能对你的应用程序业务逻辑产生严重的错误,比如你现在执行一个队列任务,很多对象都去在等待第一个对象正确执行完毕再去释放锁,但是第一个对象由于异常的出现,导致业务逻辑没有正常执行完毕,就释放了锁,那么可想而知后续的对象执行的都是错误的逻辑。所以这一点一定要引起注意,在编写代码的时候,一定要考虑周全。
synchronized代码块:
使用synchronized声明的方法在某些情况下是有弊端的,比如A线程调用同步的方法执行一个很长时间的任务,那么B线程就必须等待比较长的时间才能执行,这样的情况下可以使用synchronized代码块去优化代码执行时间,也就是通常所说的减小锁的粒度。