Java基础21 多线程线程两种实现方式 锁

一、多线程的概念

1.程序 :一个固定逻辑与数据的集合 就称为程序 例如淘宝 贪吃蛇小游戏
2.CPU: 中央处理器 主要用于协调程序与硬件进行配置的工作
3.并发与并行
1.并发(高并发)
在同一个时间段 执行两个或者多个任务 单核cpu 是进行相互切换 执行某一个任务 切换速度是非常快 很容易误解是同时执行的 其实是交替执行
2.并行
在同一时刻 执行两个或者是多个任务的时候 多核cpu是同时执行多个任务 同时执行 目前的电脑都是使用多核
例子: 可以听歌 敲代码 看视频
在这里插入图片描述
1.进程: 运行在内存中的程序 就是进程
2.线程:单线程 多线程
线程是进程中执行运算的最小单位,也称为轻量级进程
单线程 执行一条通向cpu 的执行的路径 就是单线程
多线程 有多条通向cpu的执行流程 就称为多线程

3.单线程
在这里插入图片描述
4.多线程 迅雷下载小电影(同时下载) 360 安全卫士 杀毒 清理 优化系统
在这里插入图片描述
5.Java中的main方法 主程序 就是单线程 主线程
在这里插入图片描述

二、第一种方式实现多线程(子类继承Thread)

1.步骤
(1)定义一个类 继承Thread
(2) 重写 run方法 ==> 线程执行的操作
(3)实例化这个类对象 创建线程
(4)调用start() 开启线程
2.代码
线程类
getClass()获取当前所属包名+类名,getName()获取属性名/变量名

package day21;

public class MyThread extends Thread{
   @Override
    public void run(){
        //线程执行的操作
        for (int i = 0; i < 100; i++) {
            //getClass()获取当前所属包名+类名,getName()获取属性名/变量名
            System.out.println(getName()+"\t"+i);
            
        }
    }
}

测试类

package day21;

public class Test01 {
    public static void main(String[] args) {
        //实例化线程对象
        MyThread th = new MyThread();
        //开启线程 控制台输出值是 Thread-0	0
        th.start();
        for ( int i=1;i<=100;i++){
            //控制台输出值是 main 1
            System.out.println(Thread.currentThread().getName()+"\t"+ i);

        }
    }
}

三、线程的调度方式

1.线程有两种调度分配方式: 分配时调度 抢占式调度
2.分配时调度:按照固定的时间来分配每个线程执行操作 多个线程执行操作的时间都是固定
3.抢占式调度 线程获取cpu的执行权越大 执行当前线程的优先级越大
任意一个线程抢到cpu的执行权 就执行当前线程的操作 Java中的多线程就是抢占式调度
4.代码

package day21;

public class Test01 {
    public static void main(String[] args) {
        //实例化线程对象
        MyThread th = new MyThread();
        //开启线程 控制台输出值是 Thread-0	0
        th.start();
        for ( int i=1;i<=100;i++){
            //控制台输出值是 main 1
            System.out.println(Thread.currentThread().getName()+"\t"+ i);

        }

       //实例化第二个对象 控制台输出值是 Thread-1	0
        MyThread th1 = new MyThread();
        th1.start();
    }
}

5.原因的分析
在这里插入图片描述
线程谁抢到就执行谁

四、多线程的内存分析(start和run方法区别)

在这里插入图片描述
代码从上到下运行 ,当线程需要调用start()方法时,会新开辟一个栈内存,该方法进这个栈内存运行,每个线程都会开一个单独的栈内存
当线程调用run()方法时,不会新开辟一个栈,而是进main方法的所在栈,相当于普通对象调用方法

五、获取线程的名称

在这里插入图片描述

package day21;

public class Test01 {
    public static void main(String[] args) {
        //实例化线程对象
        MyThread th = new MyThread();
        //开启线程 控制台输出值是 Thread-0	0
        th.start();
        for ( int i=1;i<=100;i++){
            //控制台输出值是 main 1
            System.out.println(Thread.currentThread().getName()+"\t"+ i);

        }


注意点:
默认获取到线程的名称:Thread-XXX ==>序号时从0开始

设置标题的名称

在这里插入图片描述

package day21;

public class MyThread extends Thread{
    //无参构造

    public MyThread() {
    }
    //有参构造
    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run(){
        //线程执行的操作
        for (int i = 0; i < 100; i++) {
            //getClass()获取当前所属包名+类名,getName()获取属性名/变量名
            System.out.println(getName()+"\t"+i);
            //间隔1秒钟来打印即1000毫秒
            /*try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }*/
        }
    }
}

package day21;

public class Test02 {
    public static void main(String[] args) {
        //使用第一种方式 通过setName
        MyThread th1 = new MyThread();
        th1.setName("线程1");
        th1.start();
        //通过第二种方式来进行设置
        MyThread th2 = new MyThread("线程2");
        th2.start();
    }
}

六、线程休眠

在这里插入图片描述

package day21;

public class MyThread extends Thread{
    //无参构造

    public MyThread() {
    }
    //有参构造
    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run(){
        //线程执行的操作
        for (int i = 0; i < 100; i++) {
            //getClass()获取当前所属包名+类名,getName()获取属性名/变量名
            System.out.println(getName()+"\t"+i);
            //间隔1秒钟来打印即1000毫秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            }
    }
}

七、守护线程

1.守护线程 也就是为其他线程做铺垫,守护其他线程完成操作 Java中gc 垃圾回收器 Java项目在运行的时候 会出现很多垃圾的对象 这个和守护线程 一致监听着 进行垃圾回收
2.守护线程的死亡
被守护的线程死亡 守护肯定会死亡
被守护的线程的操作的执行完成 守护线程也会随之死亡
一般守护的都是主线程
3.设置守护线程
在这里插入图片描述

package day21;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class MyThread1 extends Thread {
    @Override
    public void run(){

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        try {
            FileOutputStream fos = new FileOutputStream("1.txt");
            fos.write(97);
            fos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

package day21;

public class Test03 {
    public static void main(String[] args) {
        MyThread1 my = new MyThread1();
        //需要将其设置为守护线程  守护的线程就是主的线程
        //一定要再开启线程之前调用 否则没有效果
        my.setDaemon(true);
        System.out.println(my.isDaemon());
        my.start();
    }
}

八、设置线程的优先级

在这里插入图片描述
三个常量
在这里插入图片描述

注意点:
1.设置优先级的值越大 获取cpu的执行权越大 范围 1-10
2.优先级的值越大 并不一定会执行java 抢占式调度
理解 优先级 是更大概率抢到cpu 但概率是概率 不是一定

package day21;

public class Test04 {
    public static void main(String[] args) {
        MyThread th1 = new MyThread();
        th1.setPriority(Thread.MAX_PRIORITY);
        //开启线程
        th1.start();
        MyThread th2 = new MyThread();
        th2.setPriority(8);
        th2.start();
    }
}

九、第二种方式实现多现线程(实现类连接Runnable接口)

步骤
1.定义一个类 这个类需要实现Runnable这个接口
2.实现其抽象方法 run() ==>执行线程操作
3.实例化线程Thread 对象 构造方法参数就是Runnable的实现类
4.开启线程 调用start()方法
注意点:
这一种方式底层也是使用第一种方式来实现了 这种方式就是为了解决线程 java单继承的问题

package day21;

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+"\t"+i);
        }
    }
}

package day21;

public class Test05 {
    public static void main(String[] args) {
        MyRunnable runnable = new MyRunnable();
        Thread th =new Thread(runnable);
        th.start();
        Thread th1 = new Thread(runnable);
        th1.start();
    }
}

十、伪第三种方式实现多线程(使用匿名内部类)

1.匿名没有名字 类部类就是定义在类中的类
2.匿名内部类的语法
new 抽象类| 或者是接口{
实现的方法
}
3.代码
new Thread(){public void run() }.start(); 这个是在方法参数后加大括号,在大括号里重写run(),实质是第一种方式的匿名写法
new Thread(new Runnable()).start(); 这个是在方法参数里new Runnable() 匿名类,在匿名类中重写run(),实质是第二种方式的匿名写法

package day21;

public class Test06 {
    public static void main(String[] args) {
        new Thread(){
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"\t"+"在执行操作");
            }
        }.start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"\t"+"在执行操作");
            }
        }).start();
    }
}

十一、线程之间的通信问题

在这里插入图片描述
1.案例
1.使用三个线程来购买电影票 必须实现接口 这种方式来实现
2.需要定义一个变量来记录其票数
3.使用循环来进行购买 需要判断是否存在票 每次购买完票之后 票数需要-1

public class MyMovieRunnable implements Runnable {
//定义一个变量来记录其总的票数
private int count =100;
@Override
public void run() {
//使用死循环 一直进行购买票
while (true) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
//判断票是否还存在
if (count>0){
System.out.println(Thread.currentThread().getName()+"买了
第"+count+"张票");
// 需要进行递减
count--;
}
}
}
}
package day21;

public class Test07 {
    public static void main(String[] args) {
        MyMovieRunnable movieRunnable = new MyMovieRunnable();
        Thread th1 = new Thread(movieRunnable);
        th1.start();;
        Thread th2 = new Thread(movieRunnable);
        th2.start();
        Thread th3 = new Thread(movieRunnable);
        th3.start();
    }
}

2.出现问题的原因
在这里插入图片描述
3.解决方法一
第一种方式:使用同步代码块
同步就是排队,一个一个来
1.同步代码块的语法:
synchronized (锁的对象){
出现问题的代码
}

2.注意点
(1)锁的对象可以是任意的对象,但要保证多线程数据安全,应该锁多线程共享的数据
(2)所有的线程都必须使用同一个锁的对象

3.作用:
保证多线程数据共享的安全性
4.改造代码

package day21;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyMovieRunnable implements Runnable {
    //定义一个变量来记录其总的票数
    private  int count =100;
   
    //定义一个锁的对象 多线程都共享obj对象
    private Object obj = new Object();
    @Override
    public void run() {
        while (true){

            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (obj){
                if(count>0){
                    System.out.println(Thread.currentThread().getName()+"买了第"+count+"张票");
                    //需要递减
                    count--;
                }
            }

        }
    }

  

4.解决问题的原理
在这里插入图片描述
5.解决问题方法二
1.使用的是同步的方法 可以使用同步的普通方法 也可以使用同步的静态方法
2.同步方法的语法
访问修饰符 synchronized 访问值类型 方法的名称(参数列表) {
可以出现问题的代码
}
3.代码

package day21;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyMovieRunnable implements Runnable {
    //定义一个静态变量来记录其总的票数
    private static int count =100;
    //定义一个锁的对象
    private Object obj = new Object();
    @Override
    public void run() {
        while (true){

            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
          
            //判断票是否存在
            showInfo();

        }
    }

    private synchronized void showInfo() {
        if(count>0){
            System.out.println(Thread.currentThread().getName()+"买了第"+count+"张票");
            //需要递减
            count--;
        }
    }
    private static synchronized void showInfoStatic(){
        if(count>0){
            System.out.println(Thread.currentThread().getName()+"买了第"+count+"张票");
            //需要递减
            count--;
        }
    }
 

4.注意点
普通方法的锁的对象就是当前对象 就是this
静态方法的 锁对象是当前对象的.class 对象
6.第三种解决方法
1.使用Lock锁对象 Lock 实现提供了比使用synchronized 方法和语句可获得的更广泛的锁定操作
2.常用的实现类 ReentrantLock
3.常用的方法
在这里插入图片描述

package day21;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyMovieRunnable implements Runnable {
    private  int count =100;
    Lock lock =new ReentrantLock();
    @Override
    public void run() {
        while (true){
            lock.lock();
            try {
                Thread.sleep(500);
                if(count>0){
                    System.out.println(Thread.currentThread().getName()+"买了第"+count+"张票");
                    //需要递减
                    count--;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }

        }
    }
}

synchronized锁升级的原理

无锁,先偏向锁,在轻量级锁,在重量级锁,性能消耗越来越大,且锁升级顺序不可逆
原理:在锁对象的对象头里面有一个threadid字段,线程在第一次访问的时候threadid 为空,jvm 让其持有偏向锁,并将threadid 设置为其线程id ,再次访问的时候会先判断threadid 是否与其线程id一致,如果一致则可以直接使用此对象,如果不一致,则升级偏向锁为轻量级锁,等待锁对象中,通过自选循环一定次数来获取锁,执行一定次数之后,还未正常获取到要使用的对象,此时就会把锁从轻量级升级为重量级锁,此过程就构成了synchronized锁的升级。
作用:锁升级是为了减低锁带来的性能消耗。在Java6之后优化synchronized 的实现方式,使用了偏向级锁升级为轻量级锁再升级到重量级锁的方式,从而降低了锁带来的性能消耗。

十二、线程的死锁

1.死锁: A线程使用B的锁资源 B线程使用A锁资源 都不进行释放 相互等着 这就是死锁
2.例子:宿舍只有一双拖鞋 A.刘正武 B.康林 == >A B 都要出去买东西
A得到左角的拖鞋 B得到右脚的拖鞋 A B 都霸占锁资源都不释放 所以出现死锁 都不能出去
3.代码
鞋资源

package day21;

public class Shoes {
    public static final String left="left";
    public static final String right="right";
}

张三线程

package day21;

public class ZhangSanRunnable implements Runnable{
    @Override
    public void run() {
        synchronized (Shoes.right){
            System.out.println("张三获取右脚拖鞋");
            /*try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }*/
            synchronized (Shoes.left){
                System.out.println("张三获取左脚拖鞋");
            }
        }
    }
}

李四线程

package day21;

public class LiSiRunnable implements Runnable{
    @Override
    public void run() {
        synchronized (Shoes.left){
            System.out.println("李四获取左脚拖鞋");

            synchronized (Shoes.right){
                System.out.println("李四获取右脚拖鞋");
            }
        }
    }
}

测试类

package day21;

public class Test08 {
    public static void main(String[] args) {
        ZhangSanRunnable zhangSan = new ZhangSanRunnable();
        Thread th1 = new Thread(zhangSan);
        th1.start();
        LiSiRunnable liSi = new LiSiRunnable();
        Thread th2 = new Thread(liSi);
        th2.start();
        //如果线程先抢占cpu通道运行完两只鞋就能运行完
        //a占了b锁资源,b占了a锁资源才会运行不完
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值