线程是依赖于进程存在的,所以要想了解线程必须先了解什么是进程。
1.概念
进程:打开任务管理器,正在运行的程序都会出现进程,所以进程指的就是正在运行(进行)的程序
多进程:多个进程同时运行,比如计算机可以一边玩游戏一边听音乐
线程和多线程:在同一个进程内可以执行多个任务,而这每一个任务我就可以看成是一个线程,线程是程度的执行单元,执行路径,是程序使用CPU的最基本单位。如果程序有多条执行路径就叫做多线程
2.意义:
多进程:可以提高CPU的使用率
多线程:可以提高程序的使用率,程序的执行其实都是在抢CPU的资源,不能保证哪个线程会先抢到资源,所以线程的执行有随机性。
3.多线程的实现方案
A:继承Thread
B.实现Runnable接口
切记:run()方法里面的代码全部执行完毕,当前线程就会自动关闭销毁。
知道这一点以后要想关闭线程就知道怎么操作了
4.线程的调度和优先级问题
A:线程的调度
a:分时调度
所有线程轮流使用CPU的使用权,平均分配每个线程的时间片
b:抢占式调度
优先让优先级高的线程使用CPU,优先级高的线程获取CPU时间片的几率相对高一些,如果线程的优先级相同,会随机选择一个。
java使用的是抢占式调度模型。
B:优先级
默认是5,范围是1-10
5.线程的控制(常见方法)
线程休眠(sleep)
加入线程(join)
礼让线程(yield)
守护线程(setDaemon)
中断线程(interrupt)
6.线程的生命周期
7.多线程安全问题的原因(也是我们以后判断一个程序是否有线程安全问题的依据)
A:是否有多线程环境
B:是否有共享数据
C:是否有多条语句操作共享数据
8.同步解决线程安全问题
同步的前提:1.多个线程 2.多个线程使用的是同一个锁对象
A:同步代码块
synchronized(对象) {
需要被同步的代码;
}
这里的锁对象可以是任意对象。
B:同步方法
把同步加在方法上。
这里的锁对象是this
C:静态同步方法
把同步加在方法上。
这里的锁对象是当前类的字节码文件对象(也就是类.class)
9.线程安全的类
通过源码查看到这些类里面的方法都加了同步锁,所以是线程安全的
A:StringBuffer
B:Vector
C:Hashtable
D:如何把一个线程不安全的集合类变成一个线程安全的集合类
用Collections工具类的方法即可。
10.线程死锁问题
是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象。如果出现了同步嵌套,就容易产生死锁问题。
11.线程之间的通信(生产者与消费者的案例)
线程之间的通信指的是针对同一个资源的操作有不同种类的线程
举例:生产者做包子,消费者买包子。
此案例加入了两点优化:
A:等待唤醒机制
1.针对的是锁对象,所以锁对象是谁,就用谁调用wait和notify
2.wait,线程等待,释放锁
3.notify,正在等待的线程被唤醒
B:将同步锁代码块改成同步锁方法
很多人初级接触多线程时,会习惯把wait()和notify()放在run()方法里,一定 要谨记,这两个方法属于某个对象,应在对象所在的类方法中定义它,然后run中去调用它。另外wait()和notify()必须包括在synchronized代码块中。
package Thread.BaoZiPro;
//这是生产者类线程(生产包子)
public class Set implements Runnable {
private int i;
private Baozi bz;
public Set(Baozi bz) {
this.bz =bz;
}
@Override
public void run() {
while(true){
if(i%2==0){
bz.setNum(100, "糖包子");
}else{
bz.setNum(200, "肉包子");
}
i++;
}
}
}
package Thread.BaoZiPro;
//这是消费者类线程(购买包子)
public class Get implements Runnable {
private Baozi bz;
public Get(Baozi bz) {
this.bz =bz;
}
@Override
public void run() {
while(true){
bz.get();
}
}
}
package Thread.BaoZiPro;
//这是共享资源类
public class Baozi {
private int num;
private String type;
//设置一个标签判断是否有资源,默认值false
private boolean flag;
public synchronized void setNum(int num,String type) {
//如果flag=true,表示有包子
if(flag){
try {
this.wait();//生产者线程等待,并释放锁,等待被唤醒
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//如果如果flag=true,表示没有包子,则生产包子
this.num = num;
this.type= type;
//并设置标签为true,提示有包子了
flag=true;
//唤醒正在等待的线程,也就是唤醒消费者线程
this.notify();
}
public synchronized void get() {
//如果!flag=true,也就是flag=flase,没有包子
if(!flag){
try {
this.wait();//消费者线程等待,并释放锁,等待被唤醒
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//如果!flag=false,也就是flag=true,有包子,所以消费者购买
System.out.println(this.num+this.type);
//设置共享资源标签为false,表示包子买完了
flag=false;
//唤醒正在等待的线程,也就是唤醒生产者线程
this.notify();
}
}
package Thread.BaoZiPro;
//这是测试类
public class BaoziDemo {
public static void main(String[] args) {
Baozi bz=new Baozi();
Set set=new Set(bz);
Get get=new Get(bz);
Thread t1=new Thread(set);
Thread t2=new Thread(get);
t2.start();
t1.start();
}
}
12.线程组
线程组表示的多个线程的集合
1.创建一个线程租
ThreadGroup tG=new ThreadGroup(String name); name表示的该线程组的名称
2.设置一个线程所在的线程组
Thread t1=new Thread(tG, tg);
Thread t2=new Thread(tG,tg2);
这样就可把t1,t2两个线程放进线程组tG中
3.getThreadGroup 返回值是该线程所在线程组
4.getThreadGroup.getName返回的是该线程所在线程组的名字
13.线程池
Executors类包含了一些返回线程池的方法:
1.public static ExecutorService newCachedThreadPool() 带缓冲的线程池
2.public static ExecutorService newFixedThreadPool(int nThreads) 可重用固定线程数的线程池
3.public static ExecutorService newSingleThreadExecutor() 单个线程的Executor
调用这些方法会返回一个ExecutorService对象,该对象表示一个线程池。
线程池对象可以通过调用submit()方法来执行Runnable对象或者Callable对象代表的线程,相当于线程的创建并启动。
14.定时器
定时器是一个应用十分广泛的线程工具,可用于调度多个定时任务以后台线程的方式执行。在Java中,可以通过Timer和TimerTask类来实现定义调度的功能
实现一个定时器的步骤:
1.创建定时器对象
2.创建一个任务
3.通过定时器调度任务