返回顶部
扩大
缩小

Yeap

多线程

多线程

一个Java应用程序java.exe,其实至少有三个线程:main()主线程,gc()
    垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。
实现多线程的方式一: 继承Thread类
  • 方法介绍

    void run(): 在线程开启后,吃方法执行

    void start(): 开启线程的标志,Jvm在这之后会调用run() 方法

  • 实现步骤:

    • 定义一个继承了Thread的子类
    • 在子类中重写run()方法 //将此线程要做的事情 写在run方法中
    • 创建子类的对象
    • 启动线程
    public class MyThread  extends Thread{
    
        @Override
        public void run() {
            for (int i = 0; i <10 ; i++) {
                System.out.println(Thread.currentThread().getName());//线程默认提供了name  0 1 2 类推   currentThread():静态方法,返回当前代码执行的线程
                System.out.println(i);
            }
        }
    }
    public class TestMyThread {
        public static void main(String[] args) {
            MyThread my1 = new MyThread();
            MyThread my2 = new MyThread();
    
            my1.start(); //一个线程只能start一次
            my2.start();
    
    
        }
    }
    
    

    为什么重写run方法?

    ​ 因为run()是用来封装被线程执行的代码

    run()方法和start()方法的区别?

    ​ run(): 封装线程执行的代码,直接调用,相当于普通方法的调用

    ​ start(): ①启动当前线程;②然后由JVM调用此线程的run()方法

设置和获取线程的名称

​ void setName(String name) 将此线程的名称更改为等于参数name

​ String getName(): 返回此线程的名称

​ Thread currentThread(): 放回当前正在执行的线程对象的引用

public class MyThread  extends Thread{


    public MyThread(String name) {
        super(name);
    }

    public MyThread() {

    }

    @Override
    public void run() {
        for (int i = 0; i <10 ; i++) {

            System.out.println(getName()+""+i);

        }
    }
}
class  TesetDemo{

    public static void main(String[] args) {
//        调用的无参构造方法 void setName()
//        MyThread m1 = new MyThread();
//        MyThread m2 = new MyThread();
//
//        m1.setName("飞机");
//        m2.setName("汽车");

//
//        m1.start();
//        m2.start();
      
        //有参构造方法  void setName(String name)
        MyThread my1 = new MyThread("飞机");
        MyThread my2 = new MyThread("高铁");

        my1.start();
        my2.start();

    }

}

线程优先级

线程调度:

  • 两种调度方式

    • 分时调度模式:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片

    • 抢占式调度模型: 优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取cpu时间片相对多一些。

    • java使用的是抢占式调度模式

    • 随机性

      • 假如计算机之后一个CPU,那么CPU在某一个时刻只能执行一条命令,线程只有得到CPU时间片,也就是使用权,才可以执行指令,所以说多线程的执行是随机的。

      优先级相关方法:

      final int getPriority(): 返回此线程的优先级

      final void setPriority(int new Priority): 更改线程优先级,默认是5; 范围是 1-10

    更改优先级并不能保证线程一定是第一个执行,只是CPU获取它的概率更大

线程控制

​ 相关方法

​ static void sleep(long millis): 使当前执行的线程停留(暂停执行)指定的毫秒数

​ void join(): 等待这个线程死亡

​ void setDaemon(boolean on): 将此线程标记为守护线程,当运行的线程都是守护线程时,JVM 将退出

守护线程:  当被守护的线程结束后,守护它的线程也会相继结束
  
  public class TheadDaemon extends Thread {
    @Override
    public void run() {

        for (int i = 0; i < 100; i++) {
            System.out.println(getName()+ ":"+i);
        }
    }
}
class TheadDaemonDemo{
    public static void main(String[] args) {
        TheadDaemon td1 = new TheadDaemon();//创建对象
        TheadDaemon td2 = new TheadDaemon();

        td1.setName("关羽");
        td2.setName("张飞");

        //设置主线程为刘备
        Thread.currentThread().setName("刘备");

        //设置守护线程
        td1.setDaemon(true);
        td2.setDaemon(true);

        
        td1.start();
        td2.start();

        for (int i = 0; i < 10; i++) {//当被守护的线程执行结束后其他的线程也会跟着结束
            System.out.println(Thread.currentThread().getName()+":"+i);
        }

    }
}

实现线程的第二种方式: 实现Runnable接口

  • Thread构造方法

    ​ Thread(Runnable target): 分配一个新的Thread对象

    ​ Thread(runnable target,String name): 分配一个新的Thread对象

  • 实现步骤

    • 定义一个类实现Runnable接口
    • 在子类中重写run() 方法
    • 创建子类的对象
    • 创建Thread类的对象,把子类对象作为构造方法的参数
    • 启动线程
public class MyRunnable implements Runnable { 
  @Override
    public void run() {
        for(int i=0; i<100; i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
} }

public class MyRunnableDemo {
    public static void main(String[] args) {
// //
} }
//创建MyRunnable类的对象
MyRunnable my = new MyRunnable();
//创建Thread类的对象,把MyRunnable对象作为构造方法的参数
//Thread(Runnable target)
  Thread t1 = new Thread(my);
	Thread t2 = new Thread(my); 
//Thread(Runnable target, String name) Thread t1 = new Thread(my,"高铁");
Thread t2 = new Thread(my,"飞机");
//启动线程 t1.start(); t2.start();
		}
}

  • 多线程的实现方法有两种
    • 继承Thread类: (类似于创建三个线程,三个线程各自去完成自己的事情(各自循环10次))
    • 实现Runnable接口: (创建了三个线程,三个线程同时处理同一件事情(三个线程一共执行10次)
  • 相比继承Thread类,实现Runnable接口的好处

    • 避免了java单继承的局限性
    • 适合多个相同程序的代码去处理同一个资源的情况,把线程和程序的代码,数据有效分离,较好的实现了面向对象的设计思想

同步代码块解决数据安全问题

  • 安全问题出现的条件

    • 是多线程环境
    • 有共享数据
    • 有多条语句操作共享数据
  • 如何解决多线程问题?

    • 打破其中的一条: 基本思想是:让程序没有安全问题的环境
  • 怎样实现?

    • 把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行
    • Java提供了同步代码块的方法来解决
  • 同步代码块的格式:

    同步监视器:俗称: 锁! 任何类的对象都可以充当锁
      
    		要求:多个线程必须要共享同一把锁!要保证唯一性
      	
    synchronized(同步监视器){
      多条语句操作共享数据的代码 //也就是需要同步的代码   			操作共享数据的代码,即为需要被同步的代码
      
    }
    

    synchronize(任意对象):就相当于给代码加了锁,任意对象就可以看成是一把锁

  • 同步的好处和弊端

    • 好处:解决了多线程的数据安全问题
    • 弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这很耗费资源,无形中降低了运行效率
注意: 使用同步时看实现的方式是继承了Thread方法,还是实现了Runnable接口,并且注意是创建了一个对象,还是多个对象,如果是一个对象,在创建锁的时候,可以不加static就能确保唯一性,如果是多个对象,加上static类确保锁的唯一性。  相对简单的锁的对象可使用 this,此时的this表示的是唯一的对象,多个对象的时候不能使用 this,或者直接(xxx.class),在继承Thread类创建多线程的时候,谨慎使用this来确定唯一性
  • 同步方法格式
    • 如果操作共享数据的代码正好在一个方法中,我们不妨将此方法声明为同步方法

    同步方法: 就是把synchronize关键字加到方法上

    修饰符 synchronize 返回值类型 方法名(方法参数){
      
      方法体;
    }
    

    同步方法锁的对象是什么呢?

    ​ 类名.class

    使用继承时创建了三个对象,但是默认的监视器锁是this,这样就会造成锁的对象不唯一,线程依然不安全。将方法设置成static的时候,锁是 类名.class

1,同步方法依然涉及到同步监视器,只是不需要显示的声明。
2,非静态的同步方法,同步监视器是:this
			静态方法,同步监视器是: 当前类本身
线程的死锁问题

死锁:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要同步的资源,就形成了线程的死锁

死锁出现后,不会出现异常,不会有提示,只是所有的线程都处于阻塞状态,无法继续。

Lock(锁)

Lock是接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。

ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常有的是ReentrantLock,可以显式加锁、释放锁。

1,面试题: synchronized 与Lock的异同?
		同: 都可以解决线程的安全问题
		不同:synchronized,在执行完相应的同步代码块之后,会自动释放同步监视器
		Lock需要手动的启动同步(Lock()),同时结束同步也需要手动的结束(unlock())

线程通信

涉及到的三个方法:

wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器

notify():一旦执行此方法,就会唤醒被wait的一个线程,如果有多个线程就唤醒优先级高的那个。

notifyAll():一旦执行此方法,就会唤醒被wait的所有线程

说明:
  	1,wait(),notify(),notifyAll()三个方法必须使用在同步代码块或者同步方法中。
    2,wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或者同步方法中的同步监视器,否则会出现 IllegalMonitorStateException异常
    3,wait(),notify(),notifyAll()三个方法定义在java.lang.Object类中的
  
面试题:sleep()方法和wait() 的异同?
1、相同点:一旦执行都会是当前线程进入阻塞。
2、不同点:1)两个方法声明的位置不同:Thread类中声明sleep(),Object类中声明wait();
2)调用范围不同:
		sleep()可以在任何需要的场景下调用,wait()必须使用在同步代码块或同步方法中调用。
3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁(同步监视器)

posted on 2020-12-21 16:13  YP泡泡  阅读(95)  评论(0)    收藏  举报

导航