线程同步
一、线程安全问题
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
线程安全问题往往发生在多个线程调用同一方法或者操作同一变量,但是我们要知道其本质就是CPU对线程的随机调度,CPU无法保证一个线程执行完其逻辑才去调用另一个线程执行。
package threadtest; public class ThreadTest implements Runnable{ static int i = 0; public void incre() { i++; } @Override public void run() { for(int j=0;j<1000000;j++) { incre(); } } public static void main(String[] args) throws InterruptedException { ThreadTest tt = new ThreadTest(); Thread t1 = new Thread(tt); Thread t2 = new Thread(tt); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } }
由于 i++不是原子操作,先读取值,再加1 赋值,所以当在读取i值的时候线程切换了,导致两个线程读取的i相同,导致线程安全问题。
1363390 //结果小于2000000
二、线程同步
java多线程支持引入了同步监视器来解决线程同步问题,通过synchronized关键字,主要有同步方法和同步代码块
执行同步代码前必须先获得对同步监视器的锁定(任何时刻都只有一个线程可以获得同步监视器的锁定)
java5开始提供了更强大的同步机制,同步锁Lock
1、同步方法: synchronized修饰方法
package threadtest; public class ThreadTest implements Runnable{ static int i = 0; public synchronized void incre() { i++; } @Override public void run() { for(int j=0;j<1000000;j++) { incre(); } } public static void main(String[] args) throws InterruptedException { ThreadTest tt = new ThreadTest(); Thread t1 = new Thread(tt); Thread t2 = new Thread(tt); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } }
同步方法的同步监视器就是方法所属的对象本身
2000000 //结果正常
synchronized修饰静态方法
package threadtest; public class ThreadTest implements Runnable{ static int i = 0; /** * 同步静态方法的同步监视器是该类对应的class对象 */ public static synchronized void incre() { i++; } @Override public void run() { for(int j=0;j<1000000;j++) { incre(); } } public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(new ThreadTest()); Thread t2 = new Thread(new ThreadTest()); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } }
上面类中synchronized修饰的静态方法,同步监视器是该类对应的class对象,i是类属性,多个线程调用不同实例,i也是线程安全的。
2000000
2、同步代码块
除了使用关键字修饰实例方法和静态方法外,还可以使用同步代码块,在某些情况下,我们编写的方法体可能比较大,同时存在一些比较耗时的操作,而需要同步的代码又只有一小部分,如果直接对整个方法进行同步操作,可能会得不偿失,此时我们可以使用同步代码块的方式对需要同步的代码进行包裹,这样就无需对整个方法进行同步操作了
package threadtest; public class ThreadTest implements Runnable{ static int i = 0; /** * 同步代码块,synchronized(obj),obj就是同步监视器 */ public void incre() { synchronized(this) { i++; } } @Override public void run() { for(int j=0;j<1000000;j++) { incre(); } } public static void main(String[] args) throws InterruptedException { ThreadTest tt = new ThreadTest(); Thread t1 = new Thread(tt); Thread t2 = new Thread(tt); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } }
同步代码块修饰静态方法
package threadtest; public class ThreadTest implements Runnable{ static int i = 0; /** * 同步代码块,synchronized(obj),obj就是同步监视器 */ public static void incre() { synchronized(ThreadTest.class) { i++; } } @Override public void run() { for(int j=0;j<1000000;j++) { incre(); } } public static void main(String[] args) throws InterruptedException { //ThreadTest tt = new ThreadTest(); Thread t1 = new Thread(new ThreadTest()); Thread t2 = new Thread(new ThreadTest()); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } }
3、同步锁Lock
java5开始提供了通过显示定义同步锁对象来实现同步。
在实现线程安全中,比较常用的是ReentrantLock(可重入锁),它是Lock接口的实现类。
package threadtest; import java.util.concurrent.locks.ReentrantLock; public class ThreadTest implements Runnable{ private final ReentrantLock lock = new ReentrantLock(); static int i = 0; public void incre() { lock.lock();//加锁 try { i++; } finally { lock.unlock(); } } @Override public void run() { for(int j=0;j<1000000;j++) { incre(); } } public static void main(String[] args) throws InterruptedException { ThreadTest tt = new ThreadTest(); Thread t1 = new Thread(tt); Thread t2 = new Thread(tt); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } }
2000000//线程安全
4、死锁
当两个线程同时等待对方释放同步监视器就会发生死锁,java虚拟机没有检测,也没有采取措施来处理死锁情况,所以多线程编程时应该采取措施避免死锁出现。
一旦出现死锁,程序不会发生任何异常情况,也没有任何提示,只是所有线程处于阻塞状态,无法继续
下面程序就是发生死锁
package threadtest; /** * 一个简单的死锁例子,大概的思路:两个线程A和B,两把锁X和Y,现在A先拿到锁X,然后sleep()一段时间,我们知道sleep()是不会释放锁资源的。然后如果这段时间线程B拿到锁Y,也sleep()一段时间的话,那么等到两个线程都醒过来的话,那么将互相等待对方释放锁资源而僵持下去,陷入死锁。flag的作用就是让A和B获得不同的锁。 * @author rdb * */ public class ThreadTest implements Runnable{ Object o1 = new Object(); Object o2 = new Object(); private boolean flag = true ; @Override public void run() { if(flag) { flag = false; synchronized (o1) { System.out.println(Thread.currentThread().getName()); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (o2) { System.out.println("**************"); } } }else { flag = true; synchronized (o2) { System.out.println(Thread.currentThread().getName()); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (o1) { System.out.println("**************"); } } } } public static void main(String[] args) throws InterruptedException { ThreadTest tt = new ThreadTest(); Thread t1 = new Thread(tt); Thread t2 = new Thread(tt); t1.start(); t2.start(); } }