多线程Thread

多线程

三种创建方法

  • 获取当前线程的名字
    Thread.currentThread().getName();
    new Thread(tr,a);
    当有多个线程的时候,可以给他赋予名字,a就是线程的名字
  1. 继承Thread类,继承thread类,重写run方法,编写执行体,new一个继承thread的类的对象,用.start()方法启动线程

    假设一个class Tr extends继承了Thread类
    Tr tr = new Tr();
    tr.start();
    //开启线程
    
  2. 实现Runnable接口(推荐使用),他是一个函数式接口,里面只有一个run方法
    在Runnable中有两种状态Ready和Running,Ready就是准备好了等待CPU调度,Running就是真正的在运行,由CPU调度

    假设一个class Tr implements了Runnable接口
    Tr tr = new Tr();
    new Thread(tr).start();
    //或者用lambda表达式
    new Thread(()->{执行的代码块}).start()
    //开启线程
    
  3. 实现Callable接口(了解就好)

  4. public class Tr implements Runnable{
        @Override
        public void run(){
            
        }
        public static void main(String[] args){
            
        }
    }
    
  • 其他知识点

    • main方法就是main线程,他是主线程,也可以通过Thread.方法
    • 线程调用run方法就是普通的调用方法,而线程调用start方法呢就是开启了多线程, 他是跟主线程交替执行的
    • 同时执行,因为我们电脑只有一个CPU,线程的执行由cpu调度安排,所以线程可能不执行
    • lambda表达式可以减少内部类的创建,提高代码效率
      但是他仅限于函数式接口,就是只有一个方法的接口

线程的状态

线程的状态

新生,就绪,运行,阻塞,结束

new xxx runnable waiting destroy

线程停止

  • 建议线程正常停止--->利用次数,不建议死循环。

  • 建议使用标志位--->设置一个标志位 flag
    在线程的run方法中使用 while(flag)

  • 推荐让线程自己停下来

  • 不要使用stop或者destroy等过时或者JDK不建议使用的方法
    还有方法上使用了@Deprecated注解的,不建议使用

  • package com.threadd;
    
    public class TestThread implements Runnable{
    
    	private boolean flag = true;
    	
    	
    	@Override
    	public void run() {
    		// TODO Auto-generated method stub
    		int i = 0;
    		while(flag){
    			System.out.println("线程执行了"+i+++"次----");
    		}
    		
    	}
    
    	public static void main(String[] args) {
    		// TODO Auto-generated method stub
    		TestThread tr = new TestThread();
    		new Thread(tr).start();
    		
    		for(int i = 0;i<999;i++){
    			if(i==900){
    				tr.stop();
    				System.out.println("次线程停止了!");
    			}
    			System.out.println("这是主线程执行的第"+i+"次////");
    		}
    
    	}
    	
    	public void stop(){
    		this.flag = false;
    	}
    
    }
    

sleep线程休眠

package com.threadd;

import java.text.SimpleDateFormat;
import java.util.*;

public class TestThread02 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
//		try {
//			tenDown();
//		} catch (InterruptedException e) {
//			// TODO Auto-generated catch block
//			e.printStackTrace();
//		}
		
		//打印当前系统时间
		Date startTime = new Date (System.currentTimeMillis());//获取系统当前时间
		while (true){
			try {
				Thread.sleep(1000);
				System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
				startTime = new Date (System.currentTimeMillis());//更新系统当前时间
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	// 模拟倒计时
	public static void tenDown() throws InterruptedException {
		int num = 10;
		while (true) {
			Thread.sleep(1000);
			System.out.println(num--);
			if (num <= 0) {
				break;
			}
		}
	}
	
}
  • sleep可以模拟网络延时,倒计时等

每个对象都有一把锁,sleep不会释放锁

线程礼让yield

  • 礼让线程,让当前正在执行的线程暂停,但不阻塞
  • 让线程从运行状态转为就绪状态
  • 礼让可能成功也可能不成功,还是要看cpu的心情

插队vip线程join

  • 通过thread.join()插队,会让其他线程阻塞
  • 等vip线程跑完了,其他线程才能执行,少用

观察线程状态

Thread.getState();
Thread.state对象接收

线程的优先级Priority

Priority [praɪˈɒrəti]

  • 获取优先级
    thread.getPriority();
  • 设置优先级 优先级范围是1~10
    thread.setPriority(int arg);
  • 参数越大,优先级越高,不过也要看cpu的调度,也有可能不按优先级运行
  • arg可以是
    Thread.MIN_PRIORITY = 1
    Thread.MAX_PRIORITY = 10
    Thread.NORM_PRIORITY = 5
  • main线程默认优先级是5,但是一般是先启动main线程
    不然无法创造其他线程
  • 如果一个线程高的等待一个线程低的就会导致一个
    优先级倒置的问题,从而引起性能问题

守护线程 daemon

daemon [ˈdiːmən]

Thread.setDaemon(true);
默认是false表示用户线程,正常线程都是用户线程

守护线程可以为死循环,他在其他线程执行完后会自动结束

线程同步

队列和锁

锁机制:synchronized

线程同步必然会引起一个性能降低
要保证安全就会损失性能,要保留性能就会导致不安全
鱼和熊掌不可兼得

线程安全问题

因为他们的内存都是相互不影响的,可能会同时运行
线程不安全的例子:

  • 几个人同时抢票
  • 两个人同时取钱
  • 线程不安全的集合

锁机制 synchronized

synchronize是如何保证线程安全的? 以及他的原理是什么
就当我们给 一个资源加上 synchronize 进行修饰的时候, 这个时候有一个线程要使用他,他就会拿到当前资源的一个锁对象,
这个时候另外一个线程也想用它,此时synchronize就会对其他线程进行阻塞,一直到当前线程将锁释放了,另个线程才能拿到该资源的锁对象. 这样就保证了我的一个安全性

每个对象都有一把锁
关键字 synchronized (同步的) 也属于修饰符,加上关键字的方法就成了同步方法
public synchronized void method(){} 这就是个同步方法
synchronized是隐式锁,他有作用域和自动释放
synchronized有代码块锁和方法锁

同步方法:可以理解成就算有多个线程调用这个方法,也是需要排队,一个个的调用

同步块:可以锁任何对象,记住!锁的一般都是公共资源需要修改的对象

synchronized (obj){ }
obj称之为同步监视器,这个对象一般就是需要被增删改查的一个对象

在使用一个集合添加数据的时候,可能会同时插入到一个位置,导致覆盖了之后最后的长度错误
有一个 juc安全类型的集合可以解决 CopyOnWriterArrayList ,或者使用同步块儿来解决

死锁 deadLock

如何保证资源只有一份:用static修饰对象即可

死锁如何理解:

就是多个线程互相拿着对方需要的资源,形成僵局,导致程序卡死,也就是线程太贪

就比如两个人A和B,然后有两个静态资源手机和电脑
上一秒A想玩手机B想玩电脑,下一秒A又想玩电脑B又想玩手机
如果此时A一个人抱着两个东西的时候,是没办法互换的,所以一个人只能选择拿手机还是拿电脑,不能同时霸占两个东西,这样就会导致死锁的出现

案例:死锁问题

产生死锁的四个必要条件:

  1. 互斥条件:一个资源每次只能被一个进程使用。
  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

Lock锁

java.util.concurrent.locks包下提供的一个Lock接口
其实他的作用等同于 synchronized ,
只不过他是通过显式定义同步锁对象来实现同步
lock是显式锁,需要手动开启和关闭锁
synchronized是隐式锁,他有作用域和自动释放
Lock锁性能更好
优先使用顺序:
Lock > 同步代码块(已经进入了方法体,分配了相应的资源) > 同步方法(在)方法体之外

ReentrantLock 可重入锁
ReentrantLock类实现了Lock
定义lock锁
private final ReentrantLock lock = new ReentrantLock();

一般使用try finally
在try代码块中开头加上 lock.lock(); 开启锁
在finally中 使用 lock.unlock(); 关闭锁

lock锁

线程协作

线程通信

因为所有对象都有一把锁,然后Object类提供了两个方法

方法名 作用
wait() 表示线程一直等待,知道其他线程通知,与sleep不同,他会释放锁
wait(long timeout) 指定等待的毫秒数
notify() 唤醒一个处于等待状态的线程
notifyAll() 唤醒同一个对象上所有调用wait()方法的线程,优先级高的线程优先调度

他们都只能在同步方法或者同步代码块儿中使用,不然会抛出异常

协作模型:生产者、消费者模式
(管程法|买炸鸡的例子),(信号灯法flag 观众看电视,演员录制节目的例子)
生产者,消费者,缓冲区

线程池

  • corePoolSize:核心池大小
  • maxmumPoolSize:最大线程数
  • keepAliveTime:线程没有任务时最多保持多长时间会终止

Executorservice就是线程池的真正接口
Executor是一个工具类,也是线程池的工厂类,

//1.创建服务,创建线程池
//newFixedThreadPool参数为:线程池大小
Executorservice service = Executors.newFixedThreadPool (int 存储的线程数量);
//执行线程
service.execute(线程);
//关闭线程
service.shutdown();

JUC?

JUC就是java.util工具包下的三个concurrent包
java.util.concurrent
java.util.concurrent.atomic
java.util.concurrent.locks

posted @ 2022-03-26 01:12  没有烦恼的猫猫  阅读(54)  评论(0)    收藏  举报