java线程笔记(锁线程通讯线程池)

Posted 皓洲

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java线程笔记(锁线程通讯线程池)相关的知识,希望对你有一定的参考价值。

java线程

文章目录

创建线程方式一:继承Thread类

//创建线程方式一:继承Thread类,重写run()方法,调用start开启线程

//总结:注意,线程开启不一定立即执行,由CPU调度执行

public class Test extends Thread
    @Override
    public void run()
        for(int i=0;i<20;i++)
            System.out.println("我在看代码===="+i);
        
    
    public static void main(String[]args)
        //main线程,主线程
        //创建一个线程对象
        Test test=new Test();
        //调用start()方法开启线程
        test.start();
        for(int i=0;i<200;i++)
            System.out.println("I am eating lunch===="+i);
        
    

创建线程方式2:实现runnable接口

//创建线程方式2:实现runnable接口,重写run方法,执行线程需要丢入runnable接口实现类,调用start方法。

public class Test implements Runnable
    @Override
    public void run()
        for(int i=0;i<20;i++)
            System.out.println("我在看代码===="+i);
        
    
    public static void main(String[]args)
        //main线程,主线程
        //创建一个线程对象
        Test test=new Test();
        //船家女线程对象,通过线程对象来开启我们的线程,代理
        new Thread(test).start();
        
        for(int i=0;i<200;i++)
            System.out.println("I am eating lunch===="+i);
        
    

对比

  • 继承Thread类
    • 子类继承Thread类具备多线程能力
    • 启动线程:子类对象.start()
    • 不建议使用:避免OOP单继承局限性
  • 实现Runnable接口
    • 实现接口Runnable具有多线程能力
    • 启动线程:传入目标对象+Thread对象.strat()
    • 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用。

多个线程操作同一个对象:买火车票

import java.util.*;

//多个线程操作同一个对象:买火车票

//发现问题:多线程操作同一个资源的情况下,线程不安全,数据紊乱
public class Test implements Runnable
	//票数
	private int ticketNums = 10;
	@Override
	public void run()
		while(true)
			if(ticketNums<=0)
				break;
			
			try 
				Thread.sleep(200);
			 catch (InterruptedException e) 
				e.printStackTrace();
			
			System.out.println(Thread.currentThread().getName()+"--->拿到了第"+ticketNums--+"票");
		
	
	public static void main(String[]args)

		Test ticket = new Test();

		new Thread(ticket,"小明").start();
		new Thread(ticket,"老师").start();
		new Thread(ticket,"黄牛").start();
	

输出结果:

出现了两个第7票和第0票第-1票。说明存在并发问题。

同步锁synchronized

  • synchronized方法控制”对象“的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,知道该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行

    缺点:若将一个大的方法声明为synchronized将会影响效率。

import java.util.*;

//多个线程操作同一个对象:买火车票

//发现问题:多线程操作同一个资源的情况下,线程不安全,数据紊乱
public class Test implements Runnable
	//票数
	private int ticketNums = 10;
	@Override
	public void run()
		while(true)
			if(ticketNums<=0)
				break;
			
			try 
				Thread.sleep(200);
			 catch (InterruptedException e) 
				e.printStackTrace();
			
			buy();
		
	
	//加锁解决冲突
	public synchronized void buy()
		if(ticketNums>0)
			System.out.println(Thread.currentThread().getName()+"--->拿到了第"+ticketNums--+"票");
	
	public static void main(String[]args)

		Test ticket = new Test();

		new Thread(ticket,"小明").start();
		new Thread(ticket,"老师").start();
		new Thread(ticket,"黄牛").start();
	

结果:

Lock锁

import java.util.*;
import java.util.concurrent.locks.ReentrantLock;

//多个线程操作同一个对象:买火车票

//发现问题:多线程操作同一个资源的情况下,线程不安全,数据紊乱
public class Test implements Runnable
	//票数
	private int ticketNums = 10;
	private final ReentrantLock lock = new ReentrantLock();
	@Override
	public void run()
		while(true)
		    try 
		        lock.lock();
                if(ticketNums<=0)
                    break;
                
                try 
                    Thread.sleep(200);
                 catch (InterruptedException e) 
                    e.printStackTrace();
                
                System.out.println(Thread.currentThread().getName()+"拿走了第"+ticketNums--+"张票");
            finally 
		        lock.unlock();
            
		
	
	public static void main(String[]args)

		Test ticket = new Test();

		new Thread(ticket,"小明").start();
		new Thread(ticket,"老师").start();
		new Thread(ticket,"黄牛").start();
	

结果:

发现票全是黄牛拿走的,因为黄牛抢到了这把锁,一直买票,最后才释放。

synchronized与Lock的对比

  • Lock是显示锁(手动开启或关闭锁,别忘记关闭锁)synchronized是隐式锁,除了作用域自动释放
  • Lock只有代码块锁,synchronized由代码块锁和方法锁
  • 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。斌且具有很好的拓展性(提供更多子类)
  • 优先使用顺序:
    • Lock>同步代码块(已经进入了方法体,分配了相应的资源)> 同步方法(在方法体之外)

线程通信

  • 应用场景:生产者和消费者问题
    • 假设仓库中只能放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中的产品取走消费。
    • 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,知道仓库中的产品被消费者取走为止。
    • 如果仓库中有产品,则消费者可以将产品取走消费,否则停止消费并等待,知道仓库中再次放入产品为止。
  • 这是一个线程同步问题,生产者和消费者共享一个资源,并且生产者和消费者之间互相依赖,互为条件。
    • 对于生产者,没有生产商品之前,要通知消费者等待,而产生了商品之后,又要马上通知消费者消费
    • 对于消费者,再消费之后,要通知生产者 已经结束消费,需要产生新的产品以供消费。
    • 再生产者问题中,仅有synchronized是不够的
      • synchronized可阻止并发更新同一个共享资源,实现了同步
      • synchronized不能用来实现不同线程之间的消息传递(通讯)

并发协作模型“生产者/消费者模式”—>管程法

  • 生产者:负责生产数据得模块(可能是方法,对象,进程、线程)
  • 消费者:负责处理数据得模块(可能是方法,对象,进程、线程)
  • 缓冲区:消费者不能直接使用生产者的数据,他们之间有个“缓冲区”

生产者将生产好的数据放入缓冲区,消费者冲缓冲区拿出数据。

import java.util.*;
import java.util.concurrent.locks.ReentrantLock;

//生产者消费者问题
public class Test
    public static void main(String[] args) 
        SynContainer container = new SynContainer();

        new Productor(container).start();
        new Consumer(container).start();
    


//生产者
class Productor extends Thread
    SynContainer container;

    public Productor(SynContainer container)
        this.container = container;
    

    //生产

    @Override
    public void run() 
        for (int i = 0; i < 100; i++) 
            container.push(new Chicken(i));
            System.out.println("生产了第"+i+"只鸡");
        
    


//消费者
class Consumer extends Thread
    SynContainer container;

    public Consumer(SynContainer container)
        this.container = container;
    
    
    //消费

    @Override
    public void run() 
        for (int i = 0; i < 100; i++) 
            System.out.println("消费了---->第"+container.pop().id+"只鸡");
        
    


//产品
class Chicken
    int id;
    Chicken(int id)
        this.id=id;
    


//缓冲区
class SynContainer

    //需要一个容器大小
    Chicken[] chickens = new Chicken[10];
    //容器计数器
     int cnt=0;
    //生产者放入产品
    public synchronized void push(Chicken chicken)
        while(cnt>=10)
            //通知消费者消费,生产等待
            try 
                this.wait();
             catch (InterruptedException e) 
                e.printStackTrace();
            
        
        //没有满,则生产产品
        chickens[cnt++]=chicken;
        this.notifyAll();
    

    //消费者消费产品
    public synchronized Chicken pop()
        //判断是否能消费
        while(cnt<=0)
            //等待生产者生产
            try 
                this.wait();
             catch (InterruptedException e) 
                e.printStackTrace();
            
        
        //如果可以消费
        cnt--;
        Chicken chicken = chickens[cnt];

        //吃完了,通知生产者生产
        this.notifyAll();

        return chicken;
    

结果:

使用线程池

  • 背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
  • 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的交通工具。
  • 好处:
    • 提高相应速度(减少了创建新线程的时间)
    • 降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
    • 便于线程管理
      • corePoolSize:核心池的大小
      • maximuPoolSize:最大线程数
      • keepAliveTime:线程没有任务时最多保持多长时间后会终止
      • TimeUnit:时间单位
      • BlockingQueue< Runable > workqueue :等待队列
      • ThreadFactory:线程工厂
      • RejectedExecutionHandler:拒绝策略

线程池

/**
- corePoolSize:核心池的大小
- maximuPoolSize:最大线程数
- keepAliveTime:线程没有任务时最多保持多长时间后会终止
- TimeUnit:时间单位
- BlockingQueue< Runable > workqueue :等待队列
*/
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) 
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);

/**
- newCachedThreadPool()
- corePoolSize:核心线程数为0
- maximuPoolSize:最大线程数为int范围内最大的数2^31-1
- keepAliveTime:存活时间 60
- TimeUnit:单位 秒
- BlockingQueue< Runable > workqueue :等待队列为SynchronousQueue 大小为1
*/
public static ExecutorService newCachedThreadPool() 
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    
/**
- newCachedThreadPool()
- corePoolSize:核心线程数为n
- maximuPoolSize:最大线程数为n
- keepAliveTime:存活时间 0
- TimeUnit:单位 min
- BlockingQueue< Runable > workqueue :LinkedBlockingQueue 大小为int范围内最大的数2^31-1
*/
public static ExecutorService newFixedThreadPool(int nThreads) 
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());

/**
- newCachedThreadPool()
- corePoolSize:核心线程数为1
- maximuPoolSize:最大线程数为1
- keepAliveTime:存活时间 0
- TimeUnit:单位 min
- BlockingQueue< Runable > workqueue :LinkedBlockingQueue 大小为int范围内最大的数2^31-1
*/
public static ExecutorService newSingleThreadExecutor() 
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));

自定义线程池

/**
- newCachedThreadPool()
- corePoolSize:核心线程数为10
- maximuPoolSize:最大线程数为20
- keepAliveTime:存活时间 0
- TimeUnit:单位 sec
- BlockingQueue< Runable > workqueue :ArrayBlockingQueue 大小为10
*/
//自定义线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10,
                                                               20,
                                                               0L, 
                                                               TimeUnit.SECONDS,
                                                               new ArrayBlockingQueue<Runnable>(10));

提交优先级

  1. 核心线程
  2. 等待队列
  3. 非核心线程

以上是关于java线程笔记(锁线程通讯线程池)的主要内容,如果未能解决你的问题,请参考以下文章

Python成长笔记 - 基础篇

《Java并发编程实战》第八章 线程池的使用 读书笔记

Python之路第一课Day9--随堂笔记之二(进程线程协程篇)

Thread多线程速查手册

Python学习笔记——进阶篇第九周———线程进程协程篇(队列Queue和生产者消费者模型)

Java 并发编程线程池机制 ( 线程池执行任务细节分析 | 线程池执行 execute 源码分析 | 先创建核心线程 | 再放入阻塞队列 | 最后创建非核心线程 )