wyl-blogs

导航

< 2025年2月 >
26 27 28 29 30 31 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 1
2 3 4 5 6 7 8

统计

第十二章 多线程

第十二章 多线程

一、基本概念

1.1、程序、进程、线程

程序

  • 为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。

进程

  • 进程是一个运行时的应用程
  • 一个进程可以启动多个线程任务

线程

  • 线程是一个进程中的执行场景。线程,又称轻量级进程(Light Weight Process)
  • 程序中的一个顺序控制流程,同时也是CPU的基本调度单位
    • 比如:
      • 迅雷是一个进程,当中的多个下载任务就是多个线程
      • Java虚拟机是一个进程,当中默认包含主线程(Main函数),可通过代码创建多个独立线程,与Main并发执行

1.2、并发与并行

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

1.3、注意

​ 1. 进程之间内存独立不共享

​ 2. 线程之间堆内存和方法区内存共享

  1. 栈内存独立,一个线程一个栈
  2. 单核CPU在任何时间点上,只能运行一个进程,宏观并发,微观穿行
  3. 单核CPU不能够做到多线程并发,但能做到多线程并的感觉,CPU处理速度极快,多个线程频繁切换

二、线程的组成与创建

2.1、基本组成部分:

CPU时间片:

  • 操作系统(OS)会为每个线程分配时间

运行数据:

  • 堆空间: 存储线程需使用的对象,多个线程可以共享堆中的对象
  • 栈空间: 存储线程需使用的局部变量,每个线程都拥有独立的栈

2.2、创建线程的方式

2.2.1、创建线程的第一种方式---Thread

public class TestSleep {
	public static void main(String[] args) throws InterruptedException {
		MyThread t1 = new MyThread();
		t1.start();
		//main线程不会阻塞,多核并行单核并发
		for (int i = 1; i <= 20; i++) {// 线程任务
			System.out.println(Thread.currentThread().getName() + " - " + i);
			
		}
	}

}

class MyThread extends Thread {// 线程类
	public void run() {
		for (int i = 1; i <= 20; i++) {// 线程任务
			// 获得当前线程的线程名称
			System.out.println(Thread.currentThread().getName() + " - " + i);
		}
	}
}

在这里插入图片描述

2.2.2、创建线程的第二种方式--接口Runnable

public class TestSleep {
	public static void main(String[] args) throws InterruptedException {
		
		MyRunnable task = new MyRunnable();
		Thread t2 = new Thread(task);
		t2.start();
	
		// main线程
		for (int i = 1; i <= 20; i++) {// 线程任务
			System.out.println(Thread.currentThread().getName() + " - " + i);
			
		}
	}

}

class MyRunnable implements Runnable {
	public void run() {
		for (int i = 1; i <= 20; i++) {// 线程任务
			// 获得当前线程的线程名称
			System.out.println(Thread.currentThread().getName() + " - " + i);
		}
	}
}

在这里插入图片描述

匿名内部类

public class TestSleep {
	public static void main(String[] args) throws InterruptedException {
		
		MyRunnable task = new MyRunnable();
		Thread t3 = new Thread(new Runnable(){
             public void run() {
                for (int i = 1; i <= 20; i++) {// 线程任务
                    // 获得当前线程的线程名称
                    System.out.println(Thread.currentThread().getName() + " - " + i);
                }
             }   
            
        });
		t3.start();
	
		// main线程
		for (int i = 1; i <= 20; i++) {// 线程任务
			System.out.println(Thread.currentThread().getName() + " - " + i);
			
		}
	}

}


2.2.3、创建线程的第三种方式--实现Callable接口(JDK8新特性)

  • 优点:这种方式实现的线程可以获得线程的返回值

  • 缺点:效率比较低

Callable接口的定义
public interface Callable<V> {
	public V call() throws Exception;
}

	1. JDK5加入,与Runnable接口类似,实现之后代表一个线程任务
    2. 可以获得返回值
    3. 可以抛出异常
    4. 需要借助FutureTask类
    5. 重写call()方法 相当于Runnable接口的run()

import javafx.concurrent.Task;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Thread3 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        //创建一个"未来任务类"对象
        FutureTask task = new FutureTask(new Callable() {
            @Override
            public Object call() throws Exception {
                //call()相当于run()

                System.out.println("begin");
                Thread.sleep(1000*10);
                System.out.println("end");
                int a = 100;
                int b = 200;
                return a + b;//结果自动装箱成Integer
            }
        });


        //创建线程对象
        Thread t = new Thread(task);
        //启动线程
        t.start();

        //在mian方法中获得返回结果
        Object obj = task.get();//该方法会导致main方法受阻塞,下面的代码必须等get方法结束才能执行

        System.out.println("ok");//该代码会等待get方法结束才能执行
    }
}

三、线程的生命周期

3.1、线程的基本状态

在运行状态遇到阻塞事件,例如接受用户键盘输入,或者sleep方法等,此时线程会进入阻塞状态,阻塞状态的线程会放弃当前占有的时间片,阻塞解除后回到就绪状态继续抢夺时间片,抢夺后继续上次的代码接着运行。

在这里插入图片描述

3.2、线程的调度

1.抢占式调度模型

​ 那个线程的优先级高,抢到的CPU时间片的概率就会高一些,Java采用的就是该模式

2.均分式调度模型

​ 平均分配cup时间片,每个线程占有的cup时间片时间长度一样,平均分配,一切平等

实例方法:

​ viod setPriority(int newPriority) 设置线程的优先级

​ int getPriority()获得线程优先级

​ 最低优先级:1

​ 默认优先级:5

​ 最高优先级:10

3.3、常见方法

休眠:

  • public static void sleep(long millis)
  • 当前线程主动休眠millis毫秒(注意:1000毫秒=1秒)
  • 作用:让当前线程进入休眠,进入阻塞状态,放弃占有的CPU时间片,让给其他线程使用
//public static void sleep
public class TestSleep {
	public static void main(String[] args) throws InterruptedException {
		
			try {
				Thread.sleep(1000*5);// 如果写在run方法里,那么只能通过tryCatch处理异常。因为run方法在父类中没有抛出任何异常,子类不能比父类抛出更多的异常
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println( " - ");//5秒后输出 -
		
	}
}

面试题:

public class TestSleep {
	public static void main(String[] args) throws InterruptedException {
		Thread t1 = new Thread(new Task());
  		t1.start();
        
			try {
				t1.sleep(1000*5);
                // 问题:该代码会让t1线程进入休眠吗?
                //答案:不会,原因是sleep是静态方法,该行代码会在运行时会制动转换成thread.sleep(1000*5),让当前线程进入休眠,也就是main线程进入休眠
                
                
                
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println( " - ");
		
	}
}

class Task implements Runnable {
	public void run() {
		for (int i = 1; i <= 20; i++) {
			System.out.println(Thread.currentThread().getName() + " - " + i);
		}
	}
}

终止线程的睡眠

终止睡眠,不是终止线程
public class TestSleep {
	public static void main(String[] args) throws InterruptedException {
		Thread t = new Thread(new Task());
  		t.start();
        //希望5秒后t线程能够醒来
			try {
				Thread.sleep(1000*5);
                
                
                
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
        t.interrupt();//让线程睡眠代码报异常
		
	}
}
class Task implements Runnable {//线程类
	public void run() {//线程任务
        
        System.out.println(Thread.currentThread().getName() +"begin");
		try {
				t1.sleep(1000*60*60*24*365);
  
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			 System.out.println(Thread.currentThread().getName() +"end");
	}
}

线程让位:

  • public static void yield()

  • 当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片

  • 不是阻塞方法

  • 回到就绪状状态,还是有可能再次抢到cup时间片

  • 线程的礼让,让出cpu让其他线程执行,在cpu充足的情况下礼让不一定成功

    * //public static void yield()
      public class TestYield {
      	public static void main(String[] args) {
      		Thread t1 = new Thread(new Task());
      		t1.start();
    
    		//main线程
    		for (int i = 1; i <= 20; i++) {//线程任务
    			System.out.println(Thread.currentThread().getName() + " - " + i);
    			if (i % 10 == 0) {//放弃的条件(特定时候放弃时间片)
    				System.out.println("main主动放弃了!");
    				Thread.yield();//放弃!主动放弃当前持有的时间片,回到就绪状态,进入下一次时间片的竞争!
    			}
    		}
    	}
    
    }
    
    class Task implements Runnable {//线程类
    	public void run() {//线程任务
    		for (int i = 1; i <= 20; i++) {
    			System.out.println(Thread.currentThread().getName() + " - " + i);
    		}
    	}
    }
    

线程插队:

  • public final void join()

  • 实例方法

  • 当前线程进入阻塞状态,让加入的线程执行,直到该线程结束再执行原来线程

  • 类似把多线程编程单线程

      public class TestJoin {
      	public static void main(String[] args) throws InterruptedException {
      		Thread t0 = new Thread(new Task());
      		t0.start();
    
    		for (int i = 1; i <= 10; i++) {//打印10个数字
    			System.out.println(Thread.currentThread().getName() + " - " + i);//打印线程名字和数字
    			if (i == 5) {//将t0加入到main线程执行流程中,等待t1线程执行结束后!main再进行竞争时间片!
    				t0.join();//无限期等待!等待条件为调用join方法的线程执行完毕后!再进入就绪状态,竞争时间片!
    			}
    		}
    	}
    
    }
    
    class Task implements Runnable {//线程实现类——Thread-0
    	public void run() {//线程任务
    		for (int i = 1; i <= 10; i++) {//打印10个数字
    			System.out.println(Thread.currentThread().getName() + " - " + i);//打印线程名字和数字
    		}
    	}
    }
    

修改线程名字

 public class TestJoin {
  	public static void main(String[] args) throws InterruptedException {
  		Thread t0 = new Thread(new Task());
        System.out.println(t0.getName() );//默认名字Thread-0
        //修改名字
        t0.setName("ttt")
        System.out.println(t0.getName() );//ttt
        //获取当前线程对象,改代码出现在主线程中,获得线程就是主线程
        Thread t = Thread.currentThread()
  		t0.start();

		
	}

}

class Task implements Runnable {//线程实现类——Thread-0
	public void run() {//线程任务
		for (int i = 1; i <= 10; i++) {//打印10个数字
			System.out.println(Thread.currentThread().getName() + " - " + i);//打印线程名字和数字
		}
	}
}

终止线程的执行

public class TestSleep {
	public static void main(String[] args) throws InterruptedException {
		
        Task ta = new Task()
        Thread t = new Thread(ta);
  		t.start();
        //希望5秒后终止t线程
			try {
				Thread.sleep(1000*5);
           
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		方式一:
            ta.runKey = false;	
        方式二:
            t.stop();//不建议使用,会丢数据
		
	}
}
class Task implements Runnable {//线程类
	
    boolean runKey =  true;  
	public void run() {//线程任务
		for (int i = 1; i <= 10; i++) {//打印10个数字
			if(runKey){
                System.out.println(Thread.currentThread().getName() + " - " + i);//打印线程名字和数字
            try {
				t1.sleep(1000);
  
			} catch (InterruptedException e) {
				e.printStackTrace();
			}else{
                //为保存的数据可以在这保存
                return;
            }
            
		}
	}

}

四、线程安全

4.1、线程安全问题

  • 什么时候数据在多线程并发的环境下会存在安全问题?

三个条件:

​ 1.多线程并发

​ 2.有共享数据

​ 3.共享数据有修改的行为

  • 线程安全是处理成员变量问题的(实例变量 :在堆中 静态变量:在方法区)

  • 在Java三大变量中
    实例变量 :在堆中
    静态变量:在方法区
    局部变量:在栈中
    以上变量,只有局部变量不会存在线程安全问题,因为局部变量不共享(一个线程一个栈)。

在这里插入图片描述

4.2、解决线程安全问题的方法

同步和异步的理解

  • 异步编程模型

    线程t1和线程t2,各自执行各自的,谁也不需要等待谁,其实就是多线程并发,效率比较高

  • 同步编程模型

    线程t1和线程t2,线程t1执行的时候,必须等待t2线程执行结束,或者线程t2执行的时候,必须等待t1线程执行结束,连哥哥线 程之间发生了等待关系,效率比较低,线程排队执行。

解决线程安全的核心思想:对共享的核心资源加锁,每一次只能允许一个线程访问,访问结束,才允许进入新的线程

4.2.1、同步代码块

注意:继承方式创建线程时,数据要加static实现代码块共享数据,锁对象也要加static实现代码块共享锁对象,而实现方式创建天然共享数据和共享锁对象

synchronized(共享资源的对象) {/
		//代码(原子操作)
	}

注意:
每个对象都有一个互斥锁标记,用来分给线程的
只有拥有对象互斥锁标记的线程,才能进入对该对象加锁的同步代码块
线程退出同步代码块时,会释放相应的互斥锁标记
线程的状态(阻塞)

    
    对于锁对象要求:理论上,锁对象只要对于当前执行的线程来说是同一个对象即可
    如字符串"1",常量池中只有一个"1",但this只会让当前共享资源的线程线程排队
            举例:存在线程t1,t2,t3,t4,t5
            t1,t2,t3共享银行卡资源对象act
            如果锁对象为this,排队的只有t1,t2,t3
            如果锁对象为"1",则全部排队

举例:

//创建账户
public class Account {
    @SuppressWarnings("all")
    private  String actno;
    private  double balance;

    public Account(String actno, double balance) {
        this.actno = actno;
        this.balance = balance;
    }

    public Account() {
    }

    public String getActno() {
        return actno;
    }

    public void setActno(String actno) {
        this.actno = actno;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    //取款方法
    public  void withdraw(double mongey){
        synchronized (this){//取款前余额,此时this表示任何对象都可以来调用这个方法,任何对象都共用一把锁
                        double before = this.balance;

                        //取款后余额
                        double after = before - mongey;

                        //更新余额
                        this.setBalance(after);

        }

    }
}

//创建线程对象
public class AccountThread extends Thread{

    private Account act;
    public  AccountThread(Account act){
        this.act = act;
    }
    public void run (){
        double money = 5000;
        act.withdraw(money);
        System.out.println(Thread.currentThread().getName() + "取款成功,余额="+act.getBalance());
    }
}

//测试
public class AccountThread extends Thread{

    private Account act;
    public  AccountThread(Account act){
        this.act = act;
    }
    public void run (){
        double money = 5000;
        act.withdraw(money);
        System.out.println(Thread.currentThread().getName() + "取款成功,余额="+act.getBalance());
    }
}

//扩大同步范围

//测试板块
public class Test {
    public static void main(String[] args) {
        Account act = new Account("act-01",10000);

        //创建2个线程
        Thread t1 = new AccountThread(act);
        t1.setName("t1");
        Thread t2 = new AccountThread(act);
        t2.setName("t2");//启动线程取款
        t1.start();
        t2.start();
    }
    
    
//线程板块    
public class AccountThread extends Thread{

    private Account act;
    public  AccountThread(Account act){
        this.act = act;
    }
    publdouble money = 5000;
        synchronized (act){
            act.withdraw(money);//把取钱的方法上锁,这里不能写this,this分别是t1,t2不同对象只能是act公用
        }
        System.out.println(Thread.currentThread().getName() + "取款成功,余额="+act.getBalance());
    }
}
    
//  账户板块  
public class Account {
    @SuppressWarnings("all")
    private  String actno;
    private  double balance;

    public Account(String actno, double balance) {
        this.actno = actno;
        this.balance = balance;
    }

    public Account() {
    }

    public String getActno() {
        return actno;
    }

    public void setActno(String actno) {
        this.actno = actno;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    //取款方法
    public  void withdraw(double mongey){
        //取款前余额
        double before = this.balance;

        //取款后余额
        double after = before - mongey;

        //更新余额
        this.setBalance(after);

       

    }
}

4.2.2、同步方法

synchronized 返回值类型 方法名称(形参列表0) {
    
    //对当前对象(this)加锁,只能是this
		//代码(原子操作)
	}

缺点:
    1. 整个方法都需要同步,会扩大同步范围,导致程序执行效率降低,所以不常用
    2.静态方法 锁加在类上,锁是类名.class
    3.非静态方法 锁加在当前对象上  默认this
优点:
    代码量少

已知JDK中线程安全的类:
StringBuffer
Vector
Hashtable
以上类中的公开方法,均为synchonized修饰的同步方法synchronized实例
//测试板块
public class Test {
    public static void main(String[] args) {
        Account act = new Account("act-01",10000);

        //创建2个线程
        Thread t1 = new AccountThread(act);
        t1.setName("t1");
        Thread t2 = new AccountThread(act);
        t2.setName("t2");//启动线程取款
        t1.start();
        t2.start();
    }
    
    
//线程板块    
public class AccountThread extends Thread{

    private Account act;
    public  AccountThread(Account act){
        this.act = act;
    }
    publdouble money = 5000;
       
        act.withdraw(money);
       
        System.out.println(Thread.currentThread().getName() + "取款成功,余额="+act.getBalance());
    }
}
    
//  账户板块  
public class Account {
    @SuppressWarnings("all")
    private  String actno;
    private  double balance;

    public Account(String actno, double balance) {
        this.actno = actno;
        this.balance = balance;
    }

    public Account() {
    }

    public String getActno() {
        return actno;
    }

    public void setActno(String actno) {
        this.actno = actno;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    //取款方法
    public synchronized void withdraw(double mongey){
        //取款前余额
        double before = this.balance;

        //取款后余额
        double after = before - mongey;

        //更新余额
        this.setBalance(after);

       

    }
}

4.2.3、Lock接口

  • JDK5加入,与synchronized比较,显示定义,结构更灵活
  • 提供更多使用性方法,功能更强大、性能更优越
  • 常用方法:
    • void lock() //获取锁,如锁被占用,则等待
    • boolean tryLock() //尝试获取锁(成功返回true。失败返回false,不阻塞)
    • void unlock() //释放锁
Lock接口的使用
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TestLocks {
	public static void main(String[] args) {
		Test obj = new Test();

		Thread t1 = new Thread(new MyTask(obj));
		Thread t2 = new Thread(new MyTask2(obj));
	
		t1.start();
		t2.start();
	}

}

class Test {
	// 接口引用 实现类对象 创建一把锁
	Lock lock = new ReentrantLock();// Test里有一把锁的属性,就是Test自身的锁!
	// ReentrantLock rl = new ReentrantLock();
	// 第一、使用Lock,需要明确的写上锁和释放锁!
	// 第二、为了避免拿到锁的线程在运行期间出现异常,导致程序终止,没有释放锁!应用try{}finally{}来保证,无论正确执行与否,最终都会释放锁!

	public void method() {
		System.out.println(Thread.currentThread().getName() + "进入到上锁的方法里!");
		try {
			lock.lock();// 显示的写上 在此处获得锁
			// 模拟程序出错!
			// int a = 10/0;
			// method();不要出现无穷递归!容易内存溢出!导致锁一直没有释放!
			System.out.println(Thread.currentThread().getName() + "退出了上锁的方法里!");
		} finally {
			// 显示的写上,此处释放锁
			lock.unlock();
		}
	}

}

class MyTask implements Runnable {
	Test obj;// 共享资源对象

	public MyTask(Test obj) {
		this.obj = obj;
	}
	
	public void run() {
		obj.method();
	}

}

class MyTask2 implements Runnable {
	Test obj;// 共享资源对象

	public MyTask2(Test obj) {
		this.obj = obj;
	}
	
	public void run() {
		obj.method();
	}

}
重入锁

ReenTrantLock: Lock接口的实现类,与synchronized一样具有互斥锁功能

在这里插入图片描述

读写锁

ReentrantReadWriteLock:
一种支持一写多读的同步锁,读写分离,可分别分配读锁、写锁
互斥规则:
写-写: 互斥,阻塞
读-写: 互斥,读阻塞写、写阻塞读
读-读: 不互斥、不阻塞
在读操作远远高于写操作的环境中,可在保障线程安全的情况下,提高运行效率

在这里插入图片描述

读写锁案例
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class TestReadWriteLock {
    public static void main(String[] args) {
        Student student = new Student();//共享资源对象
        ExecutorService executorService = Executors.newFixedThreadPool(20);

        WriteTask writeTask = new WriteTask(student);//写线程任务
        ReadTask readTask = new ReadTask(student);//读线程任务
        //执行的两个赋值的线程任务
        Long start = System.currentTimeMillis();//开始时间(毫秒值)
        executorService.submit(writeTask);
        executorService.submit(writeTask);
    
        for (int i = 1; i <= 18; i++) {
            executorService.submit(readTask);
        }
    
        //停止线程池,但是不停止已提交的任务!等已提交任务都执行完毕!
        executorService.shutdown();
    
        //询问线程池,任务结束了吗?
        while (true) {
            System.out.println("任务结束了吗?");
            if (executorService.isTerminated() == true) {//证明线程池里的任务都执行完毕
                break;
            }
        }
    
        Long end = System.currentTimeMillis();//结束时间
        System.out.println(end - start);
    }

}

class Student {
    private int age;        //年龄
//    Lock lock = new ReentrantLock();//读和写的操作下,都锁住,性能过低!
    //有两把锁
    ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
    ReentrantReadWriteLock.WriteLock readLock = reentrantReadWriteLock.writeLock();//读锁
    ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();//写锁
    //赋值——写操作
    public void setAge(int age) {
        writeLock.lock();
        try {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.age = age;
        } finally {
            writeLock.unlock();
        }
    }
    //取值——读操作
    public int getAge() {
        readLock.lock();
        try {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return this.age;
        } finally {
            readLock.unlock();
        }
    }
}

//写操作任务
class WriteTask implements Callable {
    Student student;

    public WriteTask(Student student) {
        this.student = student;
    }
    
    public Student call() throws Exception {
        student.setAge(100);
        return null;
    }

}

class ReadTask implements Callable {
    Student student;

    public ReadTask(Student student) {
        this.student = student;
    }
    
    public Student call() throws Exception {
        student.getAge();
        return null;
    }

}

面试题1
问:doOther方法执行的时候需要等待doSome方法的结束吗?
答:不需要,下面的方法没有锁

public class Test {
    public static void main(String[] args) {
       MyClass mc = new MyClass();

      
        Thread t1 = new MyThread(mc);
        t1.setName("t1");
        Thread t2 = new MyThread(mc);
        t2.setName("t2");
        t1.start();
        t2.start();
    }
    


class MyThread extends Thread{

    private MyClass mc;
    public  MyThread( MyClass mc){
        this.mc = mc;
    }
  public void run(){
      if(Thread.currentThread().getName().equals("t1")){
          mc.doSome();
      }
      if(Thread.currentThread().getName().equals("t2")){
          mc.doOther();
      }
  }
}
    
  
class  MyClass {
 
    public synchronized void doSome(){
       System.out.println("doSome");
    }
    
     public  void doOher(){
       System.out.println("doOher");
    }
}
面试题2
问:doOther方法执行的时候需要等待doSome方法的结束吗?
答:需要,都有锁

public class Test {
    public static void main(String[] args) {
       MyClass mc = new MyClass();

      
        Thread t1 = new MyThread(mc);
        t1.setName("t1");
        Thread t2 = new MyThread(mc);
        t2.setName("t2");
        t1.start();
        t2.start();
    }
    


class MyThread extends Thread{

    private MyClass mc;
    public  MyThread( MyClass mc){
        this.mc = mc;
    }
  public void run(){
      if(Thread.currentThread().getName().equals("t1")){
          mc.doSome();
      }
      if(Thread.currentThread().getName().equals("t2")){
          mc.doOther();
      }
  }
}
    
  
class  MyClass {
 
    public synchronized void doSome(){
       System.out.println("doSome");
    }
    
     public synchronized void doOher(){
       System.out.println("doOher");
    }
}
面试题3
问:doOther方法执行的时候需要等待doSome方法的结束吗?
答:不需要,锁对象不唯一

public class Test {
    public static void main(String[] args) {
       MyClass mc1 = new MyClass();
	   MyClass mc2 = new MyClass();
      
        Thread t1 = new MyThread(mc1);
        t1.setName("t1");
        Thread t2 = new MyThread(mc2);
        t2.setName("t2");
        t1.start();
        t2.start();
    }
    


class MyThread extends Thread{

    private MyClass mc;
    public  MyThread( MyClass mc){
        this.mc = mc;
    }
  public void run(){
      if(Thread.currentThread().getName().equals("t1")){
          mc.doSome();
      }
      if(Thread.currentThread().getName().equals("t2")){
          mc.doOther();
      }
  }
}
    
  
class  MyClass {
 
    public synchronized void doSome(){
       System.out.println("doSome");
    }
    
     public synchronized void doOher(){
       System.out.println("doOher");
    }
}
面试题3
问:doOther方法执行的时候需要等待doSome方法的结束吗?
答:需要,synchronized出现在静态方法上,为类锁,锁对象为一类,不是一个实例

public class Test {
    public static void main(String[] args) {
       MyClass mc1 = new MyClass();
	   MyClass mc2 = new MyClass();
      
        Thread t1 = new MyThread(mc1);
        t1.setName("t1");
        Thread t2 = new MyThread(mc2);
        t2.setName("t2");
        t1.start();
        t2.start();
    }
    


class MyThread extends Thread{

    private MyClass mc;
    public  MyThread( MyClass mc){
        this.mc = mc;
    }
  public void run(){
      if(Thread.currentThread().getName().equals("t1")){
          mc.doSome();
      }
      if(Thread.currentThread().getName().equals("t2")){
          mc.doOther();
      }
  }
}
    
  
class  MyClass {
 
    public synchronized static void doSome(){
       System.out.println("doSome");
    }
    
     public synchronized static void doOher(){
       System.out.println("doOher");
    }
}

线程的逻辑代码

run()和start()区别

  • start()方法的作用是:启动一个分支线程,在jvm中开辟一个新的栈空间,这但代码任务完成后,瞬间就结束了。
  • 调用底层:start()》start0()》底层c/c++
  • run()等于是一个普通方法的调用,不会开启新线程

4.3、死锁

  • 当第一个线程拥有A对象锁标记,并等待B对象锁标记,同时第二个线程拥有B对象锁标记,并等待A对象标记时,产生死锁
  • 一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已经拥有的锁标记,由此可能造成死锁
public class DeadLock {
    public static void main(String[] args) {
        Object o1 = new Object();
        Object o2 = new Object();

        Thread t1 = new MyThread1(o1,o2);
        Thread t2 = new MyThread2(o1,o2);

        t1.start();
        t2.start();
    }
}
class MyThread1 extends Thread{
    Object o1;
    Object o2;
    public  MyThread1(Object o1,Object o2){
        this.o1 = o1;
        this.o2 = o2;
    }

    public void run(){
        synchronized (o1){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            synchronized (o2){

            }
        }
    }
}
class MyThread2 extends Thread{
    Object o1;
    Object o2;
    public  MyThread2(Object o1,Object o2){
        this.o1 = o1;
        this.o2 = o2;
    }

    public void run(){
        synchronized (o2){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            synchronized (o1){

            }
        }
    }
}

4.4、开发中解决线程安全的方案

synchronized会让程序执行效率降低,用户体验感不好在不得已的情况下再选择线程同步机制。

  • 第一种方案:尽量使用局部变量代替实例变量和静态变量
  • 第二种方案:如果是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享
  • 第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候只能选择synchronized,线程同步机制

4.5、释放锁

  1. 当前线程的同步方法、同步代码块执行结束释放锁

  2. 当前线程的同步方法、同步代码块执行遇到break、return

  3. 当前线程的同步方法、同步代码块执行遇到error、exception导致异常结束

  4. 当前线程的同步方法、同步代码块执行遇到线程对象执行wait()方法,当前线程暂停释放锁

  5. 线程执行同步方法、同步代码块时,遇到Thread.sleep()、Thread.yield()当前线程暂停但不会释放锁

  6. 线程执行同步代码块时,其他线程调用该线程的suspend()方法将该线程挂起,该线程不会释放锁(提示:应避免使用挂起suspend()、结束挂起resume()来控制线程

五、守护线程

守护线程分类

Java语言中的线程分为两个类:

​ 用户线程和守护线程(后台线程)

​ 守护线程最具代表性的是:垃圾回收线程

守护线程特点:

​ 1.一般是个死循环

​ 2.所有的用户线程结束,守护线程自动结束

​ 注意:主线程main方法是一个用户线程

场景

​ 1.定时备份数据,会用到定时器,并且可以将定时器设置为守护线程

语法:

线程启动前加上
线程对象名.setDaemon(true)

六、定时器

作用:间隔特定的时间,执行特定的程序

实现方式:

​ 1.sleep方法

​ 2.在java类库中已经有一个定时器:java.util.Timer 可以直接使用

​ 3.目前使用最多的是Spring框架提供的SpringTask框架

代码演示


import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;


public class TimerTest {
    public static void main(String[] args) throws Exception {

        //c创建定时器对象
        Timer timer = new Timer();
        //Timer timer = new Timer(true);//守护线程方式

        //指定定时任务
        SimpleDateFormat sim = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date firstTime  = sim.parse("2022-07-06 01:14:00");

        timer.schedule(new LogTimerTask(),firstTime,1000*1);



    }
}
//编写一个定时任务类,记录日志的
class  LogTimerTask extends TimerTask{
    @Override
    public void run() {
        //编写需要执行的任务
        SimpleDateFormat sim = new SimpleDateFormat("yyyy-MM-dd HH : mm:ss:SS");
        String strTime = sim.format(new Date());
        System.out.println(strTime + "ok");
        System.out.println("ok");

    }
}

六、线程通信

基本介绍

注意:

  • 线程通信只能出现在同步代码块或者同步方法中,lock也不行
  • 这三个方法的调用者必须是锁对象,当是this的时候可以省略
  • 这三个方法定义在Object类
	等待:暂停当前线程使得当前线程进入阻塞状态并释放锁
        public final void wait()
        public final void wait(long timeout)
        必须在对obj加锁的同步代码块中。在一个线程中,调用obj.wait()时,此线程会释放其拥有的所有锁标记。同时此线程阻塞在	
        obj的等待队列中。释放锁,进入等待队列

    通知:唤醒其他线程
        public final void notify() //释放一个,唤醒优先级最高的线程
        public final void notifyAll() //释放全部,唤醒之前沉睡的所有线程
        必须在对obj加锁的同步代码块中。从obj的Waiting中释放一个或全部线程。对自身没有任何影响
        
       注意:
        1.wait和notify方法不是线程对象的方法,是Java对象中任何一个Java对象都有的方法,是Object类中自带的
        2.wait和notify方法不是通过线程对象调用的
        3.Object o = new Object();
				o.wait();
				表示让正在o对象上活动的线程t进入等待状态,无期限等待,并且释放掉t线程之前占有的o对象的锁,
                直到用o.notify()唤醒,该唤醒只会通知,不会释放o对象上之前占有的锁
        4.wait和notify方法是建立在线程同步的基础之上
        
        复杂:一个线程持有A对象的锁,需要B对象的锁, 另一个线程持有B、想要A
        简单:一个线程持有A对象的锁,另一个线程也想要!阻塞
        
        情况1:三个线程抢锁,其中一个线程或两个线程把所让给了其他线程(wait),
                然后另外的线程就开始抢锁直至执行结束——死锁(没有notify唤醒)
        情况2:三个线程抢锁,其中一个线程或两个线程把所让给了其他线程(wait),
                然后另外的线程就开始抢锁并其中的一个或者最后一个notify唤醒——有可能出现死锁(因为notify是随机唤醒)
        情况3:三个线程抢锁,其中一个线程或两个线程把所让给了其他线程(wait),
                然后另外的线程就开始抢锁并其中的一个或者最后一个notifyAll唤醒——正常执行(因为notifyAll是唤醒全部等待的					线程)

实现生产者和消费者

import java.util.ArrayList;
import java.util.List;

public class Text {
    public static void main(String[] args) {

        /*
        需求:仓库List集合,且只能存储一个元素,个数为0表示仓库空了,效果是生产一个消费一个
         */
    //创建共享仓库对象
    List list = new ArrayList();


    //创建两个线程对象
     Thread t1 = new Thread(new Produce(list));
     Thread t2 = new Thread(new Consumer(list));

        t1.setName("生产者线程");
        t2.setName("消费者线程");

        t1.start();
        t2.start();
    }
}
//生产线程
class Produce implements Runnable{
    private List list;

    public Produce(List list) {
        this.list = list;
    }

    @Override
    public void run() {
        //一直生产,使用死循环模拟
        while (true){
            //给仓库对象加锁
            synchronized (list){
                if(list.size() > 0){
                    //当前线程进入等待状态,并且释放list的锁
                    try {
                        list.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }

                Object obj = new Object();
                list.add(obj);
                System.out.println(Thread.currentThread().getName() + "==="+obj);
                //唤醒消费者消费
                list.notify();
            }

        }
    }
}

//消费线程
class Consumer implements Runnable{
    private List list;

    public Consumer(List list) {
        this.list = list;
    }

    @Override
    public void run() {
       //一直消费
        while (true){
            synchronized (list){
                if(list.size() == 0){
                    //仓库已空,当前线程进入等待状态,并且释放list的锁
                    try {
                        list.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                //有数据,能消费
                Object obj = list.remove(0);
                System.out.println(Thread.currentThread().getName() + "==="+obj);
                //唤醒生产者生产
                list.notify();
            }

        }
    }
}

七、线程池

7.1、线程池概念

  • 线程容器,可设定线程分配的数量上限
  • 将预先创建的线程对象存入池中,并重用线程池中的线程对象
  • 避免频繁的创建和销毁线

7.2、程池原理

​ 将任务提交给线程池,由线程池分配线程、运行任务,并在当前任务结束后复用线程

在这里插入图片描述

7.3、获取线程池

常用的线程池接口和类(所在包java.util.concurrent):

  • Executor: 线程池的顶级接口
  • ExecutorService: 线程池接口,可通过submit(Runnable task)提交任务代码
  • Executors工厂类: 通过此类可以获得一个线程池
  • 通过newFixedThreadPool(int nThreads) 获得固定数量的线程池。参数: 指定线程池中线程的数量
  • 通过newCachedThreadPool() 获取动态数量的线程池,如不够创建新的,没有上限

7.3.1、newFixedThreadPool()的使用

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestCallable {
	public static void main(String[] args) {
		ExecutorService es = Executors.newFixedThreadPool(3);
		MyTask task = new MyTask();
		es.submit(task);
	}
}

class MyTask implements Callable<Integer> {
	public Integer call() throws Exception {
		for (int i = 0; i <= 20; i++) {
			if (i == 30) {
				Thread.sleep(1000);
			}
			System.out.println(Thread.currentThread().getName() + ":" + i);
		}
		return null;
	}
}

7.3.2、Future接口

概念: 异步接收ExecutorService.submit() 所返回的状态结果,当中包含了call() 的返回值
方法: V get() 以阻塞形式等待Future中的异步处理结果(call()的返回值)
线程的同步
同步:
形容一次方法调用,同步一旦开始,调用者必须等待该方法返回,才能继续
注意:单条执行路径

在这里插入图片描述

线程的异步
异步:
形容一次方法调用,异步一旦开始,像是一次消息传递,调用者告知之后立即返回。二者竞争时间片,并发执行
注意:多条执行路径

在这里插入图片描述

Future接口的使用

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class TestFuture {
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		ExecutorService es = Executors.newFixedThreadPool(2);
		

		MyCall1 mc1 = new MyCall1();
		MyCall2 mc2 = new MyCall2();
		//通过submit执行提交的任务,Future接受返回的结果
		Future<Integer> result1 = es.submit(mc1);
		Future<Integer> result2 = es.submit(mc2);
		//通过Future的get方法,获得线程执行完毕后的结果
		Integer value1 = result1.get();
		Integer value2 = result2.get();
		
		System.out.println(value1 + value2);
	}

}
//计算1~50的和
class MyCall1 implements Callable<Integer> {
	public Integer call() throws Exception {
		Integer sum = 0;
		for (int i = 1; i <= 50; i++) {
			sum += i;
		}
		return sum;
	}
}
//计算1~50的和
class MyCall2 implements Callable<Integer> {
	public Integer call() throws Exception {
		Integer sum = 0;
		for (int i = 1; i <= 50; i++) {
			sum += i;
		}
		return sum;
	}
}


7.3.3、newCachedThreadPool()的使用



import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestThreadPool {
	public static void main(String[] args) {
		// 线程池(引用) ---->Executors工具类(工厂类)
		// ExecutorService es = Executors.newFixedThreadPool(3);//手动限定线程池里的线程数量
		ExecutorService es = Executors.newCachedThreadPool();// 动态获取数量线程池
		// 1.创建任务对象
		MyTask1 task = new MyTask1();
		// 2.将任务提交到线程池,由线程池调度、执行
		es.submit(task);// Runnable类型的对象
		es.submit(task);
		es.submit(task);
	}
}

// 线程任务
class MyTask1 implements Runnable {
	public void run() {
		for (int i = 1; i <= 20; i++) {
			System.out.println(Thread.currentThread().getName() + ":" + i);
		}
	}
}

posted on   你微笑时丶好美  阅读(18)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 我与微信审核的“相爱相杀”看个人小程序副业
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· spring官宣接入deepseek,真的太香了~
点击右上角即可分享
微信分享提示