Java多线程笔记

基本概念:程序、进程、线程

  • 程序:是为了完成特定任务,用某种语言编写的椅子指令的集合。即指一段静态的代码,静态对象。
  • 进程:是程序的依次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。——生命周期
    程序是静态的,进程是动态的.
    进程作为资源分配的单位,系统在运行时会为每一个今晨分配不同的内存区域。
  • 线程:进程可进一步细化为线程,或是正在运行的一个程序。是一个程序内部的一条执行路径。
    1. 若一个进程同一时间并行执行多个线程,就是支持多线程的。
    2. 线程作为调度和执行的单位,每个线程拥有独立的运行栈和计算器,线程切换的开销小。
    3. 一个进程的多个线程共享相同的内存单元/内存地址空间->它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源就可能会带来安全的隐患。
  • 线程的五种状态:新建状态、就绪状态、运行状态、阻塞状态、死亡状态。(创建、就绪、运行、阻塞、终止。)

单核CPU和多核CPU

  • 单核CPU是一种假的多线程,一个时间单元内,只能执行一个线程的任务。(对于单核的CPU来说,在某一个时间点上实际上只能处理一件事情,但是由于CPU的处理速度极快,多个线程之间频繁切换执行,给别人的感觉是:多个事情同时在做!!!)

  • 一个Java应用程序,其实至少有三个线程:main()主线程、gc()垃圾回收线程、异常处理线程。如果发生异常,会影响主线程。

  • 并行:多个CPU同时执行多个任务。

  • 并发:一个CPU(采用事件片)同时执行多个任务。

Java实现线程的两种方式

在这里插入图片描述

第一种方式

1. 用new继承线程的类的方式来创建现成的对象。
2. 调用线程对象的start()方法来启动线程。
// 定义线程类
public class MyThread extends Thread{
	public void run(){
	
	}
}
// 创建线程对象
MyThread t = new MyThread();
// 启动线程。
t.start();

cuurentThread();//静态方法,返回执行当前代码的线程。
getName();//获取当前线程的名字
setName();//设置当前线程的名字
yield();//释放当前cpu的执行权

注意

  • t.run() 不会启动线程,只是普通的调用方法而已。不会分配新的分支栈。(这种方式就是单线程。)
  • t.start() 方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。
    这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开出来,start()方法就结束了。线程就启动成功了。
    启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部(压栈)。
    run方法在分支栈的栈底部,main方法在主栈的栈底部。run和main是平级的。

第二种方式

  • 编写一个类,实现java.lang.Runnable接口,实现run方法。
// 定义一个可运行的类
public class MyRunnable implements Runnable {
	public void run(){
	
	}
}
// 创建线程对象
Thread t = new Thread(new MyRunnable());
// 启动线程
t.start();

  • 继承Thread类的方式不能实现资源的共享,而实现Runnable接口可以实现资源的共享。
//继承Thread类不能实现资源的共享。
public class MyThread extends Thread {
    private int ticket = 5;

    @Override
    public void run() {
        super.run();
        for (int i = 0; i < 100; i++) {
            if(ticket > 0){
                System.out.println("卖票:ticket = " + ticket--);
            }
        }
    }
}

class ThreadDemo04 {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        MyThread myThread1 = new MyThread();
        MyThread myThread2 = new MyThread();
        myThread.start();
        myThread1.start();
        myThread2.start();
    }
}

//实现Runnable接口的可以资源共享
public class MyRunnable implements Runnable {
    private int ticket = 5;//ticket属性被所有线程所共享

    @Override
    public void run() {
        for(int i = 0; i < 10; i++) {
            if(ticket > 0) {
                System.out.println("卖票:ticket = " + ticket--);
            }
        }
    }
}

class RunnableTest {
    public static void main(String[] args) {
        MyRunnable test = new MyRunnable();
        new Thread(test).start();
        new Thread(test).start();
        new Thread(test).start();
    }
}

判断线程是否启动

  • Java可以使用isAlive()方法来测试线程是否已经启动而且仍然在执行。
class MyThreadAlive implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() +
                    "运行 --> " + i);
        }
    }
}

public class ThreadAlive {
    public static void main(String[] args) {
        MyThreadAlive myAlive = new MyThreadAlive();
        Thread thread = new Thread(myAlive,"线程");
        System.out.println("线程开始执行之前 --> " + thread.isAlive());
        thread.start();
        System.out.println("线程开始执行之后 --> " + thread.isAlive());
        for (int i = 0; i < 10; i++) {
            System.out.println("main 运行 --> " + i);
        }
        System.out.println("代码执行之后 --> " + thread.isAlive());
    }
}

比较创建线程的两种方式

  • 开发中:优先选择:实现Runnable接口方式
  • 原因:
    1. 实现的方式没有类的单继承的局限性。
    2. 实现的方式更适合来处理多个线程有共享数据的情况。
  • 联系:public class Thread implements Runnable
    • 相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。

线程调度关系

  • 线程名称的格式是:Thread - Xx。
  • Java运行时至少会启动两个线程,一个是main线程、另一个是垃圾收集线程。
  • 因为线程操作的不确定性,所以主线程有可能最先执行完,此时其他线程不会受到影响,并不会随着主线程的结束而结束。
static Thread currentThread();	//获取当前线程对象
//例:
Thread currentThread = Thread.currentThread();

String getName();	//获取线程对象名字
//例:
System.out.println(currentThread.getName() + "-->" + i);

void setName(String name);	//修改线程对象名字

yiled方法(线程的礼让)

  • yield()方法不是阻塞方法。让当前线程让位,让给其它线程使用。
  • yield()方法的执行会让当前线程从“运行状态”回到“就绪状态”。
  • 注意:在回到就绪之后,有可能还会再次抢到。
static void yield();	//让位方法,当前线程暂停,回到就绪状态,让给其它线程。
  • 例子:
class MyYield implements Runnable {
    @Override
    public void run() {
        for(int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName()
            + "运行 --> " + i);
            if(i == 3) {
                System.out.println("线程礼让:");
                Thread.yield();
            }
        }
    }
}

public class ThreadYield {
    public static void main(String[] args) {
        MyYield yield = new MyYield();
        Thread thread = new Thread(yield,"线程A");
        Thread thread1 = new Thread(yield,"线程B");
        thread.start();
        thread1.start();
    }
}

jion方法(线程的强制运行)

  • 在线程操作中,可以使用join方法让一个线程强制运行,线程强制运行期间,其他线程无法运行,必须等到此线程玩长城之后才可以继续运行。
void join();
//例:
t.join(); // 当前线程进入阻塞,t线程执行,直到t线程结束。当前线程才可以继续。

void join(long millis);	//接上条,等待该线程终止的时间最长为 millis 毫秒

void join(long millis, int nanos);	//接第一条,等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒

 一个线程.join(),当前线程会进入”阻塞状态“。等待加入线程执行完!

sleep方法和interrupt方法(线程的休眠)

  • 静态方法:Thread.sleep(1000);参数是毫秒
  • 作用: 让当前线程进入休眠,进入“阻塞状态”,放弃占有CPU时间片,让给其它线程使用。
  • 这行代码出现在A线程中,A线程就会进入休眠。
  • 这行代码出现在B线程中,B线程就会进入休眠。
  • Thread.sleep()方法,可以做到这种效果:
    间隔特定的时间,去执行一段特定的代码,每隔多久执行一次。
  • 休眠一旦中断之后将执行catch中的代码。
static void sleep(long millis);	//让当前线程休眠millis秒(毫秒)。
//例:
// 每执行一次该线程睡眠1秒
try {
	Thread.sleep(1000);
} catch (InterruptedException e) {
	 e.printStackTrace();
}

void interrupt();	//终止线程的睡眠
//例:
  // 终断t线程的睡眠(这种终断睡眠的方式依靠了java的异常处理机制。)
        t.interrupt();
         
// 5秒之后强行终止t线程
t.stop(); // 已过时(不建议使用。)
//缺点:这种方式存在很大的缺点:容易丢失数据
//因为这种方式是直接将线程杀死了,线程没有保存的数据将会丢失。不建议使用。

//例子:
class MyInterrupt implements Runnable {
    @Override
    public void run() {
        System.out.println("1.进入run方法。");
        try {
            Thread.sleep(10000);
            System.out.println("2.已完成休眠。");
        }catch (Exception e) {
            System.out.println("3.休眠被终止。");
            return;
        }
        System.out.println("4.线程无法正常结束。");
    }
}

public class InterruptTest {
    public static void main(String[] args) {
        MyInterrupt test = new MyInterrupt();
        Thread t = new Thread(test);
        t.start();
        try {
            Thread.sleep(2000);
        }catch (Exception e){

        }
        t.interrupt();  //中断线程的执行
    }
}

setDeamon方法(后台线程)

  • 只要前台有一个线程在运行,则整个Java进程都不会消失,所以可以设置一个后台线程,这样即使Java进程结束了,此后台线程依然会继续执行,即使用setDaemon()方法。
class MyDaemon implements Runnable {
    @Override
    public void run() {
        while (true) {//死循环
            System.out.println(Thread.currentThread().getName()
            + "在运行。");
        }
    }
}

public class ThreadDaemon {
    public static void main(String[] args) {
        MyDaemon daemon = new MyDaemon();
        Thread thread = new Thread(daemon);
        thread.setDaemon(true);
        thread.start();
    }
}
尽管run()方法中是死循环的方式,但是程序依然可以
执行完,因为方法中的线程操作已经设置成后台运行了。

线程的优先级

  • main线程的默认优先级是:5
  • 最低优先级1
  • 默认优先级是5
  • 最高优先级10
//常量
static int MAX_PRIORITY;	//最高优先级(10)
static int MIN_PRIORITY;	//最低优先级(1)
static int NORM_PRIORITY;	//默认优先级(5)

int getPriority();	//获得线程优先级
//例:
System.out.println(hread.currentThread().getName() + "线程的默认优先级是:" + currentThread.getPriority());

void setPriority(int newPriority);	//设置线程优先级
//例:
t.setPriority(10);
  • 例题:
//理解:线程t1、t2、t3同时去获取资源在i = 0这个资源时,优先级高的先运行,并且t1、t2、t3同时共享资源。到t2时也按优先级共享资源。
class MyPriority implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            try {
                Thread.sleep(500);
            }catch (Exception e) {
            }
            System.out.println(Thread.currentThread().getName()
                    + "运行,i = " + i);
        }
    }
}

public class ThreadPriority {
    public static void main(String[] args) {
        MyPriority priority = new MyPriority();
        Thread t1 = new Thread(priority,"线程A");
        Thread t2 = new Thread(priority,"线程B");
        Thread t3 = new Thread(priority,"线程C");
        t1.setPriority(Thread.MIN_PRIORITY);
        t2.setPriority(Thread.MAX_PRIORITY);
        t3.setPriority(Thread.NORM_PRIORITY);
        t1.start();
        t2.start();
        t3.start();
    }
}

线程安全问题

  • 线程重点:我们编写的程序需要放到一个多线程的环境下运行,我们更需要关注的式这些数据在多线程并发的环境下是否安全的。
  • 数据在多线程环境下存在的安全问题的条件
    1. 多线程并发。
    2. 有共享数据。
    3. 共享数据并有修改行为。
  • 解决方法:
    • 多线程并发的环境下,有共享数据,并且这个数据还会被修改,此时就存在线程的安全问题,这个问题的解决方法如下:
      线程排队执行。(不能并发)。用排队执行解决线程的安全问题。这种机制被成为:**线程同步机制。**专业术语叫做:线程同步,实际上就是线程不能并发了,必须排队执行。
      线程排队会牺牲一部分效率,数据安全是第一位,只有数据安全了,我们才可以谈效率。

同步与死锁

  • 一个多线程如果是通过Runnable接口实现的 ,则意味着类中的属性将被多个线程共享,就可能出现资源同步的问题。

同步(同步就是排队)

  • 所谓同步,就是指多个操作在同一个时间段内只能有一个线程执行,其他线程要在等待此线程执行完了之后才可以继续执行。

  • 同步可以使用同步代码块和同步方法两种方式完成

  • 同步代码块

    • 在使用同步代码块时必须指定一个需要同步的对象,但一般都将当前对象设置成同步对象。
synchronized(同步对象){
	//需要同步的代码
}
  • 例子:
class MySync implements Runnable {
    private int ticket = 5;
    @Override
    public void run() {
        for(int i = 0; i < 100; i++) {
            synchronized (this) {
                if(ticket > 0) {
                    try {
                        Thread.sleep(300);
                    }catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("卖票:ticket = " + ticket--);
                }
            }
        }
    }
}

public class Sync {
    public static void main(String[] args) {
        MySync sync = new MySync();
        Thread thread1 = new Thread(sync);
        Thread thread2 = new Thread(sync);
        Thread thread3 = new Thread(sync);
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

  • 同步方法
    • 格式:
synchronized 方法返回值 方法名称(参数列表){
}
  • 例子:
class MySync1 implements Runnable {
    public int ticket = 5;

    @Override
    public void run() {
        for(int i = 0; i < 100; i++) {
            this.sale();
        }
    }

    public synchronized void sale() {
        if(ticket > 0) {
            try {
                Thread.sleep(300);
            }catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("卖票:ticket = " + ticket);
        }
    }
}



public class Sync1 {
    public static void main(String[] args) {
        MySync sync = new MySync();
        Thread thread1 = new Thread(sync);
        Thread thread2 = new Thread(sync);
        Thread thread3 = new Thread(sync);
        thread1.start();
        thread2.start();
        thread3.start();
    }
}
  • 使用这两种方式进行代码块取值和修改值得操作进行了同步,因此不会再出现卖出票为负数的情况。

死锁

  • 所谓死锁就是指两个线程都在等待对方先完成,造成了程序的停滞,一般的死锁都是在程序运行中出现的。

  • 例子:

package ever;

class Zhangsan {
    public void say() {
        System.out.println("张三对李四说:\"你给我画,我就把书给你\"。");
    }
    public void get() {
        System.out.println("张三得到画了。");
    }
}

class Lisi {
    public void say() {
        System.out.println("李四对张三说:\"你给我书,我就把画给你\"。");
    }
    public void get() {
        System.out.println("李四得到画了。");
    }
}

public class ThreadDeadLock implements Runnable {
    private static Zhangsan zs = new Zhangsan();
    private static Lisi ls = new Lisi();
    private boolean flag = false;

    @Override
    public void run() {
        if(flag) {
            synchronized (zs){
                zs.say();
                try {
                    Thread.sleep(500);
                }catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (ls){
                    zs.get();
                }
            }
        } else {
            synchronized (ls) {
                ls.say();
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (zs) {
                    ls.get();
                }
            }
        }
    }

    public static void main(String[] args) {
        ThreadDeadLock lock1 = new ThreadDeadLock();
        ThreadDeadLock lock2 = new ThreadDeadLock();
        lock1.flag = true;
        lock2.flag = false;
        Thread thread1 = new Thread(lock1);
        Thread thread2 = new Thread(lock2);
        thread1.start();
        thread2.start();
    }
}

Object类对线程的支持——等待与唤醒

  • public final void wait() throws InterruptedException —>等待线程
  • public final void wait(long timeout) throws InterruptedException——>线程等待,并指定等待的最长时间,以毫秒为单位。
  • public final void wait(long timeout,int nanos) throws InterruptedException——>线程等待,并指定等待的最长毫秒或纳秒。
  • public final void notify()——>唤醒第一个等待的线程。
  • public final void notifyAll()——>唤醒全部等待的线程。

线程的生命周期

  • 三个新方法:
    • suspend()方法:暂时挂起线程
    • resume()方法:恢复挂起的线程
    • stop()方法:停止线程
  • 不推荐使用上三种方法,因为这三种方法在操作时会出现死锁问题。
  • 在Java多线程的开发中可以通过设置标志位的方式停止一个线程的运行。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值