01_多线程

多线程

一、进程与线程

1.1、进程:

进程:是正在运行的程序

  • 是系统进行资源分配和调用的独立单位

  • 每个进程都有它自己的内存空间和系统资源

 

1.2、线程:

在一个进程内部,可以执行一个任务,也可以执行多个任务

线程:是进程中的单个执行顺序控制流,是一条执行路径

  • 单线程:一个进程中如果只有一条执行路径,则称为单线程程序

  • 多线程:一个进程中如果有多条执行路径,则称为多线程程序

 

二、多线程的实现方式

1.1、方式1:继承Thread类

  • 定义一个类MyThread类

  • 在MyThread类中重写run()方法

  • 创建MyThread类对象

  • 启动线程

两个问题:

  • 为什么要重写run()方法?

    因为run方法是用来封装被线程执行的方法

  • run方法和start方法的区别?

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

    start:启动线程;然后由JVM调用此线程的run方法

 

1.2、Thread类中获取和设置线程名称的方法

void setName(String name):设置线程名称 String getName():返回线程名称

 

三、线程调度

线程有两种调度模型:

  • 分时调度模型

  • 抢占式调度模型

Java是抢占式

 

四、设置和获取线程名称

设置线程名称:myThread1.setName("线程1");

获取线程名称:myThread1.getName();

//static Thread currentThread() :返回对当前正在执行的线程对象的引用 System.out.println(Thread.currentThread().getName());

 

五、线程优先级

最小:1

最大:10

默认值:5

方法:

//获取线程的优先级 System.out.println(myThread1.getPriority()); //5 System.out.println(myThread2.getPriority()); //5

//设置线程优先级:max:10;min:5 myThread2.setPriority(10); myThread1.setPriority(1);

 

六、线程控制

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

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

//void setDaemon(boolean on):将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机退出

 

七、线程的生命周期

 

八、多线程实现方式

8.1、方式2:实现Runnable接口

  • 定义一个MyRunnable类实现接口

  • 在MyRunnable中重新run方法

  • 创建MyRunnable类的对象

  • 创建Thread类的对象,把MyRunnable对象作为构造方法的参数

  • 启动线程

 

8.2、多线程的实现方式有两种:

  • 继承Thread类

  • 实现Runnable接口

8.3、相比继承Thread类,实现Runnable接口的好处:

  • 避免了Java单继承的局限性

  • 适合多个相同程序的代码去处理同一资源的情况,把线程和程序的代码、数据有效分离,较好的体现了面向对象的设计思想

 

九、同步代码块

锁多条语句操作共享数据,可以使用同步代码块实现

  • 格式:

synchronized (任意对象){
   //多条语句操作共享数据的代码
   //               t1进来后,就会把这段代码给锁起来
   if (ticket > 0){
       //通过sleep方法来模拟出票时间
       try {
           Thread.sleep(100);
      } catch (InterruptedException e) {
           throw new RuntimeException(e);
      }
       System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket + "张票");
       ticket--;
  }
}
  • synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成一把锁

  • 弊端:当线程很多时,因为每个线程都会判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。

 

十、同步方法

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

  • 格式:

private synchronized void sellTicket() {
   
}
  • 同步方法的锁对象是this

同步静态方法:就是把synchronized关键字加到静态方法上

  • 格式

private static synchronized void sellTicket() {

}

同步静态方法的锁对象是

  • 类名.class

 

十一、线程安全的类

public class ThreadDemo {
   public static void main(String[] args) {
       //线程安全
       StringBuffer sb = new StringBuffer();
       //线程不安全
       StringBuilder sb2 = new StringBuilder();


       //以下两个被Collections替代了
       //线程安全
       Vector<String> v = new Vector<>();
       //线程不安全
       ArrayList<String> strings = new ArrayList<>();

       //线程安全
       Hashtable<String, String> ht = new Hashtable<>();
       //线程不安全
       HashMap<String, String> hm = new HashMap<>();

       //static <T> list<T> synchronizedList(list<T> list):返回由指定列表支持的同步(线程安全的)列表
       List<String> strings1 = Collections.synchronizedList(new ArrayList<String>());

  }
}

 

 

十二、Lock锁

Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作

Lock中提供了获得锁和释放锁的方法:

  • void lock();获得锁

  • void unlock();释放锁

Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化

ReentrantLock的构造方法

  • ReentrantLock():创建一个ReentrantLock的实例

 

 

十二、死锁

在Java多线程程序中,死锁是一种非常常见的问题。死锁是指两个或多个线程互相持有彼此需要的资源并且互相等待,从而导致程序无法继续执行下去的一种情况。

例如,如果线程 A 持有锁 1,并尝试去获得锁 2,而线程 B 此时持有锁 2,尝试去获得锁 1,那么这两个线程就会进入死锁状态。因为线程 A 需要锁 2 才能释放锁 1,而线程 B 需要锁 1 才能释放锁 2。

为了避免死锁,可以使用以下方法:

  1. 避免共享资源:尽量避免线程之间共享同一个资源,或使用同步机制来避免冲突。

  2. 加锁的顺序一致性:尽量让所有线程按照相同的顺序获取锁,例如所有线程都先获取锁1,再获取锁2。

  3. 超时机制:在获取资源的时候,设置超时机制,如果超过一定时间还没有获取到资源,就放弃。这样可以避免线程无限等待。

  4. 用 tryLock():使用可重入锁(ReentrantLock)的 tryLock() 方法,如果未能获取到锁则返回 false,避免长时间阻塞。

  5. 避免嵌套锁:尽可能避免使用嵌套锁,如果必须使用,则一定要确保加锁的顺序一致。

posted @ 2023-06-06 16:38  苏六来了✨  阅读(22)  评论(0)    收藏  举报