基本概念
进程:一个程序在运行过程中的动态指令集和系统内存、资源的集合。
线程:线程是进程中的一条执行路径。
- 一个程序运行后可以包含多个进程,一个进程可以包含多个线程,但至少包括一个线程。线程之间相互独立,各自拥有独立的堆栈,程序计数器和局部变量,但不共享系统资源,如公共的变量,所以需要考虑线程之间同步的问题。
- Java中线程的执行是抢占
式的并发执行——并发是指同一时刻只有一条指令集被执行,但多个进程之间被快速轮换执行,使得宏观上具备多个进程同时进行的效果。并行是指在同一时刻,多条指令集在多个处理器上同时执行。??有争议 - 进程是操作系统调度调度和管理的基本单位,所以操作系统不会直接参与线程的调度和管理,那是进程内部的工作。而线程是程序使用CPU的最基本单位。
tip:Java程序运行在JVM进程中,JVM是多线程的,因为至少启动了主线程和垃圾回收线程
创建线程
java是没有办法自己创建线程的,是调用了C/C++接口
1、三种方法
- 继承Thread子类,重写run();
run()只是封装了被线程执行的代码,直接调用和普通方法无差异,start()方法是启动一个线程,并由JVM调用run()。 - 实现Runnable接口 优势在于可以避免单继承带来的局限性,不须一定要和继承Thread类导致代码灵活性下降;数据分离,适合将任务代码复用到不同的线程中,多个数据共享同一份任务数据。
- 实现Callable接口,仅仅能在线程池中进行,优点是能获取线程执行的结果并且能跑出异常,缺陷是编码复杂
Tip:比较骚的语法
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("heheda");
}
}){
@Override
public void run() {
super.run();//影响的关键在于这句,自己去研究下踏马的
System.out.println("hahaha");
}
}.start();
//输出heheda
//hahaha
2、给线程起名
两种方法
- Thread类中有 setName()和getName(),而直接调用后者有默认的实现。
- 或者通过带String参数的构造方法来设置线程名,这需要重写Thread(String var1)构造函数
Thread.currentThread().getName();获取当前运行线程的名字
3、设置线程的优先级
- 分时调度模型 所有线程被平均分配CPU使用权,轮流占用CPU
- 抢占式调度模型 优先级高的有更高几率获取,同优先级的随机分配 Java使用的是这种模型。
Java中通过setPriority()和getPriority()来设置线程优先级,优先级范围是1-10,默认是5
4、线程的调度
- sleep()方法 在指定的毫秒内让当前正在执行的线程休眠,注意和notify()和wait()方法的区别:
- wait()可以指定时间参数,也可以不指定时间参数,而sleep必须指定时间参数。
- wait()由Object调用,因为wait()方法的调用是依赖于锁对象的,而锁对象可以由任何对象构成。sleep()是Thread类中的静态方法。
- sleep()不释放锁,而wait()释放锁,他们调用时都会把cpu资源让出,区别是wait调用后必须通过notify()或者notifyAll()重新获取cpu执行权。
- join()方法 让调用该方法的线程执行完毕之后,其他线程才能执行,必须放在start之后。有三个重载方法,参考 Java Thread的join() 之刨根问底
void join():当前线程等该加入该线程后面,等待该线程终止。
void join(long millis):当前线程等待该线程终止的时间最长为 millis 毫秒。 如果在millis时间内,该线程没有执行完,那么当前线程进入就绪状态,重新等待cpu调度。
void join(long millis,int nanos):等待该线程终止的时间最长为 millis 毫秒 + nanos纳秒。如果在millis时间内,该线程没有执行完,那么当前线程进入就绪状态,重新等待cpu调度
- yield()方法 暂停当前正在执行的线程,执行其他线程。不可靠的方法。应该使用等待唤醒机制
- setdeamon方法 将线程标记为守护线程或用户线程,正在运行的线程都是守护线程时,Java虚拟机退出,该方法必须在线程启动前调用——Android中守护线程的使用??
- stop方法 结束线程,但可能引起死锁,不推荐使用,推荐使用interrupt方法,该方法会将被操作线程的wait、notify、sleep等状态终止,并抛出一个interruptedException ,并将线程剩余方法跑完。
If this thread is blocked in an invocation of the wait(), wait(long), or wait(long, int) methods of the Object class, or of the join(), join(long), join(long, int), sleep(long), or sleep(long, int), methods of this class, then its interrupt status will be cleared and it will receive an InterruptedException.
SecurityException - if the current thread cannot modify this thread
5、线程的生命周期
实战演练
三个窗口售卖总共100张票
public class SellingTickets implements Runnable{
private int mTicketNum = 100;
@Override
public void run() {
while(mTicketNum>0) {
System.out.println(Thread.currentThread().getName()+" selling the ticket NO."+mTicketNum);
mTicketNum--;
}
}
}
public class SellingWindow extends Thread {
public SellingWindow() {
super();
}
public SellingWindow(Runnable runnable, String s) {
super(runnable, s);
}
}
public static void main(String[] args) {
SellingTickets sellingTask = new SellingTickets();
SellingWindow window1 = new SellingWindow(sellingTask,"window1");
SellingWindow window2 = new SellingWindow(sellingTask,"window2");
SellingWindow window3 = new SellingWindow(sellingTask,"window3");
window1.start();
window2.start();
window3.start();
}
//结果中虽然能够实现基本卖票效果,但是票的顺序和总票数有一点微小偏差
所以需要做线程同步问题
6、线程同步
多线程代码中不会等到一个方法运行结束后再跑另一个线程的方法,可能线程A的方法走到一半,线程B方法就开始执行。
以下是一个关于线程同步的例子:
public class TestClass {
private class Window implements Runnable {
private int mTicket = 1;
@Override
public void run() {
while (mTicket <= 100) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第 " + (mTicket++) + " 张票");
}
}
}
public static void main(String[] args) {
Window window = new TestClass().new Window();
Thread th1 = new Thread(window, "窗口1");
Thread th2 = new Thread(window, "窗口2");
Thread th3 = new Thread(window, "窗口3");
th1.start();
th2.start();
th3.start();
}
/**
* 问题1.相同的票卖了多次,原因是CPU的操作是原子性的,mTicket在输出后才被++,然后将新值赋值给mTicket,
* 所以当不同线程抢占CPU的时候,会出现该变量卡在被赋值前,多个线程运行了输出语句。
*
* 问题2.和出现超过100的票,原因是线程切换随机性和代码延迟造成的,
* 不同线程都成功通过了mTicket <= 100的判断后引发的并发异常
*/
}
多线程同步问题有两个出现条件
- 有共享数据
- 有多条操作共享数据的代码
解决问题方式:
Synchronized关键字
- 同步代码块 Synchronized(obj){} 多个线程使用同一把锁才有效果,弊端就是当线程增加时,每次运行到同步代码块都需要判断一次同步锁,耗费系统资源。
- 同步锁 synchronized private void function() {},synchronized也可以放到方法修饰符后面,同步的锁是this。
静态方法的锁是对应的类的字节码对象,类名.class
使用synchronized关键字时还需要注意
1.该关键字是锁住了包含的代码,并非锁住了CPU的使用权,当同步代码块被一个线程抢占时,其他线程在同一时间不能访问同步代码块,但可以访问非同步代码块。
2.synchronized会封锁住所有影响的模块,如持有同一个对象锁的几个不同方法。当一个方法中的对象锁被某个线程占用,其他持锁的方法也不能被别的线程执行。
3.synchronized关键字不能继承。
对于父类中的 synchronized 修饰方法,子类在覆盖该方法时,默认情况下不是同步的,必须显示的使用 synchronized 关键字修饰才行。
4.在定义接口方法时不能使用synchronized关键字。
5.构造方法不能使用synchronized关键字,但可以使用synchronized代码块来进行同步。
更详细知识可参照这篇文章:
Java中Synchronized的用法
Lock方法
有几个实现类,在需要同步的代码中使用,比较常用的是reentrantLock类,方法lock(),unlock();建议采用try finally结构。
try {
mLock.lock();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(mTicketNum > 0) {
System.out.println(Thread.currentThread().getName() + " selling the ticket NO." + mTicketNum);
mTicketNum--;
}
}finally {
mLock.unlock();
}
7、线程安全的工具类
线程安全是指多个Thread访问同一个对象的相同方法时不会出现同步的问题,比如多个线程不会同时调用一个集合的add()方法。但如果我们要使用线程非原子方法的组合,就不是线程安全了,比如
public void method() {
boolean absent = !list.contains(x);
if(absent)
list.add(x);
}
StringBuffer, Vector, Hashtable,通过源码可以看到他们的方法都加锁了。
Vector目前不推荐使用了,但可以通过Collections.synchronizedList()将线程不安全的集合转变为线程安全的集合。如
List<String> list = Collections.synchronizedList(new ArrayList<String>());
其内部实现也是将基本操作加上synchronized关键字,类似于Vector,所以也不推荐使用
说说线程安全包装:Collections.synchronizedList
8、同步的弊端
同步的弊端,一是效率低,因为每次运行到锁代码都要判断锁;二是可能产生死锁问题。
死锁:两个或以上的线程在争夺资源的时候,产生的相互等待的现象
//举个例子
class DeadLockTask implements Runnable {
public static Object myLock1 = new Object();
public static Object myLock2 = new Object();
private boolean mFlag;
public DeadLockTask(boolean mFlag) {
this.mFlag = mFlag;
}
@Override
public void run() {
if (mFlag) {
synchronized (myLock1) {
System.out.println("if MyLock1");
synchronized (myLock2) {
System.out.println("if MyLock2");
}
}
} else {
synchronized (myLock2) {
System.out.println("else MyLock2");
synchronized (myLock1) {
System.out.println("else MyLock1");
}
}
}
}
}
public class TestClass {
public static void main(String[] args) {
DeadLockTask task1 = new DeadLockTask(true);
DeadLockTask task2 = new DeadLockTask(false);
Thread th1 = new Thread(task1);
Thread th2 = new Thread(task2);
th1.start();
th2.start();
}
}
输出的是
else MyLock2
if MyLock1
9、线程间的通信
指的是不同线程间针对同一资源进行操作
不同线程之间都要加锁,且加的锁必须是一样的,原理参照上述synchronize关键字注意点。
经典例子生产者消费者实例代码
class SetTask implements Runnable {
private boolean mFlag;
private Student mStudent;
public SetTask(Student student) {
this.mStudent = student;
}
@Override
public void run() {
while (true) {
synchronized (mStudent) {
if (mFlag) {
mStudent.name = "Leo";
mStudent.age = 24;
} else {
mStudent.name = "Franz";
mStudent.age = 25;
}
mFlag = !mFlag;
}
}
}
}
class GetTask implements Runnable {
private Student mStudent;
public GetTask(Student mStudent) {
this.mStudent = mStudent;
}
@Override
public void run() {
while (true) {
synchronized(mStudent) {
System.out.println(mStudent.name + " " + mStudent.age);
}
}
}
}
class Student {
String name;
int age;
}
public class TestClass {
public static void main(String[] args) {
Student student = new Student();
SetTask task1 = new SetTask(student);
GetTask task2 = new GetTask(student);
Thread th1 = new Thread(task1);
Thread th2 = new Thread(task2);
th1.start();
th2.start();
}
上述代码解决了线程安全问题,但没有处理好线程之间有序地通信(同一个线程仍然可能连续执行很多次),这就需要等待唤醒机制。
class SetTask implements Runnable {
private boolean mFlag;
private Student mStudent;
public SetTask(Student student) {
this.mStudent = student;
}
@Override
public void run() {
while (true) {
synchronized (mStudent) {
if(mStudent.flag) {
try{
mStudent.wait();//释放锁,下次被唤醒的时候就从这里醒过来
}catch (InterruptedException e){
e.printStackTrace();
}
}
if (mFlag) {
mStudent.name = "Leo";
mStudent.age = 24;
} else {
mStudent.name = "Franz";
mStudent.age = 25;
}
mFlag = !mFlag;
mStudent.flag = true;
mStudent.notify();//唤醒之后并不表示被唤醒线程立马拥有执行权,而是进入等待队列争夺执行权。
}
}
}
}
class GetTask implements Runnable {
private Student mStudent;
public GetTask(Student mStudent) {
this.mStudent = mStudent;
}
@Override
public void run() {
while (true) {
synchronized(mStudent) {
if(!mStudent.flag) {
try{
mStudent.wait();
}catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(mStudent.name + " " + mStudent.age);
mStudent.flag = false;
mStudent.notify();
}
}
}
}
class Student {
String name;
int age;
boolean flag;
}
public class TestClass {
public static void main(String[] args) {
Student student = new Student();
SetTask task1 = new SetTask(student);
GetTask task2 = new GetTask(student);
Thread th1 = new Thread(task1);
Thread th2 = new Thread(task2);
th1.start();
th2.start();
}
}//其实上述代码如果把Student的成员设置为私有,
//将Runnable中的方run()方法封装到Student中,并用synchronized修饰,更加规范.
//注意线程被notify的地方就是当初wait的后一行语句;
改进版本的代码如下:
public synchronized void changeName(String name,int age) {
if (mTimes < 300) {
if (flag) {
setName(name);
setAge(age);
mTimes++;
flag = false;
notify();
} else {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public synchronized void showName() {
if (!flag) {
System.out.println("Get student name: " + getAge() + getName());
flag = true;
notify();
} else {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void run() {
while(true) {
if(mStudent.mTimes % 2 == 0) {
mStudent.changeName("Leonardo",24);
}else {
mStudent.changeName("Hugo",25);
}
}
}
public void run() {
while(true) {
mStudent.showName();
}
}
至此,线程生命周期就可以再次深化,以下附上图解:
10.线程组
Java中使用ThreadGroup来管理一组线程,并允许直接对线程组进行控制。默认情况下所有线程属于主线程组public final ThreadGroup getThreadGroup()
Task task = new Task();
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
System.out.println(thread1.getThreadGroup().getName());
System.out.println(thread2.getThreadGroup().getName());
//默认都属于main线程
可以将新建的线程归类到特定线程组中
ThreadGroup threadGroup = new ThreadGroup("Leonado Hugo up");
Task task = new Task();
Thread th1 = new Thread(threadGroup, task,"线程1");
Thread th2 = new Thread(threadGroup, task,"线程2");
System.out.println(th1.getThreadGroup().getName());
//输出我会有二猫
线程组可以对线程进行批处理,比如threadGroup.setMaxPriority(7);
不过开发中使用较少。
11.线程池
程序启动一个线程成本比较高,因为涉及到与操作系统交互,使用线程池能够很好地提高性能,因为线程池中的每一个线程代码执行完后并不会死亡,而是回到池中变成空闲状态,等待其他对象来调用。当程序中使用大量生存期很短的线程时,更应该使用线程池。
JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法
public static ExecutorService newCachedThreadPool()//可以无限扩大的线程池
public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newSingleThreadExecutor()
这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。它提供了如下方法
Future<?> submit(Runnable task)
<T> Future<T> submit(Callable<T> task)
//示例代码
ExecutorService pool = Executors.newFixedThreadPool(3);
// is a ThreadPoolExecutor exactly, implement of ExecutorService
pool.submit(new OutTask());
pool.submit(new OutTask());
//ThreadPoolExecutor还有execute方法提交一个Runnable任务
pool.shutdown();
//Initiates an orderly shutdown in which previously submitted tasks are executed, but no new tasks will be accepted.
//若没有这个方法,线程就会保留而不结束。
shutdown和shutDownNow方法的区别
线程池中独有的创建新线程的方法
public class TestClass {
static class CallableTask implements Callable<Integer> {
private Integer number;
public CallableTask(Integer number) {
this.number = number;
}
@Override
public Integer call() throws Exception {
return number * 2;
}
}
//Callable是有返回值的,其泛型就是call方法的返回值类型。
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(3);
Future<Integer> result1 = pool.submit(new CallableTask(200));
Future<Integer> result2 = pool.submit(new CallableTask(100));
System.out.println(result1.get());//该方法用于获取线程执行结果
System.out.println(result2.get());
pool.shutdown();
}
}
12、定时器
定时器用于调度单个或多个定时任务单次或多次地执行,由Timer和TimerTask结合使用,不过开发中一般不用他们,而是使用Quartz这个开源框架。
每一个Timer对应单个后台线程。当计时器所有任务执行完毕后,会被当做垃圾回收,但这可能花掉较长的时间——
After the last live reference to a Timer object goes away and all outstanding tasks have completed execution, the timer's task execution thread terminates gracefully (and becomes subject to garbage collection). However, this can take arbitrarily long to occur.
须知Timer对应的线程又不是守护线程,当用户想尽快结束该线程,应调用其cancel方法。
Timer是线程安全的,多个线程可以共享单个Timer而无需进行外部同步。
此类不提供实时保护,它使用Object.wait()来安排任务。
Timer有其固有的缺陷,
- 有且仅有一个线程去执行定时任务,如果存在多个任务,切任务时间过长,会导致执行效果与预期不符——
Timer tasks should complete quickly. If a timer task takes excessive time to complete, it "hogs" the timer's task execution thread. This can, in turn, delay the execution of subsequent tasks, which may "bunch up" and execute in rapid succession when (and if) the offending task finally completes.
- 如果TimerTask抛出RuntimeException,Timer会停止所有任务的运行
Timer有很多构造器,简单用法如下:
Timer timer = new Timer();
timer.schedule(new TaskPackage(),2000);
//你会发现即使不是循环的任务,该线程也很可能不会再执行完后结束,
//需要手动调用cancel方法或者System.gc()。
网友评论