多线程(理解)
要想了解多线程,必须先了解线程,而要想了解线程,必须先了解进程,因为线程是依赖于进程而存在。
一、 什么是进程?
通过任务管理器我们就看到了进程的存在。
就是正在运行的程序。是系统进行资源分配和调用的独立单位。
每一个进程都有它自己的内存空间和系统资源。
二、 多进程有什么意义呢?
单进程的计算机只能做一件事情,而我们现在的计算机都可以做多件事情。
举例:一边玩游戏(游戏进程),一边听音乐(音乐进程)。
现在的计算机都是支持多进程的,可以在一个时间段内执行多个任务。
可以提高CPU的使用率。
问题:
一边玩游戏,一边听音乐是同时进行的吗?
不是。因为单CPU在某一个时间点上只能做一件事情。而我们在玩游戏,或者听音乐的时候,是CPU在做着程序间的高效切换让我们觉得是同时进行的。
三、 什么是线程呢?
在同一个进程内又可以执行多个任务,而这每一个任务我就可以看出是一个线程。
线程:是进程中的单个顺序控制流,是一条执行路径。
单线程:一个进程只有一条执行路径。
多线程:一个进程有多条执行路径。
四、 多线程有什么意义呢?
多线程的存在,不是提高程序的执行速度。其实是为了提高应用程序的使用率。
程序的执行其实都是在抢CPU的资源,CPU的执行权。
多个进程是在抢这个资源,而其中的某一个进程如果执行路径比较多,就会有更高的几率抢到CPU的执行权。
我们是不敢保证哪一个线程能够在哪个时刻抢到,所以线程的执行有随机性。
五、 并行和并发。
前者是逻辑上同时发生,指在某一个时间内同时运行多个程序。
后者是物理上同时发生,指在某一个时间点同时运行多个程序。
六、 Java程序的运行原理及JVM的启动是多线程的吗?
A:Java命令会启动JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个 "主线程", 然后主线程去调用某个类的 main 方法。所以 main方法运行在主线程中。在此之前的所有程序都是单线程的。
B:JVM的启动是多线程的,因为它最低有两个线程启动了,主线程和垃圾回收线程。
七、 多线程的实现方案(自己补齐步骤及代码 掌握)
由于线程是依赖进程而存在的,所以我们应该先创建一个进程出来。而进程是由系统创建的,所以我们应该去调用系统功能创建一个进程。Java是不能直接调用系统功能的,所以,我们没有办法直接实现多线程程序。但是呢?Java可以去调用C/C++写好的程序来实现多线程程序。由C/C++去调用系统功能创建进程,然后由Java去调用这样的东西,然后提供一些类供我们使用。我们就可以实现多线程程序了。
那么Java提供的类是什么呢? Thread类
构造方法:
public Thread(Runnable target):分配新的 Thread 对象。
public Thread(Runnable target, String name):分配新的 Thread 对象。
成员方法:
public final String getName():返回该线程的名称。
public final void setName(String name):改变线程名称,使之与参数 name 相同。
public static Thread currentThread():返回对当前正在执行的线程对象的引用。
public final int getPriority():返回线程的优先级。
public final void setPriority(int newPriority):更改线程的优先级。
public static void sleep(long millis):在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),
public final void join():等待该线程终止。
public static void yield():暂停当前正在执行的线程对象,并执行其他线程。
public final void setDaemon(boolean on):将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。该方法必须在启动线程前调用。
public final void stop():强迫线程停止执行。过时了,但可以使用,不建议。
public void interrupt():中断线程。
public static boolean interrupted():测试当前线程是否已经中断。
通过查看API,我们知道了有两种方式实现多线程程序。
A:继承Thread类
步骤
1). 自定义类MyThread继承Thread类。
2). MyThread类里面重写run()?
面试题:run()和start()的区别?
run():仅仅是封装被线程执行的代码,直接调用是普通方法
start():首先启动了线程,然后再由jvm去调用该线程的run()方法。
为什么是run()方法呢?
不是类中的所有代码都需要被线程执行的。而这个时候,为了区分哪些代码能够被线程执行,java提供了Thread类中的run()用来包含那些被线程执行的代码。
3). 创建对象
4). 启动线程
/*******************************************************************************************************************************************/
public class MyThreadDemo {
public static void main(String[] args) {
// 创建线程对象,无参构造+setXxx()
// MyThread my1 = new MyThread();
// MyThread my2 = new MyThread();
// my1.setName("周润发");// 设置线程名称
// my2.setName("刘德华");
// my1.start();
// my2.start();
// my1.start();// IllegalThreadStateException:非法的线程状态异常。// 为什么呢?因为这个相当于是my1线程被调用了两次。而不是启动两个线程。
//带参构造方法给线程起名字
MyThread my1 = new MyThread("周润发AAA");
MyThread my2 = new MyThread("刘德华BBB");
MyThread my3 = new MyThread("梁朝伟CCC");
//更改线程优先级,默认是5
my1.setPriority(10);// 最大值,线程优先级高仅仅表示线程获取的 CPU时间片的几率高,但是要在次数比较多,或者多次运行的时候才能看到比较好的效果。
my2.setPriority(1);// 最小值
// 获取默认优先级
System.out.println(my1.getPriority());
System.out.println(my2.getPriority());
System.out.println(my3.getPriority());
//获取main方法所在的线程对象的名称
System.out.println(Thread.currentThread().getName());
my1.start();
try {
my1.join();// 等待该线程终止
} catch (InterruptedException e) {
e.printStackTrace();
}
my2.start();
my3.start();
}
}
public class MyThread extends Thread {
public MyThread() {}
public MyThread(String name){
super(name);
}
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(getName() + ":" + x + ",日期:" + new Date());
// try {
// Thread.sleep(1000);// 线程休眠
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
Thread.yield();// 暂停当前正在执行的线程对象,并执行其他线程。
}
}
}
/*******************************************************************************************************************************************/
public class ThreadDaemonDemo {
public static void main(String[] args) {
ThreadDaemon td1 = new ThreadDaemon();
ThreadDaemon td2 = new ThreadDaemon();
td1.setName("关羽");
td2.setName("张飞");
// 设置守护线程
td1.setDaemon(true);
td2.setDaemon(true);
td1.start();
td2.start();
Thread.currentThread().setName("刘备");
for (int x = 0; x < 5; x++) {
System.out.println(Thread.currentThread().getName() + ":" + x);
}
}
}
public class ThreadDaemon extends Thread {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(getName() + ":" + x);
}
}
}
/*******************************************************************************************************************************************/
/*
* public final void stop():让线程停止,过时了,但是还可以使用。
* public void interrupt():中断线程。 把线程的状态终止,并抛出一个InterruptedException。
*/
public class ThreadStopDemo {
public static void main(String[] args) {
ThreadStop ts = new ThreadStop();
ts.start();
// 你超过三秒不醒过来,我就干死你
try {
Thread.sleep(3000);
// ts.stop();
ts.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ThreadStop extends Thread {
@Override
public void run() {
System.out.println("开始执行:" + new Date());
// 我要休息10秒钟,亲,不要打扰我哦
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// e.printStackTrace();
System.out.println("线程被终止了");
}
System.out.println("结束执行:" + new Date());
}
}
/*******************************************************************************************************************************************/
B:实现Runnable接口
步骤:
1). 自定义类MyRunnable实现Runnable接口
2). 重写run()方法
3). 创建MyRunnable类的对象
4). 创建Thread类的对象,并把C步骤的对象作为构造参数传递
/*******************************************************************************************************************************************/
public class MyRunnableDemo {
public static void main(String[] args) {
// 创建MyRunnable类的对象
MyRunnable my = new MyRunnable();
// 创建Thread类的对象,并把C步骤的对象作为构造参数传递
// Thread(Runnable target)
// Thread t1 = new Thread(my);
// Thread t2 = new Thread(my);
// t1.setName("周润发AAA");
// t2.setName("刘德华BBB");
// Thread(Runnable target, String name)
Thread t1 = new Thread(my, "周润发AAA");
Thread t2 = new Thread(my, "刘德华BBB");
t1.start();
t2.start();
}
}
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
// 由于实现接口的方式就不能直接使用Thread类的方法了,但是可以间接的使用
System.out.println(Thread.currentThread().getName() + ":" + x);
}
}
}
/*******************************************************************************************************************************************/
八、 线程的调度和优先级问题
A:线程的调度
a:分时调度
b:抢占式调度 (Java采用的是该调度方式)
B:获取和设置线程优先级
a:默认是5
b:范围是1-10
九、 线程的控制(常见方法)
A:休眠线程
B:加入线程
C:礼让线程
D:后台线程
E:终止线程(掌握)
十、 线程的生命周期
A:新建
B:就绪
C:运行
D:阻塞
E:死亡
线程生命周期图解:
十一、 电影院卖票程序的实现
A:继承Thread类
B:实现Runnable接口
/*******************************************************************************************************************************************/
/*
* 某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。
* 继承Thread类来实现。
*/
public class SellTicketDemo {
public static void main(String[] args) {
// 创建三个线程对象
SellTicket st1 = new SellTicket();
SellTicket st2 = new SellTicket();
SellTicket st3 = new SellTicket();
// 给线程对象起名字
st1.setName("窗口1");
st2.setName("窗口2");
st3.setName("窗口3");
// 启动线程
st1.start();
st2.start();
st3.start();
}
}
public class SellTicket extends Thread {
// 定义100张票
// private int tickets = 1;
// 为了让多个线程对象共享这100张票,我们其实应该用静态修饰
private static int tickets = 1;
@Override
public void run() {
// 定义100张票
// 每个线程进来都会走这里,这样的话,每个线程对象相当于买的是自己的那100张票,这不合理,所以应该定义到外面
// int tickets = 100;
// 是为了模拟一直有票
while (true) {
if (tickets <= 100) {
try {
Thread.sleep(100);// 加入了休眠100毫秒,程序会有问题。
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + "正在出售第" + (tickets++) + "张票");
}
}
}
}
/*******************************************************************************************************************************************/
/*
* 实现Runnable接口的方式实现
*/
public class SellTicketDemo {
public static void main(String[] args) {
// 创建资源对象
SellTicket st = new SellTicket();
// 创建三个线程对象
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
// 启动线程
t1.start();
t2.start();
t3.start();
}
}
public class SellTicket implements Runnable {
// 定义100张票
private int tickets = 1;
@Override
public void run() {
while (true) {
if (tickets <= 100) {
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets++) + "张票");
}
}
}
}
/*******************************************************************************************************************************************/
十二、 电影院卖票程序出问题
A:为了更符合真实的场景,加入了休眠100毫秒。
B:卖票问题
a:同票多次
b:票数多了
public class SellTicketDemo {
public static void main(String[] args) {
// 创建资源对象
SellTicket st = new SellTicket();
// 创建三个线程对象
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
// 启动线程
t1.start();
t2.start();
t3.start();
}
}
public class SellTicket implements Runnable {
// 定义100张票
private int tickets = 1;
@Override
public void run() {
while (true) {
if (tickets <= 100) {
try {
Thread.sleep(100);// t1进来了并休息,t2进来了并休息,t3进来了并休息,
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets++) + "张票");
//窗口1正在出售第100张票,tickets=101
//窗口2正在出售第101张票,tickets=102
//窗口3正在出售第102张票,tickets=103
}
}
}
}
十三、 多线程安全问题的原因(也是我们以后判断一个程序是否有线程安全问题的依据)
A:是否有多线程环境
B:是否有共享数据
C:是否有多条语句操作共享数据
十四、 Java同步机制解决线程安全问题
同步的前提:
多个线程
解决问题的时候要注意:
个线程使用的是同一个锁对象
同步的好处
同步的出现解决了多线程的安全问题。
同步的弊端
当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
A:同步代码块
synchronized(对象) {
需要被同步的代码;
}
1). 对象是什么呢?
我们可以随便创建一个对象试试。
2). 需要同步的代码是哪些呢?
把多条语句操作共享数据的代码的部分给包起来
注意:
同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能。
这里的锁对象可以是任意对象。多个线程必须是同一把锁。
public class SellTicket implements Runnable {
// 定义100张票
private int tickets = 1;
// 创建锁对象,定义同一把锁。
private Object obj = new Object();
// @Override
// public void run() {
// while (true) {
// synchronized (new Object()) {// 不是同一把锁
// if (tickets <= 100) {
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets++) + "张票");
// }
// }
// }
// }
@Override
public void run() {
while (true) {
synchronized (obj) {
if (tickets <= 100) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets++) + "张票");
}
}
}
}
}
B:同步方法
把同步加在方法上。
这里的锁对象是this
此处缺少代码...
C:静态同步方法
把同步加在方法上。
这里的锁对象是当前类的字节码文件对象(反射再讲字节码文件对象)
此处缺少代码...
十五、 回顾以前的线程安全的类
A:StringBuffer
B:Vector
C:Hashtable
D:如何把一个线程不安全的集合类变成一个线程安全的集合类
用Collections工具类的方法即可。
public class ThreadDemo {
public static void main(String[] args) {
// 线程安全的类
StringBuffer sb = new StringBuffer();
Vector<String> v = new Vector<String>();
Hashtable<String, String> h = new Hashtable<String, String>();
// Vector是线程安全的时候才去考虑使用的,但是我还说过即使要安全,我也不用你
// 那么到底用谁呢?
// public static <T> List<T> synchronizedList(List<T> list)
List<String> list1 = new ArrayList<String>();// 线程不安全
List<String> list2 = Collections.synchronizedList(new ArrayList<String>()); // 线程安全
}
}