多线程基础及应用总结
一、多线程基础
1 多线程概述
1)进程的概念?
正在运行的程序。每个程序启动时都会拉起一个进程。
2)多进程的意义?
单进程只做一件事,多进程即做多件事。多进程的意义在于提高CPU的利用率。
3)什么是线程?
一个进程可以执行多条任务,每个任务可以看成一个线程。
线程:程序的执行单元,是程序使用CPU的基本单位。
4)多线程的意义?
提高应用程序的使用率,提高获取CPU使用权的概率。
2 多线程实现方式
查看JDK API 1.8 查看官方怎么创建线程
2.1继承Thread类,重写run方法。
子类的实例执行父类的start()方法,生成线程。
使用这种方法,我们起2个新的线程。
观察启动的线程,Thread-0,Thread-1,查看Thread源码。上图中,实例化一个Thread就能拿到线程名,优先级等信息就是通过下面的构造方法+init()方法实现的。
t1.run()执行普通方法的调用,t1.start()为什么就牛逼哄哄创建新线程了?
native start0();调用了一个本地方法。这个本地方法在初始化时就通过静态代码块进行了注册。这里的本地方法是通过C/C++来实现的,主要通过JVM启动一个线程。
2.2 实现Runable接口
根据文档写代码。这里线程命名只是为了测试run()方法调用速度,threadName有机会在2个子线程中拿到相同的值,复现线程不安全问题。当然对run()方法加锁synchronized即可解决线程安全问题(更优的办法是另外添加synchronized的处理数据方法,保证CPU操作的原子性),这里为了体现多线程争抢CPU时间片的特性不做赘述,希望引起看官对多线程和线程安全的理解,仅供参考。2.3节展示多线程线程安全问题的一般做法。
/**
* 方式:二 实现Runnable接口
* 步骤 1 创建线程实现runnable接口
* 2 实现run方法
* 3 创建自定义线程类的对象
* 4 创建Thread类对象,并把自定义线程类对象作为构造参数传递
* @author Administrator
*/
public class MyRunnableThread implements Runnable{
private int threadName = 0;
@Override
public void run() {
this.threadName++;
Thread.currentThread().setName("子线程" +threadName);
for (int i =0;i<100;i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
/**
* @author Administrator
*/
public class HeiMaTest2 {
public static void main(String[] args) {
MyRunnableThread r =new MyRunnableThread();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
Thread.currentThread().setName("主线程");
for (int i=0;i<100;i++) {
System.out.println(Thread.currentThread().getName()+" " + i);
}
}
}
Runnable接口很简单,只要实现run()方法,就能跑。
2.3 实现Runable接口和继承Thread类实现多线程的区别
1、Runnable可以解决java单继承局限性,例:某个类有父类无法再继承Thread类场景。
2、实现Runnable,可以复用MyRunnableThread 的代码,因为多个线程使用的是同一个r对象。[结合2.2节实例理解] —更适合数据线程共享。
3、多线程卖票代码实例[ps:同步代码块的锁对象是任意对象,同步方法的锁对象是this,静态方法的所对象是字节码文件sellTicket.class 反射]
/**
* @author Administrator
*/
public class SellTicket implements Runnable{
private int ticket = 100;
@Override
public void run() {
while (ticket > 0){
sell();
}
}
public synchronized void sell(){
if (ticket>0){
//每调用一次这个方法,总票数减少1张
this.ticket--;
int i = 100 - ticket;
System.out.println(Thread.currentThread().getName() + "卖出第" + i + "一张票,目前余票:" + ticket);
}
}
}
2.4 JDK1.5后的Lock锁
3 线程调度及线程控制
3.1线程调度的两种模型
3.1.1 分时调度模型
平均分配CPU时间片
3.1.2 抢占式调度模型
优先级高的线程具有优先获取CPU时间片的几率。
3.2 线程控制
3.2.1 线程休眠
Thread.sleep(long millis);
3.2.2线程加入
public final void join(); 等待该线程消亡(其他线程等待)
3.2.3 线程礼让
public static void yield();暂停当前正在执行的线程对象,并执行其他线程。
3.2.4 后台线程 (守护线程— 当主线程结束时,守护线程也推退出)
跑了好几遍才跑出一个能说明问题的素材(守护线程均需要执行200次,这里在主线程结束时守护线程立即消亡)
3.2.5 线程终止
public final void stop(); 不安全,会影响其他线程的运行。
public void interrupt();interrupt方法用于中断线程,把线程状态修改,并抛出InterruptedException。
4 线程的生命周期?
新建:创建线程对象实例
就绪:有执行资格,没有CPU使用权
运行:有执行资格,有CPU执行权
阻塞:没有执行这个,没有执行权。激活后拥有执行资格,处于就绪状态
死亡 :线程对象变成垃圾,等待被回收
二、多线程应用
1、多使用局部对象(局部临时变量),而不是全局变量
2、使用不可变类(final),一旦创建,其值不再可变,降低代码中需要同步的数量。
3、尽量不适用上锁的代码
4、使用线程池的Excutor,而不是new Thread
5、使用BlockingQueue实现生产-消费模式,使用队列消减浪涌
6、避免使用静态变量,必要是使用final
7、即使使用Lock.lock(),也不用同步synachronized