A.线程的概述和多线程的意义
l 多线程概述
• 进程:
• 正在运行的程序,是系统进行资源分配和调用的独立单位。
• 每一个进程都有它自己的内存空间和系统资源。
• 线程:
• 是进程中的单个顺序控制流,是一条执行路径
• 一个进程如果只有一条执行路径,则称为单线程程序。
• 一个进程如果有多条执行路径,则称为多线程程序。
1:要想了解多线程,必须先了解线程,而要想了解线程,必须先了解进程,因为线程是依赖于进程而存在。
2:什么是进程?
通过任务管理器我们就看到了进程的存在。
而通过观察,我们发现只有运行的程序才会出现进程。
进程:就是正在运行的程序。
进程是系统进行资源分配和调用的独立单位。每一个进程都有它自己的内存空间和系统资源。
3:多进程有什么意义呢?
单进程的计算机只能做一件事情,而我们现在的计算机都可以做多件事情。
举例:一边玩游戏(游戏进程),一边听音乐(音乐进程)。
也就是说现在的计算机都是支持多进程的,可以在一个时间段内执行多个任务。
并且呢,可以提高CPU的使用率。
问题:
一边玩游戏,一边听音乐是同时进行的吗?
不是。因为单CPU在某一个时间点上只能做一件事情。
而我们在玩游戏,或者听音乐的时候,是CPU在做着程序间的高效切换让我们觉得是同时进行的。
4:什么是线程呢?
在同一个进程内又可以执行多个任务,而这每一个任务我就可以看出是一个线程。
线程:是程序的执行单元,执行路径。是程序使用CPU的最基本单位。
单线程:如果程序只有一条执行路径。
多线程:如果程序有多条执行路径。
5:多线程有什么意义呢?
多线程的存在,不是提高程序的执行速度。其实是为了提高应用程序的使用率。
程序的执行其实都是在抢CPU的资源,CPU的执行权。
多个进程是在抢这个资源,而其中的某一个进程如果执行路径比较多,就会有更高的几率抢到CPU的执行权。
我们是不敢保证哪一个线程能够在哪个时刻抢到,所以线程的执行有随机性。
6.并行和并发
并行:逻辑上同时发生,指在某一个时间内同时运行多个程序。
并发:物理上同时发生,指在某一个时间点同时运行多个程序。
B.Java程序运行原理
l Java程序运行原理
• java 命令会启动java 虚拟机,启动JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个“主线程” ,然后主线程去调用某个类的 main 方法。所以main方法运行在主线程中。在此之前的所有程序都是单线程的。
• 思考:
• jvm虚拟机的启动是单线程的还是多线程的?
多线程。原因是垃圾回收线程也要先启动,否则很容易出现内存溢出。
现在的垃圾回收线程加上前面的主线程,最低启动了两个线程,所以,jvm的启动其实是多线程的。
C.如何实现多线程及多线程方式1的思路
方式1:
step1.写一个MyThread类继承Thread类,
step2.重写MyThread里面的run方法
step3.创建MyThread对象
step4.启动线程
问题:为什么要重写run方法呢?
因为不是类中所有的代码都需要被线程执行的。
而为了区分哪些代码被执行,java提供了Thread类中的run()用来包含那些被线程执行的代码。
package com.core.thread;
public class MyThread extends Thread {
public MyThread() {
super();
}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
//耗时操作
for (int i = 0; i < 10; i++) {
//获取线程的名称
System.out.println(getName()+"::"+i);
}
}
}
package com.core.thread;
public class Test {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
// 设置线程名称
t2.setName("线程t2");
Thread t3 = new MyThread("线程t3");
// 获得main函数的线程名称?
// 返回当前正在执行的线程对象
Thread currentThread = Thread.currentThread();
System.out.println("currentThread::" + currentThread.getName());
}
}
D.线程调度及获取和设置线程优先级
package com.core.thread;
public class Test {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
// 设置线程名称
t2.setName("线程t2");
Thread t3 = new MyThread("线程t3");
// 获得main函数的线程名称?
// 返回当前正在执行的线程对象
Thread currentThread = Thread.currentThread();
System.out.println("currentThread::" + currentThread.getName());
// • public final int getPriority()
// • public final void setPriority(int newPriority)
System.out.println(t1.getPriority());
System.out.println(t2.getPriority());
System.out.println(t3.getPriority());
t1.setPriority(10);
t2.setPriority(1);
t3.setPriority(1);
t1.start();
t2.start();
t3.start();
}
}
E.休眠线程
• 线程休眠
• public static void sleep(long millis)
package com.core.thread.demo1;
import java.util.Date;
public class SleepThread extends Thread {
public SleepThread() {
super();
}
public SleepThread(String name) {
super(name);
}
@Override
public void run() {
//耗时操作
for (int i = 0; i < 100; i++) {
//获取线程的名称
System.out.println(getName()+"--"+i+","+new Date());
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package com.core.thread.demo1;
public class Test {
public static void main(String[] args) {
SleepThread st1 = new SleepThread();
SleepThread st2 = new SleepThread("线程2");
st1.setName("线程1");
st1.start();
st2.start();
}
}
F.加入线程
• 线程加入
• public final void join()
等待线程结束package com.core.thread.demo1;
import java.util.Date;
public class JoinThread extends Thread {
public JoinThread() {
super();
}
public JoinThread(String name) {
super(name);
}
@Override
public void run() {
//耗时操作
for (int i = 0; i < 100; i++) {
//获取线程的名称
System.out.println(getName()+"--"+i+","+new Date());
}
}
}
测试类:
package com.core.thread.demo1;
public class Test {
// 线程加入
// public final void join()
public static void main(String[] args) {
JoinThread jt1 = new JoinThread("爸爸");
JoinThread jt2 = new JoinThread("大儿子");
JoinThread jt3 = new JoinThread("小儿子");
jt1.start();
try {
jt1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
jt2.start();
jt3.start();
}
}
G.礼让线程
• 线程礼让
• public static void yield()
package com.core.thread.demo3;
import java.util.Date;
public class YieldThread extends Thread {
public YieldThread() {
super();
}
public YieldThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + "--" + i);
Thread.yield();
}
}
}
测试类:
package com.core.thread.demo3;
public class Test {
//线程礼让,暂停当前正在执行的线程对象,并执行其他线程
//让多个线程的执行更协调,但是不能保证一人一次
public static void main(String[] args) {
YieldThread st1 = new YieldThread();
YieldThread st2 = new YieldThread("线程2");
st1.setName("线程1");
st1.start();
st2.start();
}
}
H.守护线程
public final void setDaemon(boolean on)
将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。
该方法必须在启动线程前调用。
package com.core.thread.demo4;
import java.util.Date;
public class DaemonThread extends Thread {
public DaemonThread() {
super();
}
public DaemonThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + "--" + i);
}
}
}
测试类:
package com.core.thread.demo4;
public class Test {
// public final void setDaemon(boolean on)
// 将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。
// 该方法必须在启动线程前调用
public static void main(String[] args) {
DaemonThread dt1 = new DaemonThread();
DaemonThread dt2 = new DaemonThread("关羽");
dt1.setName("张飞");
// 设置dt1和dt2为守护线程,必须放在启动线程之前
dt1.setDaemon(true);
dt2.setDaemon(true);
dt1.start();
dt2.start();
Thread.currentThread().setName("刘备");
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "," + i);
}
}
}
I .中断线程
package com.core.thread.demo5;
import java.util.Date;
public class StopThread extends Thread {
public StopThread() {
super();
}
public StopThread(String name) {
super(name);
}
@Override
public void run() {
long currentTimeMillis = System.currentTimeMillis();
Date date = new Date(currentTimeMillis);
System.out.println("开始时间:" + date);
try {
sleep(5000);
} catch (InterruptedException e) {
// e.printStackTrace();
System.out.println("线程被终止");
}
currentTimeMillis = System.currentTimeMillis();
date = new Date(currentTimeMillis);
System.out.println("结束时间:" + date);
}
}
测试类:
package com.core.thread.demo5;
public class Test {
public static void main(String[] args) {
StopThread st1 = new StopThread("线程1");
st1.start();
try {
Thread.currentThread().sleep(3000);
// st1.stop();
st1.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
注意在终止线程的时候,stop和interrupt的区别。
J.线程生命周期图解
K.多线程方式2的思路及代码实现
l 实现Runnable接口
• 如何获取线程名称
• 如何给线程设置名称
l 实现接口方式的好处
• 可以避免由于Java单继承带来的局限性。
• 适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,数据有效分离,较好的体现了面向对象的设计思想。
package com.core.thread.demo6;
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+","+i);
}
}
}
测试类:
package com.core.thread.demo6;
public class Test {
// 多线程的方式2
// a.写一个MyRunnable类继承Runnable接口
// b.重写run()方法
// c.创建MyRunnable对象
// d.创建Thread对象,并把c步骤创建的MyRunnable对象作为构造参数传递
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread t1 = new Thread(runnable, "线程1");
Thread t2 = new Thread(runnable, "线程2");
t1.start();
t2.start();
}
}
L.多线程两种方式的图解比较及区别
M.线程安全
用多线程来实现,两种方法。
第一种,继承Thread类,重写run方法。
第二种,实现Runnable接口。
先用第一种方法:
package com.core.thread.demo7;
public class SellTicket extends Thread {
public static int ticketNum = 100;
public SellTicket(String name){
super(name);
}
@Override
public void run() {
// while(ticketNum>0){
//
// }
while(true){
if(ticketNum >0){
System.out.println(Thread.currentThread().getName()+"正在出售"+ticketNum+"张票");
ticketNum--;
}
}
}
}
测试类:
package com.core.thread.demo7;
//某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票
public class Test {
public static void main(String[] args) {
SellTicket st1 = new SellTicket("窗口1");
SellTicket st2 = new SellTicket("窗口2");
st1.start();
st2.start();
}
}
通过打印可以看出,这个是存在线程安全的!暂时先放一下,稍后解决。
接下来,使用实现Runnable接口的方式。
package com.core.thread.demo8;
public class SellTicket implements Runnable {
private static int ticketNum = 100;
@Override
public void run() {
while(true){
if(ticketNum >0){
try {
Thread.currentThread().sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售"+ticketNum+"张票");
ticketNum--;
}
}
}
}
package com.core.thread.demo8;
//某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票
//实现Runnable接口
public class Test {
public static void main(String[] args) {
SellTicket st = new SellTicket();
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
t1.start();
t2.start();
}
}
注意:为了演示方便,我在售票的时候,睡眠了一段时间。从打印的数据来看,还是存在线程安全的问题。
N.出现了同票和负数票的原因分析
l 问题
• 相同的票出现多次
• CPU的一次操作必须是原子性的
• 还出现了负数的票
• 随机性和延迟导致的
l 注意
• 线程安全问题在理想状态下,不容易出现,但一旦出现对软件的影响是非常大的。
O.线程安全问题的产生原因分析
l 首先想为什么出现问题?(也是我们判断是否有问题的标准)
• 是否是多线程环境
• 是否有共享数据
• 是否有多条语句操作共享数据
l 如何解决多线程安全问题呢?
• 基本思想:让程序没有安全问题的环境。
• 怎么实现呢?
• 把多个语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可。
P.使用同步代码块解决线程安全问题
package com.core.thread.demo8;
public class SellTicket implements Runnable {
private static int ticketNum = 100;
//创建锁对象
private Object obj = new Object();
@Override
public void run() {
while (true) {
synchronized (obj) {
if (ticketNum > 0) {
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售"
+ ticketNum + "张票");
ticketNum--;
}
}
}
}
}
package com.core.thread.demo8;
//某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票
//实现Runnable接口
public class Test {
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();
}
}
Q.同步代码块的特点和优缺点
l 同步的前提
• 多个线程
• 多个线程使用的是同一个锁对象
l 同步的好处
• 同步的出现解决了多线程的安全问题。
l 同步的弊端
• 当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
R.同步方法
package com.core.thread.demo10;
public class SellTicket implements Runnable {
private static int ticketNum = 100;
@Override
public void run() {
while(true){
sellTicket();
}
}
private synchronized void sellTicket() {
if(ticketNum >0){
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售"+ticketNum+"张票");
ticketNum--;
}
}
}
S.关于使用同步方法锁的问题
package com.core.thread.demo11;
public class SellTicket implements Runnable {
private static int ticketNum = 100;
private Object obj = new Object();
private Test t = new Test();
@Override
public void run() {
while (true) {
// 使用Object类为锁
/* synchronized (obj) {
if (ticketNum > 0) {
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售" + ticketNum + "张票");
ticketNum--;
}
}
// 使用任意类为锁
synchronized (t) {
if (ticketNum > 0) {
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售" + ticketNum + "张票");
ticketNum--;
}
}
*/
// 使用字节码类为锁
synchronized (SellTicket.this) {
if (ticketNum > 0) {
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售" + ticketNum + "张票");
ticketNum--;
}
}
}
}
private synchronized void sellTicket() {
if (ticketNum > 0) {
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售"
+ ticketNum + "张票");
ticketNum--;
}
}
}
再回过来,看同步方法到底是使用哪一个锁呢?
package com.core.thread.demo12;
public class SellTicket implements Runnable {
private static int ticketNum = 100;
private Object obj = new Object();
private Test t = new Test();
private int x = 1;
@Override
public void run() {
while (true) {
if (x % 2 == 1) {
synchronized (this) {
// 这里注意了,如果是SellTicke.class 设为锁,则会出现售0票的现象。
// 但是如果要是this,或者SellTicket.this 设为锁,则售票正常[可以看下面的例子,sellTicket为静态方法]
if (ticketNum > 0) {
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售" + ticketNum + "张票");
ticketNum--;
}
}
} else {
sellTicket();
}
x++;
}
}
private synchronized void sellTicket() {
if (ticketNum > 0) {
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售"
+ ticketNum + "张票");
ticketNum--;
}
}
}
静态方法发锁:
package com.core.thread.demo12;
//某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票
//实现Runnable接口
public class Test {
// 小结:
// A:同步代码块上的锁对象是谁?
// 任意对象。
// B:同步方法是锁是谁?
// this
// C:静态方法的锁对象是谁?
// 类的字节码文件
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();
}
}
小结:
A:同步代码块上的锁对象是谁?
任意对象。
B:同步方法是锁是谁?
this
C:静态方法的锁对象是谁?
类的字节码文件对象
T.回顾以前的线程安全的类
package com.core.thread.demo13;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Hashtable;
import java.util.List;
import java.util.Vector;
public class Test {
public static void main(String[] args) {
// 回顾以前讲过的线程安全的类
StringBuffer sb = new StringBuffer();
Vector<Object> vector = new Vector<>();
Hashtable<Object, Object> hashtable = new Hashtable<>();
ArrayList<Object> list = new ArrayList<>();// 线程不安全。
// Collections类中给我提供一个方法
// synchronizedList
// public static <T> List<T> synchronizedList(List<T> list)
// 返回指定列表支持的同步(线程安全的)列表。为了保证按顺序访问,必须通过返回的列表完成所有对底层实现列表的访问。
List<Object> list2 = Collections.synchronizedList(list);//线程安全。
}
}