Java 多线程及线程间通信(Synchronized和Volatile关键字)
一、多线程创建
1. 继承Thread类,重写run()方法
1 public class MyThread extends Thread { 2 3 @Override 4 public void run() { 5 // TODO: 6 } 7 } 8 9 MyThread myThread = new MyThread(); 10 myThread.start();
2. 实现Runnable接口
1 public class MythreadBRunnable implements Runnable { 2 3 @Override 4 public void run() { 5 6 } 7 } 8 9 MythreadBRunnable runnable = new MythreadBRunnable(); 10 Thread thread = new Thread(runnable); 11 thread.start();
3. start()方法和run()方法区别
run()方法是线程具体执行的实现,run()执行完后,线程就结束了。
start()方法是开启线程,但是,要注意的是start()方法开启线程,并不代表立即执行,这个要看系统何时调用执行。开启线程代表线程进入就绪状态。
4. 两种启动线程区别
(1)继承Thread类,由于Java中类继承是单继承,不同于C++,在Java中为弥补这一缺点,通过实现Runnable接口来弥补这一缺点。用接口方法比Thread继承更灵活。
(2)当开启多个线程时,使用继承Thread类方式,就必须产生相应多个Thread线程,而使用Runnable接口就只实现一个Runnable,通过实例Runnable接口创建对象,再将实例传递给Thread,并且调用start()开启线程。这样,做比使用继承Thread类要更方便。
二、多线程通信
(一)synchronized关键字
1. synchronized关键字
(1)synchronized对象锁
public class SynchronizedObject { synchronized public void methodA() { } public void methodB() { synchronized (this) { // TODO: } } }
synchronized关键字修饰methodA()方法,methodA()方法是一个实例方法,而调用methodA()方法,肯定要创建一个对象,通过对象调用方法(syncObj.methodA())。这个对象就是SynchronizedObject类的对象,所以,就是synchronized关键字就是将这个对象加锁了。
所以,synchronized关键字修饰methodA()与methodB()中,synchronized(this){}为this代表的对象加锁,所以,这两种方式锁住的是同一个对象,两种方式的效果是一样的。这就是synchronized对象锁。
2. 实践
使用synchronized关键字修饰类方法和成员方法:
1 public class CustomSyncThread extends Object { 2 3 private static final String TAG = "CustomLock"; 4 5 public CustomSyncThread() { 6 7 } 8 9 synchronized public static void methodA() { 10 try { 11 Log.d(TAG, "MethodA 进入"); 12 Thread.sleep(2000); 13 Log.d(TAG, "MethodA 离开"); 14 } catch (InterruptedException e) { 15 e.printStackTrace(); 16 } 17 } 18 19 synchronized public static void methodB() { 20 try { 21 Log.d(TAG, "MethodB 进入"); 22 Thread.sleep(2000); 23 Log.d(TAG, "methodB 离开"); 24 } catch (InterruptedException e) { 25 e.printStackTrace(); 26 } 27 } 28 29 synchronized public void methodC() { 30 try { 31 Log.d(TAG, "methodC 进入"); 32 Thread.sleep(2000); 33 Log.d(TAG, "methodC 离开"); 34 } catch (InterruptedException e) { 35 e.printStackTrace(); 36 } 37 } 38 39 synchronized public void methodD() { 40 try { 41 Log.d(TAG, "methodD 进入"); 42 Thread.sleep(2000); 43 Log.d(TAG, "methodD 离开"); 44 } catch (InterruptedException e) { 45 e.printStackTrace(); 46 } 47 } 48 }
多线程调用
1. 多线程调用类方法
1 new Thread(new Runnable() { 2 @Override 3 public void run() { 4 CustomSyncThread.methodA(); 5 } 6 }).start(); 7 8 new Thread(new Runnable() { 9 @Override 10 public void run() { 11 CustomSyncThread.methodB(); 12 } 13 }).start();
输出:
1 04-25 14:24:28.210 3937-3978/com.naray.demo D/CustomLock: methodB 进入 2 04-25 14:24:30.214 3937-3978/com.naray.demo D/CustomLock: methodB 离开 3 04-25 14:24:30.215 3937-3979/com.naray.demo D/CustomLock: methodA 进入 4 04-25 14:24:32.216 3937-3979/com.naray.demo D/CustomLock: methodA 离开
从输出可以看出synchronized是为类方法添加的是类锁,所以,同一时刻只有一条线程可以方法该类。
2. 多线程调用对象方法
1 final CustomSyncThread syncThread = new CustomSyncThread(); 2 new Thread(new Runnable() { 3 @Override 4 public void run() { 5 syncThread.methodC(); 6 } 7 }).start(); 8 9 new Thread(new Runnable() { 10 @Override 11 public void run() { 12 syncThread.methodD(); 13 } 14 }).start();
输出:
1 04-25 14:35:31.898 4374-4416/com.naray.demo D/CustomLock: methodD 进入 2 04-25 14:35:33.899 4374-4416/com.naray.demo D/CustomLock: methodD 离开 3 04-25 14:35:33.900 4374-4415/com.naray.demo D/CustomLock: methodC 进入 4 04-25 14:35:35.902 4374-4415/com.naray.demo D/CustomLock: methodC 离开
从输出可以看出synchronized为对象添加的是对象锁。
3. 多线程调用对象方法和类方法
1 final CustomSyncThread syncThread = new CustomSyncThread(); 2 new Thread(new Runnable() { 3 @Override 4 public void run() { 5 CustomSyncThread.methodA(); 6 } 7 }).start(); 8 9 new Thread(new Runnable() { 10 @Override 11 public void run() { 12 syncThread.methodC(); 13 } 14 }).start();
输出结果:
1 04-25 14:38:16.261 4544-4589/com.naray.demo D/CustomLock: methodC 进入 2 04-25 14:38:16.262 4544-4588/com.naray.demo D/CustomLock: MethodA 进入 3 04-25 14:38:18.263 4544-4589/com.naray.demo D/CustomLock: methodC 离开 4 04-25 14:38:18.263 4544-4588/com.naray.demo D/CustomLock: MethodA 离开
从上面结果可以看出,类锁和对象锁是互不干扰的。
4. 多线程调用对象方法,在对象方法中调用其它的对象方法,在同一个对象锁中会有什么效果?
CustomSyncThread类:
1 public class CustomSyncThread extends Object { 2 3 private static final String TAG = "CustomLock"; 4 5 public CustomSyncThread() { 6 7 } 8 9 synchronized public void methodC() { 10 try { 11 Log.d(TAG, "methodC 进入"); 12 this.methodD(); 13 Thread.sleep(2000); 14 Log.d(TAG, "methodC 离开"); 15 } catch (InterruptedException e) { 16 e.printStackTrace(); 17 } 18 } 19 20 synchronized public void methodD() { 21 try { 22 Log.d(TAG, "methodD 进入"); 23 Thread.sleep(2000); 24 Log.d(TAG, "methodD 离开"); 25 } catch (InterruptedException e) { 26 e.printStackTrace(); 27 } 28 } 29 }
输出结果:
1 04-25 14:53:48.876 4833-4877/com.naray.demo D/CustomLock: methodC 进入 2 04-25 14:53:48.876 4833-4877/com.naray.demo D/CustomLock: methodD 进入 3 04-25 14:53:50.878 4833-4877/com.naray.demo D/CustomLock: methodD 离开 4 04-25 14:53:52.880 4833-4877/com.naray.demo D/CustomLock: methodC 离开
从上面的结果可以看出,同一个对象锁下对象的成员方法调用另一个成员方法,是允许调用的。
(二)线程间通信
每个线程都有自己的工作内存,在线程执行过程中用到某个变量时,会将主内存的变量copy到线程的工作内存,在线程完成读取、修改、赋值操作后将变量写回到主内存。因此多线程同时用到同一个变量时,无法保证变量值的一致性,这就涉及到多线程的特性:原子性、有序性、可见性。
- volatile 关键字:使用volatile关键的变量在多线程用到此变量时,是不允许将此变量从主内存中copy到线程的工作内存中,多线程修改或者使用的变量都是主内存中的变量。当其它线程修改此变量后其它线程会同时收到变量被修改的值。从变量内存地址上说就是多线程操作的变量的内存地址是同一个。
- synchronized关键字:synchronized有一个监听器Monitior,多线程操作同一个对象时,对象内存使用synchronized关键字修饰,那么就会和同一个Monitior监听器,在synchronized代码块中MonitiorEnter和MonitiorExit标识。一个线程操作中,没有退出前,其它线程是不能进入操作的。
- volatile和synchronized区别:volatile只能用于主内存中,并且volatile只能用于变量。而synchronized可以修饰方法与代码块能用于主内存和副内存中。
(三)Java Lock类
是通过Java实现的锁机制,synchronized是托管于JVM虚拟机执行。synchronized性能比lock低。synchronized是CPU悲观锁机制,lock是CPU乐观机制。