javaSE多线程笔记总结

进程和线程的概念

进程:正在运行的程序,是系统内进行资源分配和调用的独立单位。每个进程都有它自己的内存空间和系统资源。
线程:是进程中单个顺序控制流,是一条执行路径。一个进程如果只有一条执行路径,则称为单线程程序。一个进程如果有多条执行路径,则称为多线程程序。

实现多线程程序的方法:

  • 继承Thread类
 public class MyThread extends Thread {
    
    
	@Override
	public void run() {
    
    
		for (int x = 0; x < 100; x++) {
    
    
			System.out.println(getName() + ":" + x);
		}
	}
}
public static void main(String[] args){
    
    
        MyThread thread1 = new MyThread();
        MyThread thread2 = new MyThread();
        thread1.start();
        thread2.start();
    }

相关方法:
public static void yield():暂停当前正在执行的线程对象,并执行其他线程。 让多个线程的执行更和谐,但是不能靠它保证一人一次。
public final void stop():让线程停止,过时了,但是还可以使用。
public void interrupt():中断线程。 把线程的状态终止,并抛出一个InterruptedException。

  • 实现接口
public class MyRunnableDemo {
    
    
    public static void main(String[] args){
    
    
        MyRunnable runnable1 = new MyRunnable();
        Thread thread1 = new Thread(runnable1, "刘雨昕");
        Thread thread2 = new Thread(runnable1, "许佳琪");

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

线程的生命周期

  1. 新建
  2. 就绪
  3. 运行
  4. 阻塞
  5. 死亡
    在这里插入图片描述

实际应用(模拟卖票)

public class SellTicket extends Thread{
    
    
    private static int tickets = 100;

    @Override
    public void run() {
    
    
        while (tickets >= 0){
    
    
            try {
    
    
                Thread.sleep(100);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            System.out.println(getName() + ":" + tickets--);
        }
    }
}

public class SellTicketDemo {
    
    
    public static void main(String[] args){
    
    
        SellTicket thread1 = new SellTicket();
        SellTicket thread2 = new SellTicket();
        SellTicket thread3 = new SellTicket();
        thread1.setName("窗口一");
        thread2.setName("窗口二");
        thread3.setName("窗口三");

        thread1.start();
        thread2.start();
        thread3.start();
    }
}

出现的问题:

  1. 相同的票卖了多次
    在这里插入图片描述
    原因:CPU的一次操作必须是原子性的
    System.out.println(getName() + “:” + tickets–);这一语句分为三个原子性操作,分别为先记录以前的值,接着把ticket–,然后输出以前的值。
    过程分析:假设当前 tickets为94,thread1线程运行,执行System.out.println(getName() + “:” + tickets–)时,记录以前的值为94,此时thread2抢到时间片,执行System.out.println(getName() + “:” + tickets–)记录以前的值为94,此时thread3抢到时间片,执行System.out.println(getName() + “:” + tickets–)记录以前的值为94,接着把ticket–,然后输出94,此时thread2抢到时间片,这时thread2记录的值是94(但是实际值已经变成了93),ticket–,然后输出94,由此带来错误。
  2. 出现了负票数
    在这里插入图片描述
    原因:随机性和延迟
    过程分析:thread1抢到时间片,进入run程序睡眠,此时thread2抢到时间片,进入run程序并睡眠,此时thread3抢到时间片,进入run程序睡眠,thread123依次醒来,执行System.out.println(getName() + “:” + tickets–),就出现了图片所示情况。
    解决:
    出现线程安全问题的情况为:
    A:是多线程环境
    B:有共享数据
    C:有多条语句操作共享数据
    AB无法控制,对于C可以加锁将多条操作语句锁起来。即线程同步。
    注意:
  •  	同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能。
    
  •  	多个线程必须是同一把锁。
    
public class SellTicket extends Thread{
    
    
    private static int tickets = 100;
    //创建锁对象
    private static Object obj = new Object();

    @Override
    public void run() {
    
    
        synchronized (obj){
    
    
            while (tickets >= 0){
    
    
                try {
    
    
                    Thread.sleep(100);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                System.out.println(getName() + ":" + tickets--);
            }
        }
    }
}
public class SellTicketDemo {
    
    
    public static void main(String[] args){
    
    
        SellTicket thread1 = new SellTicket();
        SellTicket thread2 = new SellTicket();
        SellTicket thread3 = new SellTicket();
        thread1.setName("窗口一");
        thread2.setName("窗口二");
        thread3.setName("窗口三");

        thread1.start();
        thread2.start();
        thread3.start();
    }
}

这是用继承thread类的方法实现的,其中tickets和obj都必须为静态变量,保证一个类中共用一个tickets和obj。
但这样会出现死锁问题需要注意,如:

public class DieLock extends Thread{
    
    
    private boolean flag;
    private static final Object obj1 = new Object();
    private static final Object obj2 = new Object();

    public DieLock(boolean flag) {
    
    
        this.flag = flag;
    }

    public DieLock() {
    
    
    }

    @Override
    public void run() {
    
    
        if(flag){
    
    
            synchronized (obj1){
    
    
                System.out.println("if obj 1");
                synchronized (obj2){
    
    
                    System.out.println("if obj 2");
                }
            }
        }else {
    
    
            synchronized (obj2){
    
    
                System.out.println("if obj 2jiojoiji");
                synchronized (obj1){
    
    
                    System.out.println("if obj 1hiughjhjkhk");
                }
            }

        }
    }
}

```java
public class DieLockDemo {
    
    
    public static void main(String[] args){
    
    
        DieLock lock1 = new DieLock(true);
        DieLock lock2 = new DieLock(false);

        lock1.start();
        lock2.start();
    }
}

正确结果为:在这里插入图片描述
但会出现死锁情况,如:在这里插入图片描述
分析:这是由于lock2将obj2锁住打印出“if obj 2”后,lock1抢到时间片将obj2锁住打印出“if obj 1”,接下来就无法执行了,因为两个锁都被锁住了。

线程组和线程池

线程组:java使用ThreadGroup来表示线程组,它可以对一匹线程进行分类管理,并且允许程序直接对线程组进行控制。
线程池:程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。

  • 线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。
  • 在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池

相关知识点

并发和并行:
并行指逻辑上同时发生,指在某一个时间内同时运行多个程序。
并发指物理上同时发生,指在某一个时间点同时运行多个程序。

java程序的运行原理:
由java命令启动JVM,JVM启动就相当于启动了一个进程,该进程会创建一个主线程调用main方法。
实现多线程的程序:
线程是依赖进程而存在的,应该先创建一个进程,而进程是由系统创建的,所以要调用系统功能创建一个进程。而Java无法直接调用系统功能,所以没有办法直接实现多线程程序。但是Java可以调用C/C++写好的程序来实现多线程程序。由C/C++去调用系统功能创建进程,然后由Java去调用这样的东西,然后提供一些类供我们使用由此实现多线程程序。
线程安全的类:
StringBuffer
Vector
Hashtable

猜你喜欢

转载自blog.csdn.net/go_to_study/article/details/109296471