多线程笔记
1、创建多线程的两种方法
(1)通过创建Thread的子类实现多线程
创建多线程步骤:
① 将一个类MyThread
声明为Thread
的子类。 这个子类应该重写Thread
类的方法run()
。
② 在run()
方法中编写多线程想要执行的方法
③ 创建MyThread
的实例对象,使用实例对象的start()
方法执行线程
void start(): 导致此线程开始执行; Java虚拟机调用此线程的run方法。
代码如下:
package com.java.myThread;
/**
* @Description 该类实现Thread类,为线程类
* @Author yc
* @Version V1.0.0
* @Date 2021/8/1
*/
public class MyThread extends Thread{
@Override
public void run() {
//run()方法中编写多线程想要执行的代码
for (int i = 0;i < 100; i++){
System.out.println(i);
}
}
}
package com.java.myThread;
/**
* @Description 该类展示通过实现Thread来实现多线程
* @Author yc
* @Version V1.0.0
* @Date 2021/8/1
*/
public class MyThreadTest01 {
public static void main(String[] args) {
//创建线程1
MyThread myThread01 = new MyThread();
//创建线程2
MyThread myThread02 = new MyThread();
//使用start()方法启动线程
//void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法。
myThread01.start();
myThread02.start();
}
}
(2)通过继承Runnable接口实现多线程
创建多线程步骤:
① 创建一个类MyRunnable
继承Runnable
接口。 这个类需要run()
。
② 在run()
方法中编写多线程想要执行的方法
③ 通过Thread
的构造函数创建MyRunnable
的实例对象,使用实例对象的start()
方法执行线程
package com.java.myThread;
/**
* @Description 该类继承了Runnable接口
* @Author yc
* @Version V1.0.0
* @Date 2021/8/1
*/
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.java.myThread;
/**
* @Description
* @Author yc
* @Version V1.0.0
* @Date 2021/8/1
*/
public class MyRunnableTest {
public static void main(String[] args) {
//创建MyRunnable对象实例
MyRunnable myRunnable = new MyRunnable();
//创建线程
Thread thread01 = new Thread(myRunnable,"线程1");
Thread thread02 = new Thread(myRunnable,"线程2");
//启动线程
thread01.start();
thread02.start();
}
}
相比继承Thread类,实现Runnable接口的好处:
① 避免了JAVA单继承的局限性
② 适合多个相同程序的代码去处理同一个资源的情况,把线程和程序的代码、数据有效分离,较好的体现了面对对象的设计思想
2、设置与获取线程名称
(1)设置线程名称
①通过setName()
方法对线程名称进行设置
public final void setName(String name)
:将此线程的名称更改为等于参数name 。
//创建线程1
MyThread myThread01 = new MyThread();
//将myThread01线程名称设置为线程1
myThread01.setName("线程1");
//创建线程2
MyThread myThread02 = new MyThread();
//将myThread02线程名称设置为线程2
myThread02.setName("线程2");
②通过Thread(String name)
构造方法对线程名称进行设置
Thread(String name)
:分配一个新的 Thread对象。
//创建线程1 名称设置为线程1
MyThread myThread01 = new MyThread("线程1");
//创建线程2 名称设置为线程2
MyThread myThread02 = new MyThread("线程2");
使用构造方法设置线程名称时,需要注意,在MyThread
类中必须重写Thread类的Thread(String name)
方法
//重写构造方法
public MyThread(String name) {
super(name);
}
(2)获取线程名称
通过getName()方法获取线程名称
String getName()
:返回此线程的名称。
public void run() {
//run()方法中编写多线程想要执行的代码
for (int i = 0;i < 100; i++){
//getName()获取线程名称
System.out.println(getName()+":"+i);
}
}
如果想要获取main()
方法所在的线程名称,可通过currentThread()
方法获取线程名称
public static Thread currentThread()
返回对当前正在执行的线程对象的引用。
public static void main(String[] args) {
//输出main()方法对应的线程名称
System.out.println(Thread.currentThread().getName()); //输出结果为main
}
3、线程的优先级
(1)线程调度模型
线程有两种调度模型:
① 分时调度模型 : 所有的线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片
② 抢占式调度模型 : 优先让优先级高的线程占用CPU,如果优先级相同,那么会随机选择一个,优先级高的线程获取的CPU时间片相对多一些
在JAVA中使用的是抢占式调度模型
(2)优先级相关方法
①获取线程的优先级
int getPriority()
: 返回此线程的优先级。
②设置线程的优先级
void setPriority(int newPriority)
:更改此线程的优先级。
package com.java.myThread;
/**
* @Description
* @Author yc
* @Version V1.0.0
* @Date 2021/8/1
*/
public class MyThreadPriority {
public static void main(String[] args) {
//创建三个线程
MyThread myThread01 = new MyThread("线程1");
MyThread myThread02 = new MyThread("线程2");
MyThread myThread03 = new MyThread("线程3");
//获取三个线程的优先级
System.out.println(myThread01.getPriority()); //5
System.out.println(myThread02.getPriority()); //5
System.out.println(myThread02.getPriority()); //5
//设置三个线程的优先级
myThread01.setPriority(5);
myThread02.setPriority(10);
myThread03.setPriority(1);
//启动三个线程
myThread01.start();
myThread02.start();
myThread03.start();
}
}
在设置线程优先级时,需要注意可以设置优先优先级的范围为1-10
/**
* 优先级最小值
*/
public static final int MIN_PRIORITY = 1;
/**
* 优先级默认值
*/
public static final int NORM_PRIORITY = 5;
/**
* 优先级最大值
*/
public static final int MAX_PRIORITY = 10;
4、线程控制
①public static void sleep(long millis) throws InterruptedException
使当前正在执行的线程以指定的 毫秒数 暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。 线程不会丢失任何显示器的所有权
@Override
public void run() {
//run()方法中编写多线程想要执行的代码
for (int i = 0;i < 100; i++){
System.out.println(getName()+":"+i);
try {
//当线程执行到此处时,会睡眠1000毫秒
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
②public final void join() throws InterruptedException
等待这个线程死亡。
package com.java.myThread;
/**
* @Description 该类用与演示join方法
* @Author yc
* @Version V1.0.0
* @Date 2021/8/1
*/
public class MyThreadJoin {
public static void main(String[] args) throws InterruptedException {
//创建三个线程
MyThread myThread01 = new MyThread("线程1");
MyThread myThread02 = new MyThread("线程2");
MyThread myThread03 = new MyThread("线程3");
//启动三个线程
myThread01.start();
//调用join()方法,调用后,只有线程1执行完后,才能执行其他线程
myThread01.join();
myThread02.start();
myThread03.start();
}
}
③public final void setDaemon(boolean on)
将此线程标记为daemon线程或用户线程。 当运行的唯一线程都是守护进程线程时,Java虚拟机将退出。
线程启动前必须调用此方法。
package com.java.myThread;
/**
* @Description
* @Author yc
* @Version V1.0.0
* @Date 2021/8/1
*/
public class MyThreadDaemon {
public static void main(String[] args) {
//创建线程
MyThread myThread01 = new MyThread("线程1");
MyThread myThread02 = new MyThread("线程2");
//将线程1和线程2设置为守护线程,当主线程跑完,只剩下守护线程时会退出JAVA虚拟机
myThread01.setDaemon(true);
myThread02.setDaemon(true);
//启动线程
myThread01.start();
myThread02.start();
}
}
5、线程的生命周期
6、卖票案例
(1)判断多线程程序是否存在数据安全问题的标准
① 是否为多线程环境
② 是否存在共享数据
③ 是否存在多条语句对共享数据进行操作
如何解决多线程数据安全问题 : 破坏以上三个条件中的一个即可
如何实现 : 给操作共享数据的代码锁起来,让任意时刻只能有一个线程执行,java提供了同步代码块的方式
(2)同步代码块
锁多条语句操作共享数据,可以通过使用同步代码块实现
格式:
synchronized(任意对象){
多条语句操作共享数据的代码
}
synchronized(任意对象) : 就相当于给代码加锁了,任意对象就可以看出一把锁
同步的好处和弊端:
好处 : 解决了多线程的数据安全问题
弊端 :当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率
卖票案例代码:
package com.java.caseStudy;
/**
* @Description
* @Author yc
* @Version V1.0.0
* @Date 2021/8/1
*/
@SuppressWarnings("all")
public class CellTicket implements Runnable {
//共享资源 总共100张票
private int tickets = 100;
private Object object = new Object();
@Override
public void run() {
while (true) {
//给操作共享资源的语句加上锁 锁为object变量
synchronized (object) {
//如果还有票则进行出售
if (tickets > 0) {
try {
//模拟买票操作
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在买第" + tickets + "张票");
//买了一张票,票需要减一
tickets--;
}
}
}
}
}
package com.java.caseStudy;
/**
* @Description
* @Author yc
* @Version V1.0.0
* @Date 2021/8/1
*/
public class CellTicketTest {
public static void main(String[] args) {
CellTicket cellTicket = new CellTicket();
//创建三个线程,模拟三个窗口
Thread thread01 = new Thread(cellTicket,"窗口1");
Thread thread02 = new Thread(cellTicket,"窗口2");
Thread thread03 = new Thread(cellTicket,"窗口3");
//启动线程
thread01.start();
thread02.start();
thread03.start();
}
}
(3)同步方法
同步方法:就是把synchronized
关键字加到方法上
格式: 修饰符 synchronized 返回值类型 方法名(方法参数){ }
同步方法的锁对象: this
package com.java.caseStudy;
/**
* @Description
* @Author yc
* @Version V1.0.0
* @Date 2021/8/1
*/
@SuppressWarnings("all")
public class CellTicket implements Runnable {
//共享资源 总共100张票
private int tickets = 100;
private Object object = new Object();
@Override
public void run() {
while (true) {
//同步方法处理数据安全问题
cellTicket();
}
}
//同步方法
private synchronized void cellTicket() {
if (tickets > 0) {
try {
//模拟买票操作
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在买第" + tickets + "张票");
//买了一张票,票需要减一
tickets--;
}
}
}
(4)同步静态方法
同步静态方法:就是把synchronized
关键字加到静态方法上
格式 : 修饰符 static synchronized 返回值类型 方法名(方法参数) { }
同步静态方法的锁对象: 类名.class
package com.java.caseStudy;
/**
* @Description
* @Author yc
* @Version V1.0.0
* @Date 2021/8/1
*/
@SuppressWarnings("all")
public class CellTicket implements Runnable {
//共享资源 总共100张票
private static int tickets = 100;
private Object object = new Object();
@Override
public void run() {
while (true) {
//同步静态方法处理数据安全问题
cellTicket();
}
}
//同步静态方法
private static synchronized void cellTicket() {
if (tickets > 0) {
try {
//模拟买票操作
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在买第" + tickets + "张票");
//买了一张票,票需要减一
tickets--;
}
}
}
(5)Lock锁
虽然我们可以理解同步代码和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更加清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
Lock
实现提供比使用synchronized
方法和语句可以获得更广泛的锁定操作
Lock
提供了获得锁和释放锁的方法
void lock()
: 获得锁
void unlock()
: 释放锁
Lock
是接口不能直接实例化,这里采用它的实现类ReentrantLock
来实例化
ReentrantLock
的构造方法 : ReentrantLock()
7、线程安全的类
StringBuffer
① 线程安全,可变的序列
② 从版本5开始,被StringBuilder
替代。通常应该使用StringBuilder
类,因为它支持所有相同的操作,但它更快,因为它不执行同步
Vector
从Java2平台v1.2开始,该类改进了List
接口,使其成为Java Collections Framework
的成员。与新的集合实现不同,Vector
被同步,如果不需要线程安全的实现,建议使用ArrayList
代替Vector
Hashtable
① 该类实现了一个哈希表,它将键映射到值。任何非null对象都可以用作键或者值
② 从Java2平台v1.2开始,该类进行了改进,实现了Map
接口,使其成为Java Collections Framework
的成员。与新的集合实现不同,Hashtable被同步,如果不需要线程安全的实现,建议使用HashMap
代替Hashtable
如果想使用线程安全的集合,除了Vector
和Hashtable
外,还可以使用Collections
的synchronizedCollection(Collection<T> c)
、synchronizedList(List<T> list)
等方法