线程同步
1.synchronized
下面代码,启动了2个线程,对同一个实例syntest的age变量进行自增操作
package com.ljj.study; public class SynTest { private int age = 0; public void add() { age++; } public void remove() { age--; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } // private Object o = new Object(); private static class SynAddThread implements Runnable { private SynTest synTest; private String name; public SynAddThread(SynTest synTest, String name) { this.synTest = synTest; this.name = name; } public void run() { for (int i = 0; i < 10000; i++) { synTest.add(); } System.out.println(name + " addThread age " + synTest.getAge()); } } public static void main(String[] args) throws InterruptedException { SynTest syntest = new SynTest(); SynAddThread sat = new SynAddThread(syntest, "t0"); Thread t0 = new Thread(sat); SynAddThread sat1 = new SynAddThread(syntest, "t1"); Thread t1 = new Thread(sat1); t0.start(); t1.start(); } }
t1 addThread age 148130 t0 addThread age 156546
多次执行,每一次的结果都不同,而且不符合预期结果。原因和cpu时间片轮转机制,线程工作内存,主内存有关吧,准确原因还不知道。
为了获得预期结果,我想着给add方法加上synchronized修饰符 多次执行 结果如下
t0 addThread age 18741
t1 addThread age 20000
t1的值符合预期,t0值不符合。原因是synchronized方法一旦被某个线程执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,所以t0线程执行了一次 synTest.add()返回后,就释放了该锁,然后t0线程阻塞在那,2个线程重新争夺该锁。
所以可以用一个20000大小的数组描述执行顺序[t0,t0,t1,......],10000个t0和10000个t1往这个数组里填充,每执行一次毫无疑问age值+1,age的最终结果就是20000,由于某个线程比较强势提前执行完就先打印结果了,导致最先打印的值大于10000。
synchronized锁又叫可重入锁,意思是说同一线程外层函数获得锁之后 ,内层函数仍然持有锁,这也就是synchronized修饰的方法的锁被某个线程持有后,其他线程不能访问此对象的所有synchronized修饰的方法的原因。
public class SynTest { private int age = 0; public synchronized void add(int i) { age = age + i; System.out.println(Thread.currentThread().getName() + "=========" + age); if (age < 100) { add(i); } } public int getAge() { return age; } public void setAge(int age) { this.age = age; } private Object o = new Object(); private static class SynAddThread implements Runnable { private SynTest synTest; private String name; public SynAddThread(SynTest synTest, String name) { this.synTest = synTest; this.name = name; } public void run() { synTest.add(1); System.out.println(name + " addThread age " + synTest.getAge()); } } public static void main(String[] args) throws InterruptedException { SynTest syntest = new SynTest(); SynAddThread sat = new SynAddThread(syntest, "t0"); Thread t0 = new Thread(sat); SynAddThread sat1 = new SynAddThread(syntest, "t1"); Thread t1 = new Thread(sat1); t0.start(); t1.start(); } }
打印的结果是只有一个线程在执行0-100的自增,这也就是说这个是可重入锁
看来synchronized方法还是不能完全符合预期,我想着用同步代码块的方法 修改代码如下
package com.ljj.study; public class SynTest { private int age = 0; public synchronized void add() { age++; } public void remove() { age--; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } private Object o = new Object(); private static class SynAddThread implements Runnable { private SynTest synTest; private String name; public SynAddThread(SynTest synTest, String name) { this.synTest = synTest; this.name = name; } public void run() { synchronized (synTest.o) { for (int i = 0; i < 10000; i++) { synTest.add(); } System.out.println(name + " addThread age " + synTest.getAge()); } } } public static void main(String[] args) throws InterruptedException { SynTest syntest = new SynTest(); SynAddThread sat = new SynAddThread(syntest, "t0"); Thread t0 = new Thread(sat); SynAddThread sat1 = new SynAddThread(syntest, "t1"); Thread t1 = new Thread(sat1); t0.start(); t1.start(); } }
执行结果如下,符合预期。2个线程去争夺synTest.o这个锁,谁争取到就执行后面的代码块
总结:synchronized方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁。不是说某线程获得了该锁,就一直持有直到线程结束。
t0 addThread age 10000
t1 addThread age 20000
2.volatile--轻量级同步
不能确保原子性,能确保可见性。
所谓的确保可见性,就是说每次操作该变量时都会把主存中的该变量刷新到线程内存中。
不能确保原子性就是说,在做原子性操作时,假如主内存中的该值改变了不能及时的应用于此次操作中。比如volatile int a ; a=a+1这个操作会先从主内存中获取最新的值(可见性) 然后自增,然而在自增过程中a值在主内存中改变了,a=a+1不能同步此值(不能确保原子性)
public class VolatileUnsafe { private static class VolatileVar implements Runnable { private volatile int a = 0; @Override public void run() { String threadName = Thread.currentThread().getName(); a = a+1; System.out.println(threadName+":======"+a); SleepTools.ms(10); a = a+1; System.out.println(threadName+":======"+a); } } public static void main(String[] args) { VolatileVar v = new VolatileVar(); Thread t1 = new Thread(v); Thread t2 = new Thread(v); Thread t3 = new Thread(v); Thread t4 = new Thread(v); t1.start(); t2.start(); t3.start(); t4.start(); } }
上述代码每次执行的结果都是千奇百怪的,所以volatile不能保证线程同步。
但是在只有一个线程写,多个线程读的场景下,volatile就可以使用。
3.Thredlocal
public class UseThreadLocal { //可以理解为 一个map,类型 Map<Thread,Integer> static ThreadLocal<Integer> threadLaocl = new ThreadLocal<Integer>(){ @Override protected Integer initialValue() { return 1; } }; /** * 运行3个线程 */ public void StartThreadArray(){ Thread[] runs = new Thread[3]; for(int i=0;i<runs.length;i++){ runs[i]=new Thread(new TestThread(i)); } for(int i=0;i<runs.length;i++){ runs[i].start(); } } /** *类说明:测试线程,线程的工作是将ThreadLocal变量的值变化,并写回,看看线程之间是否会互相影响 */ public static class TestThread implements Runnable{ int id; public TestThread(int id){ this.id = id; } public void run() { System.out.println(Thread.currentThread().getName()+":start"); Integer s = threadLaocl.get();//获得变量的值 s = s+id; threadLaocl.set(s); System.out.println(Thread.currentThread().getName()+":" +threadLaocl.get()); //threadLaocl.remove(); } } public static void main(String[] args){ UseThreadLocal test = new UseThreadLocal(); test.StartThreadArray(); } }
Thread-0:start Thread-2:start Thread-1:start Thread-2:3 Thread-1:2 Thread-0:1
ThreadLocal可以理解为 一个map,类型 Map<Thread,Integer>,相当于每个线程都有一个该变量的副本,每个线程都只操作自己的副本,但是假如该变量所占空间很大,会很浪费空间。