多线程:就是应用程序有多条执行路径
线程:是进程的执行单元,执行路径。
如果一个应用程序只有一条只有执行路径,那么,该程序就是单线程程序
如果一个应用程序有多条执行路径,那么,该程序就是多线程
进程:正在运行的应用程序,
每个进程具有独立的空间。
我们目前的操作系统都是支持多进程的。
举例:360,迅雷
一个人吃一桌饭:单进程单线程
多个人吃一桌饭:单进程多线程
多线程的好处:效率高一点
多线程的问题:
多个线程针对同一个资源,导致数据会有安全问题,资源冲突
我们怎么实现多线程程序?
假设JAVA没有提供,怎么办?
我们要想实现多线程,首先要去创建一个进程,然后将进程拆分成多个线程
问题来了:进程是由操作系统根据应用程序的运行产生的,也就是,我想要创建进程,我们得去操作操作系统的资源,但是,java程序是不能直接操作操作系统的资源的,那么,我们直接通过java语言做这件事情是实现不了的。
所以,java就提供了线程的类供我们使用,
但是,底层肯定是C或者C++去完成这件事情,这个操作被包装了,我们看不多,把C或C++的程序按照java的模式去编写,然后用java去调用了C或C++的方法。
接下来,我们只能依赖Thread实现。
方法一:通过继承Thread类
通过查看API,可以知道,多线程程序的模拟有两种方法
A:继承Thread类
B:实现Runnable接口
继承Thread类的步骤:
A:自定义类实现Thread类
B:在自定义类中重写run()方法
为什么要重写run()方法呢?
因为run()方法中的封装的代码才是可以被线程执行的。
C:创建自定义类的对象
D:启动线程并使用
如何启动?
线程对象创建后,将来调用的肯定是run方法里面的代码
但是,必须通过start()方法启动
run()和start()的区别
run()封装了被线程执行的代码
start():让线程启动,并由jvm调用run()方法。
如何获取到运行时的线程的名字?
getName()//返回线程名称,Thread类中的方法
设置线程的名字:
一:自定义名字:
setName(String name)//更改线程名称
二:Thread(String name):父类的带参构造。
自定义类调用上面的方法:
public myThread(String name){
super(name);
}
方法二:通过实现Runnable接口
实现Runnable接口的方式:
A:自定义类MyRunnable实现Runnable接口
B:重写run()方法
C:创建自定义类的对象
D:创建Thread类的对象,将MyRunnable类的对象作为构造参数传递
E:??调用Thread的对象调用start方法。
针对实现接口的这种方法,没有办法获取名称,所以java就提供了一个静态方法。获取当前正在执行的线程对象
public static Thread currentThread();
然后通过线程对象去获取对象的名称。
设置线程名字:
A:setName()
B:Thread(Runnable target, String name)通过构造函数参数直接设置名字。
问题1:继承Thread类和实现Runnable有什么区别?
因为类只能单继承!!!!!!!!!!!!!
比如:
我有一个fu类,一个Zi类,Zi类已经继承了父类,要想实现多继承,怎么办?
实现Runnable接口。
问题2:继承Thread类的方式,子类本身就是一个线程类,实现Runnable接口的方式,具体类本身是不是一个线程实例呢?
不是!因为如果是的话,就可以直接调用setName方法去设置线程的名字了。
应用程序的执行:
CPU在多个应用程序之间进行着高效的切换运行,这个切换的时间很短,短到你感觉不到,所以,我们觉得我们的程序是在同时并发的执行, 但是准确的说:CPU在某个时间点上只能有一个程序在执行
多线程的好处
开多线程的目的是提高CPU的使用率,进而来提高程序的效率。
面试题:线程的生命周期
新建:创建线程对象
就绪:准备随时执行,具备执行资格,没有执行权
运行:执行Run中代码
死亡:run结束
阻塞:在运行过程中出现意外情况,,当情况呗解决后,就回到就绪状态。
代码一:Thread
package com.thread.ticket.thread;
/**
* 我有一百张票,放在4个窗口卖
*
* 线程是共享进程的资源
* 一个JAVA程序是 一个进程
* 栈:每个线程都有自己的一份
* 堆:共享的
* 方法区:共享的
*
* 问题:
* A:把int ticket = 100 ;放到run()方法中,每次调用run()都会重新定义100张票。
* 每个线程都有自己的栈空间,那么,每个线程对象都会拥有自己的100张票
* B:把private int tickets =100 ;定义到了成员变量的位置,还是会有问题
* 按照正常的思绪,应该是没有问题了的,但是还是出现了问题。
* 原因很简单,因为我们创建了四个对象,而普通的成员变量是每个对象所特有的。
* 也就是说,每个线程对象还是拥有自己的100张票。
* 我们的目的是:这四个线程对象共同拥有100张票,请问怎么能够实现?
* 加static
*/
public class TicketDemo {
public static void main(String[] args) {
Ticket t = new Ticket("窗口1");
Ticket t1 = new Ticket("窗口2");
Ticket t2 = new Ticket("窗口3");
Ticket t3 = new Ticket("窗口4");
t.start();
t1.start();
t2.start();
t3.start();
}
}
package com.thread.ticket.thread;
public class Ticket extends Thread {
private static int tickets = 100;
public Ticket() {
}
public Ticket(String name) {
super(name);
}
@Override
public void run() {
while (true) {
if (tickets > 0) {
System.out.println(getName() + "正在出售第" + (tickets--) + "张票");
}
}
}
}
代码二:Runnable优化
package com.thread.ticket.runnable;
/**
* 我有一百张票,放在4个窗口卖
* 通过Runnable改进卖票程序
*/
public class TicketDemo {
public static void main(String[] args) {
Ticket t1 = new Ticket();
Thread t = new Thread(t1, "窗口1");
Thread t4 = new Thread(t1, "窗口2");
Thread t2 = new Thread(t1, "窗口3");
Thread t3 = new Thread(t1, "窗口4");
t.start();
t4.start();
t2.start();
t3.start();
}
}
package com.thread.ticket.runnable;
public class Ticket implements Runnable {
private static int tickets = 100;
@Override
public void run() {
while (true) {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
}
}
}
}
代码三:线程安全问题分析与讨论
package com.thread.ticket1;
/**
* 我有一百张票,放在4个窗口卖
* 通过Runnable改进卖票程序
* //模拟正常的现象,我让线程稍微等一下。
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
*问题:出现相同的票卖了两次,以及出现负数票。
*A:相同票卖了两次:
* CPU的每一次执行,必须是一个原子性的操作,这个操作是不能再分隔的,
* int i= 10; 其实是两个原子性动作。
* 这样的话
* tickets-- 也不是一个原子性动作,可能在操作的中间部分,被其他的线程给执行了,
* 这样就会有相同的票执行了多次
* 面试题:
* if(true)
* int a=10;
* //if语句没有写大括号,默认情况下,只负责处理跟在其后面的一条语句,
* 而:int a = 10; 其实不是一个语句;
* 等价:
* int a;
* a=10;
*B:负票:
* 因为线程的随机性,
*/
public class TicketDemo {
public static void main(String[] args) {
Ticket t1 = new Ticket();
Thread t = new Thread(t1, "窗口1");
Thread t4 = new Thread(t1, "窗口2");
Thread t2 = new Thread(t1, "窗口3");
Thread t3 = new Thread(t1, "窗口4");
t.start();
t4.start();
t2.start();
t3.start();
}
}
package com.thread.ticket1;
public class Ticket implements Runnable {
private static int tickets = 100;
/* @Override
public void run() {
while (true) {
//t1,t2,t3,t4
//tickets = 100;
//假设t1抢到了CPU的执行权
if (tickets > 0) {
//模拟正常的现象,我让线程稍微等一下。
try {
Thread.sleep(100);//t1睡眠了。t2抢到了CPU的执行权,t2进来后,也睡眠了
} catch (InterruptedException e) {
e.printStackTrace();
}
//t1醒过来,继续执行
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
//窗口1正在出售100张票
//tickets = 99
//这仅仅是我们自己的理想状况,但是实际情况并不是这个样子的。
//实际情况是:每一次线程执行的程序应该是一个原子性的操作。也就是这个操作是不能再分割的。
//tickets--这个动作有tickets-1=99和tickets=99两个动作
//这里的话,至少是两个动作,那么在-1的动作执行时候,并没有给tickets重新赋值。
//t2醒过来了,这个时候tickets还是100,所以:这个时候,窗口二正在执行100张票。
}
}
}*/
@Override
public void run() {
while (true) {
//t1,t2,t3,t4
//tickets = 100;
//假设t1抢到了CPU的执行权
//t2抢到了
//t3抢到了
//t4抢到了
if (tickets > 0) {
//t1睡了
//t2睡了
//t3睡了
//t4睡了
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
/**
* t1醒过来了
* 窗口1正在出售第1张票 tickets = 0
* t2醒过来了
* 窗口2正在出售第0张票 tickets = -1
* t3醒过来了
* 窗口3正在出售第-1张票 tickets = -2
* t4醒过来了
* 窗口4正在出售第-2张票 tickets = -3
*/
}
}
}
}
代码4:线程安全的问题解决
package com.thread.ticket2;
/**
* 这里解决上面代码出现负票的方法:
*
* 首先:多线程出现安全问题的原因:
* A:是多线程程序;
* B:有共享数据
* C:针对共享数据有多条语句操作
*
* 解决:
* 只需要把多线程环境中,操作共享数据的操作给变成单线程的就没有问题了。
* java针对这种情况,就提供了同步技术,,同步代码块
* 格式:
* synchronized(对象){
* 需要被同步的代码;
* }
* A:对象?
* 如果不知道用哪个对象,就使用Object,
* B:需要被同步的代码块?
* 哪些代码导致出现了问题,就把哪些代码同步起来。
*
* 哪些代码会出现问题呢?
* 有共享数据
* 针对共享数据有多条语句操作
* 加入同步后发现还有问题,为什么??
* while (true) {
synchronized (new Object()) {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
}
}
}
* 同步代码块中的对象针对多个线程必须是同一个,
* 其实这个对象被称为同步锁对象
*
*/
public class TicketDemo {
public static void main(String[] args) {
Ticket t1 = new Ticket();
Thread t = new Thread(t1, "窗口1");
Thread t4 = new Thread(t1, "窗口2");
Thread t2 = new Thread(t1, "窗口3");
Thread t3 = new Thread(t1, "窗口4");
t.start();
t4.start();
t2.start();
t3.start();
}
}
package com.thread.ticket2;
/**
* 类似火车上厕所的例子
*
* @author yuliyang
* @version $Id: Ticket.java, v 0.1 2016年11月30日 下午10:02:32 yuliyang Exp $
*/
public class Ticket implements Runnable {
private static int tickets = 100;
private final Object b = new Object();
@Override
public void run() {
while (true) {
/**
* t1,t2,t3,t4
* 假设 ticket = 1
* t1过来,看到synchronized就知道下面的代码被同步了。
* 看到obj就知道这是这是锁对象,假设t1进来前,obj这个锁是开的状态
*
*/
synchronized (b) {
/**
* t1进来后,就把锁对象的状态改为关的状态。
*/
if (tickets > 0) {
try {
Thread.sleep(100);
//t1睡着了。
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
}
//t1出来了,你们继续抢,我也还可以抢。
}
}
}
}