Java的多线程和线程同步
多线程主要设计两点:多线程如何正确使用,如何保证线程安全。
进程:操作系统上的一块独立的运行区域,有自己的数据管理,数据不共享。每隔进程内部是自己完整的程序逻辑,不能的程序之间本来就不应该共享资源;而同一个进程的多个不同线程,需要都能操作到这个进程的资源,程序才能正常运行。
线程: 运行在进程中,线程之间可以共享资源,所以线程安全的问题是重中之重。
操作系统线程和CPU线程是两回事,一般程序的线程是操作系统的线程。
Android APP,主线程不会结束,反复的刷新界面,AndroidUI线程为什么死循环不会卡界面的问题。UI线程是一个大循环,每一圈都是一次界面刷新操作,而不是对某一次界面刷新过程进行内部的死循环,所以不会卡死界面。
线程创建的几种方式
重写run方法 (不推荐)
Thread thread = new Thread() {@Overridepublic void run() {//重写run方法System.out.println("Thread start!");}};thread.start();
runnable (不推荐)
Runnable runnable = new Runnable() {@Overridepublic void run() {System.out.println("Thread with Runnable started!");}};//赋值给target,在run中执行,runnable可以进行重用Thread thread = new Thread(runnable);thread.start();
ThreadFactory 工厂方法创建线程,统一线程的初始化
ThreadFactory factory = new ThreadFactory() {//AtomicIntegerAtomicInteger count = new AtomicInteger(0);@Overridepublic Thread newThread(Runnable r) {//统一线程的初始化操作return new Thread(r, "Thread-" + count.incrementAndGet());}};//runnable的复用Runnable runnable = new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + "started!");}};//通过工厂模式 创建线程,统一线程的初始化操作Thread thread = factory.newThread(runnable);thread.start();Thread thread1 = factory.newThread(runnable);thread1.start();
Executes 线程池,开发中常用的方式
一个线程池的线程数定义为动态值:CPU核心数的1倍,CPU核心越多线程池越大,这样可以在高性能的手机上有更大的线程池,相当于按手机的能力来分配线程池的大小。
Runnable runnable = new Runnable() {@Overridepublic void run() {System.out.println("Thread with Runnable started!");}};//newCachedThreadPool 线程无限大,闲置60S回收Executor executor = Executors.newCachedThreadPool();executor.execute(runnable);executor.execute(runnable);executor.execute(runnable);
- 自带的几种线程池的创建方式
创建线程池的参数说明:ThreadPoolExecutor(5,20,60,S) 默认线程数5个,最大线程数是20个,当线程执行完毕,60S后没有被使用则进行回收。 shutdown: 不会终止当前排队的任务,但是会阻止后面的任务进来,当前的任务队列执行完毕之前,后面都不会有任务排队进来。 shutdownNew: 结束所有的线程,调用线程的interrupt方法。
Executors.newCachedThreadPool(); //线程无限大,闲置60s回收Executors.newSingleThreadExecutor();//只有1个线程,线程运行完毕,立即回收Executors.newFixedThreadPool(10);//固定线程数,使用完立即回收,用于集中处理多个瞬时爆发的任务//例如如下代码:处理20个bitmap// List<Bitmap> bitma = ...// ExecutorService executor1 = Executors.newFixedThreadPool(20);// for (Bitmap bitmap: bimaps) {// executor1.execute(processImagesRunnable);// }// //只允许现在的任务队列执行,不允许在添加排队任务// executor1.shutdown();// processImagesRunnable // processImageExecutors.newScheduledThreadPool(10);//定时的线程池
自定义线程池:
//自定义线程池ExecutorService myExecutor = new ThreadPoolExecutor(5, 100,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
- callable 可以有返回值
Future.get()会阻塞主线程,等待后台任务执行完毕,对于submit不会阻塞主线程。
Callable<String> callable = new Callable<String>() {@Overridepublic String call() throws Exception {Thread.sleep(1500);return "Done";}};ExecutorService executor = Executors.newCachedThreadPool();Future<String> future = executor.submit(callable);try {System.out.println(future.get());} catch (ExecutionException e) {e.printStackTrace();} catch (InterruptedException e) {e.printStackTrace();}
诶,我们使用线程的目的就是不阻塞主线程,但是这里阻塞了主线程,Java API层只能做到这一步,我们可以进行检测future.isDone子线程是否执行完毕了
while (true) {System.out.println("其他指令,不会卡主线程");if (future.isDone()) {try {System.out.println("start");System.out.println(future.get());System.out.println("end");} catch (ExecutionException e) {e.printStackTrace();} catch (InterruptedException e) {e.printStackTrace();}break;}}
线程安全
线程安全的本质:某些资源被多个线程同时访问,导致资源在一个线程对它写到一半的途中被其他线程写或读,或在读到一半的途中被其他线程写,导致出现数据错误。
volatile
volatile 关键字可以保证数据的可见性,当一个线程进行写操作时,写完会立即同步数据;当一个线程读取数据时,会先进行同步数据再读取
如下代码:通过isRunning来控制子线程的结束,在主线程修改isRunning,然后来看子线程中的while循环是否结束?
public class Synchronized1Demo implements TestDemo {private boolean isRunning = true;private void stop() {isRunning = false;}@Overridepublic void runTest() {new Thread() {@Overridepublic void run() {while (isRunning) {//=false 子线程就会结束}}}.start();try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}stop();}}
当执行了stop()方法,修改了isRunning = false,但是子线程并没有结束,还在执行,Why??
这就是典型的线程安全的问题,首先来看Java中的线程模型:主内存中是存储了资源数据,而每个线程有自己的工作内存,当要用到主内存中的资源,需要将资源拷贝到自己的工作内存中,当资源发生改变就需要同步到主内存中去。
上述的代码中isRunning,主线程中的工作内存对isRunning进行了修改,但是并没有同步到主内存中,导致子线程工作内存isRunning一直是true. 通过volatile关键字可以将isRunning的变化及时的同步到主内存中,同时子线程读取数据也会从主内存中读取。voltaile保证了变量的可见性,当一个变量在一个线程发生变化,会同步到主内存,另一个线程读取时会立即进行同步,首先从主内存中读取数据。
public class Synchronized1Demo implements TestDemo {private volatile boolean isRunning = true;private void stop() {isRunning = false;}@Overridepublic void runTest() {new Thread() {@Overridepublic void run() {while (isRunning) {//=false 子线程就会结束}}}.start();try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}stop();}}
synchronized
synchronized 保证了原子性,一组操作是不可分割的原子,不能被打断,同时也保证了可见性,在加锁前会清空工作内存变量值,重新从主内存中读取,在释放锁之前会将变量的值刷新回主内存中。
如下代码,来了解synchronized的本质:根据上面所学习的volatile关键字,保证变量值的在多线程下的同步性,那么下面代码看似没有问题,x的值最终应该为:2_000_000
private volatile int x = 0;private void count() {x++;}@Overridepublic void runTest() {new Thread() {@Overridepublic void run() {for (int i = 0; i < 1_000_000; i++) {count();}System.out.println("final x from 1:" + x);}}.start();new Thread() {@Overridepublic void run() {for (int i = 0; i < 1_000_000; i++) {count();}System.out.println("final x from 2:" + x);}}.start();}
但实际结果:这是为什么呢?volatile 不是保证了x的值具有同步性吗?
其实x++ 是分两步进行编译:int temp = x+ 1 x = temp 不是一个原子操作 而volatile只是保证了x具有同步性,但是temp不具备同步性了,如下图模拟执行的流程:
线程之间是共享主内存中的数据的,假如线程1,一直执行执行到temp = 10了这时候发生线程切换,注意x = temp没有执行,虽然x具有了同步性,但是x=temp没有执行,那么x并且没有同步到主内存中,x 的还是9,那么这时候线程2开始执行 temp = 9+1 = 10 x= 10,一直执行到x=15,这是切换到线程1,线程1继续上一次的操作,然而temp是在线程1工作内存中存储了值为10,那么下一步为x赋值 x=10,由于volatile的同步性,将x同步到了主内存中,这是在切换线程2拿到的x就是10了,这也是为什么最终打印的结果小于预期的值。

这时候就需要有synchronized,而synchronized就是为了解决volatile不能处理的问题,volatile只能保证变量的值具有同步性,不能保证一组操作具有同步性,synchronized就实现了一组操作具有原子性,不可分割的。synchronized 会保证在每次执行时都会从主内存同步数据,执行完毕后将数据刷新回主内存中。
//同步性问题 多个线程各自的内存拷贝//volatile 在这里不会起作用,X++是两步操作 int temp = x + 1 x = temp 导致不是一个原子操作(不可拆的操作)private int x = 0;//synchronized 保证count方法具有原子性,一个线程必须执行完count方法,下一个线程才能执行count//内部的变量 都会具有同步性 synchronized 保证了 同步性和原子性private synchronized void count() {x++;}@Overridepublic void runTest() {new Thread() {@Overridepublic void run() {for (int i = 0; i < 1_000_000; i++) {count();}System.out.println("final x from 1:" + x);}}.start();new Thread() {@Overridepublic void run() {for (int i = 0; i < 1_000_000; i++) {count();}System.out.println("final x from 2:" + x);}}.start();}
这时候在执行代码就能拿到想要的结果了:
如下图来看一下synchronized的执行流程:
- 首先被synchronized包裹的一组操作只允许一个线程被访问,会上锁,其他线程要访问需要进行等待上一个线程执行完毕才可以访问
- 线程1 获取到synchronized锁,会先从主内存中同步数据,然后执行操作,当线程执行结束,会将数据刷新到主内存中,最后释放锁
- 然后其他的线程会公平竞争锁,拿到锁的线程执行,如此反复

synchronized 的使用是不是所有的操作都在方法上加上synchronized关键字呢?这要做会有什么问题吗?
如下代码:看起来没有任何问题,一个线程访问count2(),一个线程访问setName2(),预期的执行结果就是:线程1输出,线程2输出,但是实际结果线程1执行完count2才会执行setName2方法,而不是线程1和线程2各执行自己的方法???这是为什么呢?完全是两个线程也没有共享一样的资源
private int x = 0;private int y = 0;private String name;private synchronized void count2(int newValue) {x = newValue;y = newValue;System.out.println("newValue = " + newValue);try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}}private synchronized void setName2(String name){System.out.println("name = " + name);}@Overridepublic void runTest() {new Thread() {@Overridepublic void run() {count2(10);}}.start();new Thread() {@Overridepublic void run() {setName2("jakeprim");}}.start();}
这其实是synchronized的一个特性:synchronized 具有互斥访问的特性,会为方法提供一个监视器monitor,上述两个方法count2和setName2使用的是同一个监视器,所以当访问count2的时候,就不能访问setName2了,这个monitor的作用在于监视访问的线程,当一个线程访问了,这时候monitor就是设置一个标记,其他线程就不允许访问了。
这种设定的意义:在于开发者可以指定设置的monitor,哪些操作共享一个monitor,哪些操作是其他的monitor进行监视, 这些操作是互不影响的
如下代码,synchronized(this) == synchronized void count2() 和作用在方法上是等价的
synchronized (this) {//this 指定的monitor 他和在方法上的设置是一样的,默认是共享了一个monitorx = newvalue;y = newvalue;}
为setName2指定一个monitor,不使用默认的monitor
private static final Object object2 = new Object();private final Object monitor1 = new Object();private void setName(String newName) {synchronized (monitor1) {//指定monitorname = newName;}
例如如下代码,使用一个monitor的情况:当一个线程访问count方法,那么肯定不希望其他的线程访问minus去改变x和y的值。
/*** 当一个线程访问count,那么是不希望另一个线程访问minus的,这是为什么monitor要管理多个方法** @param delta*/private synchronized void minus(int delta) {x -= delta;y -= delta;}private void count(int newvalue) {synchronized (this) {//this 指定的monitor 他和在方法上的设置是一样的,默认是共享了一个monitorx = newvalue;y = newvalue;}}
这时候的monitor的分布情况如下图:精准的控制monitor
synchronized 经常会被问到死锁的问题,其实只有多重锁才会造成死锁的问题,如下代码死锁的情况:
monitor1 和 monitor2 两个监视器,假如线程1执行了count方法,持有了monitor2的锁,这时候线程2访问了setName方法,持有了monitor1的锁,线程1想要执行完毕,就需要拿到monitor1的锁执行,线程2要想执行完毕就要拿到monitor2的锁执行,两个线程都无法释放锁造成死锁的问题
private void count(int newvalue) {synchronized (monitor2) {//this 指定的monitor 他和在方法上的设置是一样的,默认是共享了一个monitorx = newvalue;y = newvalue;//死锁的问题 当setName 被锁定,这里就不能执行 只有多重锁才会出现死锁synchronized (monitor1){name = "xxx";}}}private void setName(String newName) {synchronized (monitor1) {//指定monitorname = newName;//死锁的问题synchronized (monitor2){x = 1;y = 2;}}}

synchronized 整体的执行流程如下图所示:
常用的单例模式:
public class SingleMan {private static volatile SingleMan sInstance;private SingleMan() {}static SingleMan getInstance() {if (sInstance == null) {synchronized (SingleMan.class) { //等价于 static synchronizedif (sInstance == null) {sInstance = new SingleMan();//volatile 保证构造方法 初始化完毕}}}return sInstance;}}
Lock
public class ReadWriteLockDemo implements TestDemo {private int x = 0;ReentrantLock lock = new ReentrantLock();//将锁分成 读锁和写锁 更精细的控制 进行写操作的时候,不能进行读。在没有写操作的时候,就可以读//这样就不会因为写操作的冲突,或者写了一半的时候进行了读操作,或者写了一半的时候进行写操作等等这些问题//线程安全:主要是共享资源的安全问题ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();//读锁Lock readLock = reentrantReadWriteLock.readLock();//写锁Lock writeLock = reentrantReadWriteLock.writeLock();private void count() {// lock.lock();//上锁writeLock.lock();try {x++;//... 如果中间抛异常 就无法解锁了} finally {// lock.unlock();//解锁writeLock.unlock();}}@Overridepublic void runTest() {readLock.lock();try {System.out.println(x);} finally {readLock.unlock();}}}
正确停止线程
Thread.stop() 是非常危险的,因为它会直接杀死线程,有几率让线程在某些工作运行到一半的时候被结束,从而系统被强制停留在某种中间状态,进而导致问题。 Thread.interrupt() 来结束线程是安全的,因为它并不会直接杀死线程,而是告诉线程【外界希望你停止】,具体的结束工作由线程自己来完成,所以更加安全。
public class ThreadInteractionDemo implements TestDemo {@Overridepublic void runTest() {Thread thread = new Thread() {@Overridepublic void run() {try {Thread.sleep(10000);//等待状态的线程终止} catch (InterruptedException e) {//不会改中断的标记的 还是false//擦屁股return;//在等待的过程中 中断了线程 会直接抛出一个异常 直接打断就可以 在等待过程中不会改资源的}// for (int i = 0; i < 1_000_000; i++) {// //Thread.interrupted()// 会把标记重置为false 会改标记的状态的// isInterrupted 不会改状态// if (isInterrupted()) {//如果中断状态被标记为true了 则结束线程 在哪里中断 需要根据业务来判断// return;// }// System.out.println("number:" + i);// }}};thread.start();try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}//thread.stop();//停止线程 但是stop被弃用了,会导致线程不可预期的错误,stop强制中断线程// 假如线程正在修改两个变量,这是一个变量修改了 另一个变量被中断了a=100 b=50 导致中间状态 程序的状态是不可控的// 使用外力终止线程是非常危险的,本身是一个不靠谱的机制//interrupt 是打断,需要自己去程序中断线程,而Thread不会打断线程,只是把线程标记为中断状态,需要自己去中断//不是强制的,这样就能避免了stop导致的不可预期的错误thread.interrupt();}}
线程间通信
Object.wait() 经常需要外面包着while循环,因为线程在等待过程中被叫醒的原因是不确定的,所以醒来后需要重新判断条件是否达成。
public class WaitDemo implements TestDemo {private String sharedString;private Object monitor = new Object();private void initString() {synchronized (monitor) {sharedString = "jakeprim";monitor.notify();//通知 等待区的出来一个,重新进行公平竞争锁// notifyAll();//通知 等待区的全部拿出来}}private void printString() {// if (sharedString != null) {// System.out.println("String:" + sharedString);// }//锁住了 initString 拿不到锁 永远在等待了// while (sharedString == null) {// }synchronized (monitor) {while (sharedString == null) {try {//wait 线程进入等待区 排队拿锁,会释放锁monitor.wait();} catch (InterruptedException e) {}}System.out.println("String:" + sharedString);}}@Overridepublic void runTest() {final Thread thread2 = new Thread() {@Overridepublic void run() {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}//初始化之后 通知另一个线程initString();}};thread2.start();final Thread thread1 = new Thread() {@Overridepublic void run() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}//等待2号线程执行完毕// try {// thread2.join();// } catch (InterruptedException e) {// e.printStackTrace();// }// yield(); 暂时让出 和自己同优先级的线程printString();}};thread1.start();//join 运行在主线程前面,后面的不在执行了try {//让线程变成串行的关系thread1.join();//等待状态 interrupt 中断} catch (InterruptedException e) {e.printStackTrace();}System.out.println("haha");}}
Android 的多线程机制
Handler 只能往HandlerThread插任务,而HandlerThread其实是无线循环,因此可以在每一环都做一次任务的检查与执行 AsyncTask 内部持有线程,而运行中的线程属于GC Root 可能会导致内存泄漏
public class CustomThread extends Thread {Looper looper = new Looper();@Overridepublic void run() {//注意不要在run 设置synchronized 否则锁处于一直锁定的状态//永不结束的线程looper.loop();}class Looper {//要执行的任务private Runnable task;//线程安全的AtomicBoolean 保证同步性private final AtomicBoolean quit = new AtomicBoolean(false);synchronized void setTask(Runnable task) {this.task = task;}void quit() {quit.set(true);}void loop() {while (!quit.get()) {synchronized (this) {if (task != null) {//执行任务task.run();//任务执行完毕清空task = null;}}}}}}
Service 后台线程的持续的空间 和 IntentService 后台任务
