Java高级----多线程、线程池总结


一、线程的概念

1、在一个程序中同时运行的多个独立流程,每一个独立的流程就是一个线程
2、线程的三要素:CPU、Code、Data

二、进程与线程

1、根本区别:进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位。
2、开销方面:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销。
3、所处环境:在操作系统中能同时运行多个进程(程序);而在同一个进程(程序)中有多个线程同时执行(通过CPU调度,在每个时间片中只有一个线程执行)。
4、所处环境:系统在运行的时候会为每个进程分配不同的内存空间;而对线程而言,除了CPU外,系统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),线程组之间只能共享资源。
5、包含关系:没有线程的进程可以看做是单线程的,如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。
6、 线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。

三、线程并发与主线程

1、并发就是在单核处理中同时处理多个任务。
2、主线程:main方法

四、创建线程的四种方式

1、继承Thread,重写run方法(无返回值),调用start()方法;如下:
public class MyThread1 extends Thread {
	public void run() {
		for (int i = 0; i <= 1000; i++) {
			System.out.println("*******************");
		}
	}
}
---------------------------------------------------------------------
public class MyThread2 extends Thread {
	public void run() {
		for (int i = 0; i <= 1000; i++) {
			System.out.println("$$$$$$$$$$$$$$$");
		}
	}
}
public class MyThreadTest {
   public static void main(String[] args) {
   	// 1、继承Thread类启动线程
   	// 创建线程对象
   	MyThread1 t1 = new MyThread1();
   	MyThread2 t2 = new MyThread2();
   	// 调用run()方法执行,此时并没有开启线程,会执行完t1中方法后执行t2方法
   	t1.run();
   	t2.run();
   	// 获取CPU后,调用start()方法开启线程,并且执行
   	t1.start();
   	t2.start();
   }
}
2、实现Runnable接口,重写run方法,创建实现类,启动进程:
public class MyRunnable1 implements Runnable {
	@Override
	public void run() {
		for (int i = 0; i < 1000; i++) {
			System.out.println("++++++++++++++++++");
		}

	}
}
---------------------------------------------------------------------------
public class MyRunnable2 implements Runnable {
	@Override
	public void run() {
		for (int i = 0; i < 1000; i++) {
			System.out.println("****************");
		}
	}
}

  (1) 、先创建实现类对象,然后将Runnable实现类对象封装成Thread对象,如下:

public class MyRunnableTest {
	public static void main(String[] args) {
		// 实现Runnable接口,重写run方法
		// 封装Runnable接口的实现类对象,封装成Thread对象
		MyRunnable1 r1 = new MyRunnable1();
		MyRunnable2 r2 = new MyRunnable2();
		Thread t1 = new Thread(r1);
		Thread t2 = new Thread(r2);
		// 获取CPU后,调用start()方法开启线程,并且执行
		t1.start();
		t2.start();
		// 直接创建匿名内部类对象
		new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println("@@@@@@@@@@@@@@@@@@");
			}

		}).start();

	}
}

  (2)、直接在new Thread构造方法中直接new实现类对象,如下:

// 封装Runnable接口的实现类对象,封装成Thread对象
	Thread t1 = new Thread(new MyRunnable1());
	Thread t2 = new Thread(new MyRunnable2());
// 获取CPU后,调用start()方法开启线程,并且执行
	t1.start();
	t2.start();

  (3)、在Thread构造方法中通过new Runnable( )接口的方法创建一个默认实现Runnable接口的匿名内部类对象,如下:

 // 直接创建匿名内部类对象
new Thread(new Runnable() {
	@Override
	public void run() {			System.out.println("@@@@@@@@@@@@@@@@@@");
		}
	}).start();
3、实现Callable接口,重写call方法

    Callable是类似于Runnable的接口,实现Callable接口的类和实现Runnable的类都是可被其它线程执行的任务。

//实现Callable接口,重写call方法
class MyCallable implements Callable {
	@Override
	public Object call() throws Exception {
		String [] str= {"apple","pear","banana","orange","grape"};
		int i=(int)(Math.random()*5);
		return str[i];
	}
public class Test {
	public static void main(String[] args) throws Exception {
	//创建任务
	MyCallable mc=new MyCallable();
	/**FutureTask同时实现了Runnable,Future接口。
	* 它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值
	 */
	//交付任务管理
	FutureTask<String> task=new FutureTask<>(mc);//可以看成FutureTask实现了runnable接口
	Thread t=new Thread(task);
	t.start();
	System.out.println("获取结果:"+task.get());
	System.out.println("任务是否完成:"+task.isDone());
	}
}
4、通过创建线程池,从池中获取

     通过new ThreadPoolExecutor 创建线程池,这样写线程数量更灵活,开发中多数用这个类创建线程。

5、 Callable和Runnable有几点不同:

    ① Callable规定的方法是call(),而Runnable规定的方法是run().
    ② Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。
    ③ call()方法可抛出异常,而run()方法是不能抛出异常的。
    ④ 运行Callable任务可拿到一个Future对象,Future表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可了解任务执行情况,可取消任务的执行,还可获取任务执行的结果。

五、Sleep、join和yield()方法

1、Join方法的作用

  (1)、 Thread类中的join方法的主要作用就是同步,它可以使得线程之间的并行执行变为串行执行。
  (2)、 在A线程中调用了B线程的join()方法时,表示只有当B线程执行完毕时,A线程才能继续执行。
  (3)、 join方法中如果传入参数,则表示这样的意思:如果A线程中掉用B线程的join(10),则表示A线程会等待B线程执行10毫秒,10毫秒过后,A、B线程并行执行。需要注意的是,jdk规定,join(0)的意思不是A线程等待B线程0秒,而是A线程等待B线程无限时间,直到B线程执行完毕,即join(0)等价于join()。
  (4) 、join方法必须在线程start方法调用之后调用才有意义。这个也很容易理解:如果一个线程都没有start,那它也就无法同步了。
  (5) 、注意:
    ① 两个线程分别持有对方的引用,并且调用对方的join方法,结果程序锁死了。
    ② 解决办法:至少让一个线程调用join()方法时传递了等待时间。

2、Sleep与wait方法的区别

  (1)、最大的不同是在等待时 wait 会释放锁,而 sleep 一直持有锁。wait 通常被用于线程间交互,sleep 通常被用于暂停执行。

3、Thread.yield()方法

  (1)、暂停当前正在执行的线程对象(及放弃当前拥有的CPU资源),并执行其他线程。
  (2)、yield()做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。
  (3)、结论:
     yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。

六、死锁

1、死锁产生原因:两个(以上)线程竞争两(以上)个锁,并且竞争的顺序相反。
2、产生死锁必须具备以下四个条件:

① 互斥条件:该资源任意一个时刻只由一个线程占用。
② 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
③ 不剥夺条件:线程已获得的资源在未使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
④ 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

3、最简单的死锁案例:
public class DeadLockDemo {
    private static Object resource1 = new Object();//资源 1
    private static Object resource2 = new Object();//资源 2

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (resource1) {
                System.out.println(Thread.currentThread() + "get resource1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + "waiting get resource2");
                synchronized (resource2) {
                    System.out.println(Thread.currentThread() + "get resource2");
                }
            }
        }, "线程 1").start();

        new Thread(() -> {
            synchronized (resource2) {
                System.out.println(Thread.currentThread() + "get resource2");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + "waiting get resource1");
                synchronized (resource1) {
                    System.out.println(Thread.currentThread() + "get resource1");
                }
            }
        }, "线程 2").start();
    }
}

4、解决死锁:

  (1)、如何预防死锁:破坏死锁的产生的必要条件即可

  • 破坏请求与保持条件 :一次性申请所有的资源。
  • 占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
  • 靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。

  (2)、尝如何避免死锁:

  • 避免死锁就是在资源分配时,借助于算法(比如银行家算法)对资源分配进行计算评估,使其进入安全状态。

  • 安全状态 指的是系统能够按照某种进程推进顺序(P1、P2、P3…Pn)来为每个进程分配所需资源,直到满足每个进程对资源的最大需求,使每个进程都可顺利完成。称<P1、P2、P3…Pn>序列为安全序列。

    七、生产者与消费者

1、wait()方法:在其他线程调用此对象的notify()方法或notifyAll()方法前,导致当前线程等待。
2、notify():唤醒线程,notifyAll()方法是唤醒所有线程。

八、 线程池ExecutorService类的五种创建方法

1、按需创建线程数量,有线程空闲则不新创建,重用之,这种方式首选 :
ExecutorService threadPool = Executors.newCachedThreadPool();
//实现Runnable接口创建线程
public class TestThread implements Runnable {
	private String token;

	public TestThread(String token) {
		this.token = token;
	}

	// 重写run()方法
	@Override
	public void run() {
		while (true) {
			System.out.println(token);
		}

	}

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

public class TestThreadPool {
	public static void main(String[] args) {
		// 按照需求创建线程数量,有线程空闲则不新建,重用已经存在的线程,这种方式是首选
		ExecutorService threadPool = Executors.newCachedThreadPool();
		// 往线程池中放入两个线程,需要几个就放入几个
		threadPool.submit(new TestThread("+++++++++++++"));
		threadPool.submit(new TestThread("*************"));
		// 启动线程池
		threadPool.shutdown();

	}
}
2、创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待:
ExecutorService threadPool = Executors.newFixedThreadPool(3);
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestThreadPool {
	public static void main(String[] args) {
		// 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
		ExecutorService threadPool = Executors.newFixedThreadPool(3);
		threadPool.submit(new TestThread("**********"));
		threadPool.submit(new TestThread("$$$$$$$$$$"));
		threadPool.submit(new TestThread("@@@@@@@@@@"));
		//这一个线程会等待,直到有空闲的线程的时候才会执行
		threadPool.submit(new TestThread("&&&&&&&&&&"));
		// 启动线程池
		threadPool.shutdown();
	}
}
3、创建一个定长线程池,支持定时及周期性任务执行

newScheduledThreadPool();

4、创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行

newSingleThreadExecutor ();

5、创建持有足够线程的线程池来支持给定的并行级别,并通过使用多个队列,减少竞争,它需要穿一个并行级别的参数,如果不传,则被设定为默认的CPU数量。

newWorkStealingPool();

九、 线程生命周期

1、sleep()方法与yield()方法

sleep 方法允许较低优先级的线程获得运行机会,但yield()方法执行时,当前线程仍处在可运行状态,所以不可能让出较低优先级的线程此时获取CPU占有权。在一个运行系统中,如果较高优先级的线程没有调用sleep方法,也没有受到I/O阻塞,那么较低优先级线程只能等待所有较高优先级的线程运行结束,方可有机会运行。
yield()只是使当前线程重新回到可执行状态,所有执行yield()的线程有可能在进入到可执行状态后马上又被执行,所以yield()方法只能使同优先级的线程有执行的机会。

2、sleep()是Thread类中的方法,wait()是object类中的方法,调用wait()方法后线程也进入阻塞状态。
3、生命周期图片

在这里插入图片描述

十、Java5.0中新并发库

1、java.util.concurrent.locks.LOCK 摒弃synchronized

  (1)、lock():加锁
  (2)、unlock():解锁
  (3)、tryLock():尝试加锁,有锁就不加,没锁就添加

2、java.util.concurrent.locks. Condition 、 摒弃wait() notify() notifyAll()

  (1)、awaite():代替wait()
  (2)、signal():代替notify()
  (3)、signalAll():代替notifyAll

private Lock lock = new ReentrantLock();
//一个条件,线程协调时需要考虑的因素
private Condition condition = lock.newCondition();
public void deposit(double count) throws InterruptedException{
System.out.println("存入的金额:"+count);
//加锁
lock.lock();
while (true){
//如果条件不满足,则等待条件,并且释放锁;如果条件满足,则break继续
if (balance + count > 2000){
//等待
	condition.await();
} else { break;}
}
double now = balance + count;
Thread.sleep(100);//存钱操作用时0.1秒钟
balance = now;
//考虑的条件(余额)有变,唤醒所有等待条件的线程
condition.signalAll();
//释放锁
lock.unlock();
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值