先浅显的写一下Java多线程的知识,之后再添加
#多线程
一、前置知识
1、并发和并行
并发:两个或多个事件在同一时间段前后发生或交替发生
并行:两个或多个事件在同一时间点发生
2、进程和线程
进程:进入内存执行的应用程序
线程:是进程的一个基本单元,是操作系统运算调度的最小单位
思考一下:单核CPU系统中,多线程的并发还有没有用?
解答:理所当然,在多核CPU的系统中,多线程可以大大提高速度。
但是,在单核系统中,多线程并发的好坏得看情况而言:
*好:
1、在一些情形下,我们有多个程序或功能看起来是同时运行的需求;
2、或者单线程需要运行一会休息一会,而我们的多线程并发可以在本该休息的时候工作,提高了cpu的利用率;
3、在有频繁的IO操作时使用多线程会大大提高程序的执行效率。
*坏:只是提高了CPU的利用率,并没有提高速度
二、多线程并发
1、多线程的内存图示(和单线程相比)
*假设main方法中有两个线程创建了(其中一个主线程)
*拿线程调用run()方法和start()方法来进行比较
1、直接调用run方法,这俩压入一个栈执行,是单线程
2、调用start方法,操作系统会给线程重新开辟一个栈内存空间,cpu可以选择执行,所以这俩线程就
是并发执行
2、线程的创建(2种方法)
1、写Thread的子类,并重写run()方法;创建子类对象,调用start()方法开启线程;
2、写实现类实现Runnable接口,重写run()方法;创建实现类对象;创建Thraed对象,传入实现类对象,调用start()方法开启线程。
3、还可以使用这俩的内部类写法
(1)new Thraed(){重写run()方法}.start();
(2)new Thread(new Runnable(重写run()方法)).start();
4、两种方法的优劣:
java是单继承的关系,子类的方法让该类无法再继承其它类;
接口的方法还有利于解耦;
结论: 实现runnable接口的方法好一点
3、线程的六大状态:
1、new 新建状态:没有启动,还不能被CPU选择运行;
2、Runnable 运行状态:start启动了,被CPU选择运行
3、Blocked 阻塞状态:start启动了,可以被CPU选择,但是没有被选中运行;
4、TIMED_WAITING 计时睡眠状态:调用sleep()方法,进入睡眠状态,睡一段时间后会自己醒来继续
参与CPU的竞争。 wait(时间参数),等待唤醒,若没被唤醒,时间一到,自己醒来。
5、WAITING 无限等待状态:调用wait()方法进入无限等待状态,除非notify()唤醒,不然会一直等下
去
6、TERMINATED 死亡状态:run()方法执行结束;stop()方法强行结束;或出现异常导致结束;线程进入死亡状态,不会再执行。
4、线程的常用方法:
1、getName():获取线程名称
2、setName():设置线程名称
3、Thread.currentThread().getName(); 获取当前执行线程的名称
4、sleep():暂停执行,到时间后自动醒来,线程.sleep();
5、wait():调用方式,锁对象.wait(); 还可以加时间参数,还未被唤醒,时间一到也会自己醒来
6、notify():唤醒其所在锁所阻塞的单个线程,锁对象.notify();
7、notifyAll():唤醒所在监视器上所有等待的线程
5、线程的安全问题
1、产生原因:cpu执行多线程时,执行的过程中随时有可能切换到其它线程执行。
2、解决办法:将使用共同数据(可能造成安全问题的代码锁起来)
(1)同步代码块
(2)同步方法(静态同步方法也可以)
(3)lock锁机制
例题:两个窗口同时售卖十张票。
主方法:
` int num = 10;
RunnableImpl r1 = new RunnableImpl(num);
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r1);
t1.start();
t2.start();`
Runnable接口的实现类:
` public class RunnableImpl implements Runnable {
private static int num = 0;
Object o = new Object();
Lock l = new ReentrantLock();
@Override
public void run() {
while (true) {
//1、同步代码块
synchronized (o) {
if (num > 0) {
System.out.println(Thread.currentThread().getName() + "窗口顾客正在支付第" + num + "张票");
try {
Thread.sleep(2000);//触发操作系统立刻重新进行一次CPU竞争
} catch (InterruptedException e) {
e.printStackTrace();
}
num--;
System.out.println(Thread.currentThread().getName() + "窗口顾客支付第" + (num + 1) + "张票成功");
}
}
pay();
pay2();
pay3();
}
}
public RunnableImpl(int num) {
this.num = num;
}
//2、同步方法:有个默认的锁对象,就是我们的实现类对象,也就是this
public synchronized void pay(){
if (num > 0) {
System.out.println(Thread.currentThread().getName() + "窗口顾客正在支付第" + num + "张票");
try {
Thread.sleep(2000);//触发操作系统立刻重新进行一次CPU竞争
} catch (InterruptedException e) {
e.printStackTrace();
}
num--;
System.out.println(Thread.currentThread().getName() + "窗口顾客支付第" + (num + 1) + "张票成功");
}
}
//3、静态同步方法也可以,因为静态修饰的东西优先进入内存,所以不能在用new出来的实现类对象当做锁对象。
// 这里使用实现类的class对象当做锁对象,比如本例:RunnableImpl.class 对象
public static synchronized void pay2(){
if (num > 0) {
System.out.println(Thread.currentThread().getName() + "窗口顾客正在支付第" + num + "张票");
try {
Thread.sleep(2000);//触发操作系统立刻重新进行一次CPU竞争
} catch (InterruptedException e) {
e.printStackTrace();
}
num--;
System.out.println(Thread.currentThread().getName() + "窗口顾客支付第" + (num + 1) + "张票成功");
}
}
//4、lock锁
public void pay3(){
l.lock();
if (num > 0) {
try {
System.out.println(Thread.currentThread().getName() + "窗口顾客正在支付第" + num + "张票");
Thread.sleep(2000);//触发操作系统立刻重新进行一次CPU竞争
num--;
System.out.println(Thread.currentThread().getName() + "窗口顾客支付第" + (num + 1) + "张票成功");
} catch (InterruptedException e) {
e.printStackTrace();
}finally { //不论异常是否发生,总是释放锁资源
l.unlock();
}
}
}
}`
6、简单的等待唤醒案例
*描述:
客户线程:客户给老板说要买包子,然后进入无限等待状态
老板线程:老板知道客户要买包子,做包子,唤醒客户线程,让客户吃包子
`
Object o = new Object(); //共同使用的锁对象
//客户线程
new Thread(){
@Override
public void run() {
synchronized (o){
System.out.println("老板,来2个包子");
try {
o.wait(); //进入无限等待状态
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("吃完了");
}
}
}.start();
//老板线程
new Thread(){
@Override
public void run() {
synchronized (o){
System.out.println("请稍等");
try {
Thread.sleep(2000); //睡眠状态做包子
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("做好了");
o.notify(); //唤醒锁对象o正在阻塞的线程
}
}
}.start();
'
线程池
池子就是容器,可以用集合来存放
1、当程序第一次启动时,创建多个线程,保存到一个集合中。方便取用,省去了频繁创建的过程
2、使用list集合存放。remove()方法返回线程名字,且保证了一个线程只能被一个任务使用
3、使用完毕后,归还线程给线程池,add()方法归还。
4、jdk1.5之后,有内置线程池
内置线程池
1、java.util.concurrent.Executors类
static ExecutorService newCachedThreadPool()
创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。
2、java.util.concurrent 接口 ExecutorService
(1)Future<?> submit(Runnable task)
提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future。
(2)void shutdown() 销毁线程池
3、步骤:
(1)创建线程池对象,参数为线程数量(返回值是ExecutorService接口对象)
ExecutorService es = Executors.newFixedThreadPool(5);
(2)创建Runnable接口的实现类,重写run()方法
public class RunnableImpl implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
(3)调用ExecutorService接口的submit()方法,拿出线程,传递实现类对象。
es.submit(new RunnableImpl());
(4)线程使用完毕,会被自动归还给线程池