多线程
基本概念:程序 进程 线程
程序(program):为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
进程(process):程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。——生命周期
进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。
若一个进程同一时间并行执行多个线程,就是支持多线程的
线程是调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小
一个进程中的多个线程共享相同的内存单元/内存地址空间—》它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患。
线程的创建和启动
Thread类的特性
每个线程都是通过某个特定Thread对象的run()方法来完成操作的,经常把run()方法的主体称为线程体
通过该Thread对象的start()方法来启动这个线程,而非直接调用run()
创建线程的两种方式
JDK1.5之前创建新执行线程有两种方法:
继承Thread类的方式
实现Runnable接口的方式
创建多线程的方式一:继承Thread类
/**
* 多线程的创建,方式一:继承于Thread类
* 1.创建一个继承于Thread类的子类
* 2.重写Thread的run()方法 ---> 将此线程的方法声明在run()中
* 3.创建Thread类的子对象
* 4.通过此对象调用start()
*
* 例子:遍历100以内的所有的偶数
*/
//1.创建一个继承于Thread类的子类
class MyThread extends Thread{
//重写Thread类的run()
@Override
public void run() {
for(int i = 1;i < 100;i++){
if(i % 2 == 0){
System.out.println(i);
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
//3.创建Thread类的子对象
MyThread t1 = new MyThread();
//4.通过此对象调用start():①启动当前线程 ②调用当前线程的run()
t1.start();
//如下操作仍在main线程中执行的
for(int i = 1;i < 100;i++){
if(i % 2 == 0){
System.out.println(i + "***main()***");
}
}
}
}
创建多线程的方式二:实现Runnable接口
/**
* 创建多线程的方式二:实现Runnable接口
* 1.创建一个实现了Runnable接口得类
* 2.实现类去实现Runnable中的抽象方法:run()
* 3.创建实现类的对象
* 4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
* 5.通过Thread类的对象调用start()
*/
//1.创建一个实现了Runnable接口得类
class MThread implements Runnable{
//2.实现类去实现Runnable中的抽象方法:run()
@Override
public void run() {
for(int i = 0;i < 100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class ThreadTest1 {
public static void main(String[] args) {
//3.创建实现类的对象
MThread m1 = new MThread();
//4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
Thread t1 = new Thread(m1);
//5.通过Thread类的对象调用start():①启动线程 ②调用当前线程的run() --> 调用了Runnable类型的target的run()
t1.start();
//再启动一个线程,遍历100以内的偶数
Thread t2 = new Thread(m1);
t2.setName("线程2");
t2.start();
}
}
继承方式和实现方式的联系与区别
比较创建线程的两种方式。
开发中:优先选择:实现Runnable接口的方式
原因:1. 实现的方式没有类的单继承性的局限性
2. 实现的方式更适合来处理多个线程有共享数据的情况。
联系:public class Thread implements Runnable
相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。
线程的生命周期
新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能
阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态
死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束
代理设计模式
代理设计模式
* 代理核心思想:
* 真实角色专注自己的事情(开发中,针对自己的业务)
* 代理角色帮助真实完成一件事情
*
* 静态代理
* 代理角色和真实角色要实现同一个接口
*
* 动态代理:后面讲
* jdk动态代理
* cglib动态代理(需要导入cglib.jar包)
*
*第二种方式实现:
* //class MyRunnable implements Runnable{}
* MyRunnable my = new MyRunnable() ; ---->真实角色
* Thread t1 = new Thread(my,"t1") ; ---->Thread代理角色
*
*/
//结婚接口
interface Mary{
void mary() ;
}
//每一个人都需要结婚
//真实角色
class You implements Mary{
@Override
public void mary() {
System.out.println("要结婚了很开心...");
}
}
//代理角色---->婚庆公司
class WeddingCompany implements Mary{
//声明接口
private Mary mary ;
//有一个有参构造方法
public WeddingCompany(Mary mary){
//需要接口子实现类 You
this.mary = mary ;
}
@Override
public void mary() {
System.out.println("结婚之前,混穷公司布置婚礼现场!");
if(mary!=null){
mary.mary(); //核心的方法(真实角色要实现自己的事情)
}
System.out.println("婚礼现场完成之后,给婚庆公司付尾款!");
}
}
//测试类
public class StaticProxyDemo {
public static void main(String[] args) {
//创建You类对象(多态/自己new 自己)
You you = new You() ;
you.mary();
System.out.println("------------------使用静态代理-------------------------");
//创建资源类对---->真实角色
You you2 = new You() ;
//创建代理角色---->婚庆公司
WeddingCompany wc = new WeddingCompany(you2) ;
wc.mary();
}
}
继承类线程安全问题
/**
* 解决线程安全
*
* 校验多线程安全问题的标准 (使用标准来看多线程环境是否存在问题,以及解决方案)
* 1)是否是多线程环境 --->是
* 2)是否有共享数据 ---> 是存在的
* 3)是否有多条语句对共享数据操作
* tickets票:多条语句同时操作
* 将3)多条语句多共享数据的操作使用同步代码块包起来---解决线程安全问题
* synchronized(锁对象){
* 多条语句对共享数据操作
* }
*
* 锁对象:可以是任意Java类对象,但是多个线程必须使用的同一个锁对象,否则"锁不住"!
*
* 什么是同步方法(非静态)?如果一个方法中第一句话是一个同步代码块,可以将synchronized关键字定义在声明上
* 权限修饰符 synchronized 返回值类型 方法名(参数列表){
* ...
* }
* 锁对象:this---代表类对象的地址值引用
* 如果是静态的同步方法,锁对象---->当前类名.class(字节码文件对象)
*
*/
public class SellTicketTest {
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() ;
}
}
/**
* @author www
* @date 2022/11/21 11:12
*/
//真实角色
class Demo{
}
public class SellTicket implements Runnable {
//100张票
private static int tickets = 100 ;
private Object obj = new Object() ; //实例变量
private Demo demo = new Demo() ;
private int x = 0 ;
@Override
public void run() {
//模拟一直有票
while (true){
//t1,t2,,t3
try {
Thread.sleep(1000);//模拟网络延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
// synchronized(锁对象){
// * 多条语句对共享数据操作
// * }
//t1/t2/t3
//synchronized (new Object()){ //锁对象 ,每一个线程进来使用自己的锁对象!
//t1如果先抢占cpu执行权了
if(x % 2==0){
// synchronized (demo){ //t1,t2,t3这个三个线程使用的同一个锁
//synchronized (this){ //t1,t2,t3这个三个线程使用的同一个锁
synchronized(SellTicket.class){
if(tickets>0){
System.out.println(Thread.currentThread().getName()+
"正在出售第"+(tickets--)+"张票");
}
}
}else{
//x%2 !=0
/* synchronized (demo){ //t1,t2,t3这个三个线程使用的同一个锁
if(tickets>0){
System.out.println(Thread.currentThread().getName()+
"正在出售第"+(tickets--)+"张票");
}
}*/
SellTicket.sellTicket();
}
x++;
}
}
//定义一个方法sellTicket
/**
* 什么是同步方法(非静态)?如果一个方法中第一句话是一个同步代码块,可以将synchronized关键字定义在声明上
* 权限修饰符 synchronized 返回值类型 方法名(参数列表){
* ...
* }
*/
/* public synchronized void sellTicket(){ //锁对象是谁? --->this
//t1,t2,t3这个三个线程使用的同一个锁
if(tickets>0){
System.out.println(Thread.currentThread().getName()+
"正在出售第"+(tickets--)+"张票");
}
}*/
public static synchronized void sellTicket(){
//静态的同步方法的锁跟类相关---->当期类的字节码文件对象 类名.class
//t1,t2,t3这个三个线程使用的同一个锁
if(tickets>0){
System.out.println(Thread.currentThread().getName()+
"正在出售第"+(tickets--)+"张票");
}
}
}
同步方法处理实现Runnable的线程安全问题
/**
* 使用同步方法解决实现Runnable接口的线程安全问题
*
* 关于同步方法的总结:
* 1. 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
* 2. 非静态的同步方法,同步监视器是:this
* 静态的同步方法,同步监视器是:当前类本身
*/
class Windows3 implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (true) {
show();
}
}
public synchronized void show() {
//同步监视器:this
// synchronized (this){
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为: " + ticket);
ticket--;
}
// }
}
}
public class WindowsTest3 {
public static void main(String[] args) {
Windows3 w3 = new Windows3();
Thread t1 = new Thread(w3);
Thread t2 = new Thread(w3);
Thread t3 = new Thread(w3);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
同步方法处理继承Thread类的线程安全问题
/**
* 使用同步方法处理继承Thread类的方式中的线程安全问题
*/
class Windows4 extends Thread {
private static int ticket = 100;
@Override
public void run() {
while (true) {
show();
}
}
private static synchronized void show(){
//同步监视器:Window4.class
//private synchronized void show(){ //同步监视器:t1,t2,t3。此种解决方式是错误的
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--;
}
}
}
public class WindowsTest4 {
public static void main(String[] args) {
Windows4 t1 = new Windows4();
Windows4 t2 = new Windows4();
Windows4 t3 = new Windows4();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
死锁问题
/**
* 演示线程的死锁
*
* 1.死锁的理解:不同的线程分别占用对方需要的同步资源不放弃,
* 都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
* 2.说明:
* 》出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
* 》我们使用同步时,要避免出现死锁。
*/
public class ThreadTest {
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
new Thread(){
@Override
public void run() {
synchronized (s1){
s1.append("a");
s2.append("1");
try {
Thread.