一、多线程
多线程就是指一个进程中同时有多个线程正在执行。
二、多线程优缺点:
优点:
1、提高程序运行效率,如同时上传多个图片;
2、耗时操作放子线程执行,提高主线程执行效率,无需等待。
缺点:
1、线程间切换,如果有大量的线程,会影响性能;
2、更多的线程需要更多的内存空间;
3、多线程操作可能会出现线程安全或者死锁等问题。
三、多线程同步方法:
线程的同步的意义在于线程安全,也就是说有多个线程并发访问同一个对象,而线程调度的不确定性可能带来潜在的安全问题。
1、同步函数
对于同步函数,其同步监视器是默认实例this
。
private synchronized void count() {
if (count > 0) {
Log.e(TAG, Thread.currentThread().getName() + "--->" + count--);
} else {
isRunning = false;
}
}
2、同步代码块
private void count() {
synchronized (this) {
if (count > 0) {
Log.e(TAG, Thread.currentThread().getName() + "--->" + count--);
} else {
isRunning = false;
}
}
}
3、使用特殊域变量(volatile)实现线程同步
private volatile int count = 1000;
volatile
关键字为域变量的访问提供了一种免锁机制,- 使用
volatile
修饰域相当于告诉虚拟机该域可能会被其他线程更新, - 因此每次使用该域就要重新计算,而不是使用寄存器中的值
volatile
不会提供任何原子操作,它也不能用来修饰final
类型的变量
4、使用重入锁实现线程同步
- ReentrantLock() : 创建一个ReentrantLock实例
- lock() : 获得锁
- unlock() : 释放锁
private void count() {
lock.lock();
if (count > 0) {
Log.e(TAG, Thread.currentThread().getName() + "--->" + count--);
} else {
isRunning = false;
}
lock.unlock();
}
5、线程锁释放的几种情况
- 当前线程的同步方法,同步代码块执行结束或者在执行中遇到
break
,return
等终止了代码块的执行; - 同步代码块或者方法中出现未处理的
Error
或者Exception
,导致异常结束; - 当前线程执行同步代码块或者同步方法时,程序中执行了同步监视器的
wait()
方法。
四、死锁:
指多线程因竞争资源而造成的一种僵局(互相等待),若无外力作用这些进程都将无法向前推进。
产生死锁的必要条件:
(1) 互斥条件:一个资源每次只能被一个进程使用。
(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
处理死锁的方法:
-
预防死锁:这是一种较简单和直观的事先预防的方法。方法是通过设置某些限制条件,去破坏产生死锁的四个必要条件中的一个或者几个,来预防发生死锁。预防死锁是一种较易实现的方法,已被广泛使用。但是由于所施加的限制条件往往太严格,可能会导致系统资源利用率和系统吞吐量降低。
-
避免死锁:该方法同样是属于事先预防的策略,但它并不须事先采取各种限制措施去破坏产生死锁的的四个必要条件,而是在资源的动态分配过程中,用某种方法去防止系统进入不安全状态,从而避免发生死锁。
-
检测死锁:这种方法并不须事先采取任何限制性措施,也不必检查系统是否已经进入不安全区,此方法允许系统在运行过程中发生死锁。但可通过系统所设置的检测机构,及时地检测出死锁的发生,并精确地确定与死锁有关的进程和资源,然后采取适当措施,从系统中将已发生的死锁清除掉。
-
解除死锁:这是与检测死锁相配套的一种措施。当检测到系统中已发生死锁时,须将进程从死锁状态中解脱出来。常用的实施方法是撤销或挂起一些进程,以便回收一些资源,再将这些资源分配给已处于阻塞状态的进程,使之转为就绪状态,以继续运行。死锁的检测和解除措施,有可能使系统获得较好的资源利用率和吞吐量,但在实现上难度也最大。
线程安全是以牺牲程序运行效率为代价的,因此在注意线程安全的同时,也要注意不要滥用锁和同步方法,尽量只对那些会改变竞争资源的方法进行同步。同时还要根据单线程和多线程运行环境来提供线程不安全和线程安全两种版本,JDK提供的StringBuilder,StringBuffer就是一个例子。
五、线程通信
1、wait() - 线程等待
- 当前线程自动释放锁对象,线程进入等待状态(阻塞),直到其他线程调用了该锁的notify()或者notifyAll方法。在调用wait()之前,线程必须获得该对象的锁,因此只能在同步方法/同步代码块中调用wait()方法。
2、notify() - 线程唤醒
- 唤醒在同一个锁对象等待的单个线程,若有多个线程等待,则任意选择其中一个。和wait()一样,notify()也要在同步方法/同步代码块中调用。
3、notifyAll() - 线程唤醒
- 唤醒在此锁对象上等待的所有线程。
public class MainActivity extends AppCompatActivity {
public static final String TAG = "test";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Object lock = new Object();
Thread thread1 = new TestThread1(lock);
thread1.start();
Thread thread2 = new TestThread2(lock);
thread2.start();
}
class TestThread1 extends Thread {
private Object lock;
private TestThread1(Object lock) {
this.lock = lock;
}
@Override
public void run() {
try {
synchronized (lock) {
Log.i(TAG, Thread.currentThread().getName() + "开始运行....");
lock.wait();
Log.i(TAG, Thread.currentThread().getName() + "结束运行....");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class TestThread2 extends Thread {
private Object lock;
private TestThread2(Object lock) {
this.lock = lock;
}
@Override
public void run() {
synchronized (lock) {
Log.i(TAG, Thread.currentThread().getName() + "开始运行....");
lock.notify();
Log.i(TAG, Thread.currentThread().getName() + "结束运行....");
}
}
}
}
运行结果:
参考文章:
Android线程管理之Thread使用总结
java多线程以及Android多线程