目录
目录
一、线程常用API
Thread类:String getName() 返回此线程的名称
setName(String name) 设置此线程的名称为name
public static void sleep(long millis) 是当前正在执行的线程以指定的毫秒数暂停,也就是睡眠
public static void yield() 主动放弃时间片,回到就绪状态,等待参与下一次竞争
线程对象. join() 加入当前线程(主线程),并阻塞当前线程,直到加入的线程执行完毕
线程对象. setPriority(5) 这里参数默认为5,优先级为1-10级,越大越优先
线程对象. setDaemon(True) 设置该线程为守护线程(后台线程),要在start()之前指定。[垃圾回收线程属于守护线程]
二、创建线程的两种方式
第一种:继承Thread类
1、创建一个继承Thread的子类
2、在子类中重写Thread类中的run方法,设置线程任务
3、创建一个子类实现对象
4、用子类对象调用start方法,就开启了新线程,执行run方法
代码实现:
public class MyThread extends Thread{
@Override
public void run() {
System.out.println("这里是线程任务!");
}
}
public class Demo {
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();
}
}
第二种:实现Runnable接口
- 创建一个实现Runnable接口的实现类
- 重写Runnable接口中的run方法,设置线程方法
- 创建一个实现类的对象
- 创建一个Thread类对象,构造方法中将实现类对象作为参数
- 使用Thread类对象调用start方法,就开启了新线程,执行run方法
代码实现:
public class MyThread implements Runnable{
@Override
public void run() {
System.out.println("这里是线程任务!");
}
}
public class Demo {
public static void main(String[] args) {
MyThread mt = new MyThread();
new Thread(mt).start();
}
}
附:匿名内部类方式实现上述两种创建线程的方法
new Thread(){
@Override
public void run() {
for (int i = 1;i < 99; i++){
System.out.println(Thread.currentThread().getName() + "线程1");
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+ "线程2");
}
}
}).start();
第三种:使用Callable接口
Callable和Runable接口的区别:Callable接口中的call()方法有返回值,声明了异常,而Runable接口中的run()方法没有返回值也没有声明异常。
1、创建Callable对象并且重写call()方法
2、将Callable对象转化为一个可执行的任务
3、创建线程(注意这里的参数为FutureTask类型的可执行任务对象),启动线程
4、获取任务结果
// 1创建Callable对象
Callable<Integer> callable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName() + "开始计算");
int sum = 0;
for(int i=1; i<=100; i++) {
sum += i;
}
return sum;
}
};
// 2把Callable对象转化为可执行任务
FutureTask<Integer> task = new FutureTask<>(callable);
// 3创建线程
Thread thread = new Thread(task);
// 4启动线程
thread.start();
// 5获取任务结果
Integer result = task.get();
System.out.println("结果是" + result);
三、实现Runnable接口创建多线程程序的好处
- 避免了单继承的局限(一个类只能继承一个类)
- 增强了程序的扩展性,降低了程序的耦合性(设置线程任务和开启线程分开了)
四、解决线程安全问题的三种方式
1、同步代码块
实现原理:当一个线程抢占cpu资源并执行到代码块时,判断锁对象是否存在,如果存在则锁定代码块并执行,直到执行完毕释放锁对象,当第一个线程没有执行完毕时,这时另一个线程抢占到cpu资源时就获取不到锁对象,无法执行同步代码块,直到前一个线程释放锁对象为止,也就是处于阻塞状态。
实现方式:将可能发生安全问题的代码块放在synchronized关键字中,表示对这个区块中的资源实行互斥访问,其参数为创建的一个任意唯一对象(锁对象)
public class MyThread implements Runnable{
Object obj = new Object();//创建一个锁对象
@Override
public void run() {
synchronized (obj){
//这里放可能产生安全问题的代码
}
}
}
2、同步方法
实现原理:和同步代码块方式类似,这里的锁对象为this,也就是在启用线程时创建的Runnable对象。
实现方式:将可能产生安全问题的代码封装在同步方法内,再调用该方法即可。
public class MyThread implements Runnable{
@Override
public void run() {
problem();
}
public synchronized void problem(){//当为静态方法时,锁对象是本类的class文件对象(反射)
//这里放可能产生安全问题的代码
}
}
3、ReentrantLock锁
实现方式:在成员位置创建一个ReentrantLock(重入锁,如同在synchronized中再嵌套一个synchronized代码块)对象,在可能出现安全问题的代码前调用Lock接口中的lock方法获取锁,在可能出现安全问题的代码后调用Lock接口的unlock方法释放锁(一般可能出现安全问题的代码放在try里,释放锁放在finally中)
public class MyThread implements Runnable{
Lock l = new ReentrantLock();
@Override
public void run() {
l.lock();
try {
//这里写可能出现安全问题的代码
}catch (Exception e){
e.printStackTrace();
}finally {
l.unlock();
}
}
}
五、线程的几种状态
六、线程之间的通信
为什么要处理线程间的通信
多个线程并发执行,在默认情况下CPU是随机切换线程,当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行,那么多线程之间需要一些协调通信,以此来帮我们达到多线程共同操作一份数据。
如何保证线程间通信有效利用资源
多个线程在处理同一资源,并且任务不同时,需要线程通信来帮助解决线程之间对同一变量的使用或操作。就是多个线程在操作同一份数据时,避免对同一共享变量的争夺。也就是我们需要通过一定的手段使各个线程能有效利用资源,这种手段就是等待唤醒机制。
七、线程池
线程池的原理
线程池就是一个盛放多个线程的容器,【比如一个LinkedList linked,通过Thread t = linked.removeFirst()使用线程和linked.addLast(t)归还线程来操作,所以是队列性质的】当程序第一次启动时就创建多个线程并保存到这个容器中,其中的线程可以反复使用,省去了频繁创建线程对象的操作,从而节省资源。
线程池的创建
1、使用线程池的工厂类Executors的一系列静态方法
不建议使用的弊端:FixedThreadPool 和 SingleThreadExecutor : 允许请求的队列长度为 Integer.MAX_VALUE ,可能堆积大量的请求,从而导致OOM。
CachedThreadPool 和 ScheduledThreadPool : 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致OOM。
2、使用构造方法ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
7个参数的含义:
常用线程池的接口和类
https://xiaojin21cen.blog.csdn.net/article/details/87183308
线程池的使用步骤
(一)继承Thread和实现Runable接口方式使用线程池:
- 使用线程池的工厂类Executors里的静态方法newFixedTreadPool生产一个指定线程数量的线程池。(返回一个ExecutorService接口的实现类对象)
- 创建一个类,实现Runnable接口,重写run方法,设置线程任务。
- 调用ExecutorService接口中的submit(Runnable task)方法传递线程任务,开启线程,执行run方法。
- 调用ExecutorService接口中的shutdown()方法销毁线程池。[但一般不执行,因为销毁后不能再使用线程池啦,再使用还需重新创建]
//第2步:创建一个类,实现Runnable接口,重写run方法,设置线程任务。
public class MyThread implements Runnable{
int flag;
public MyThread(int flag) {
this.flag = flag;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "来执行的任务" + flag);
}
}
public class Demo {
public static void main(String[] args) {
//第1步:使用线程池的工厂类Executors里的静态方法newFixedTreadPool生产一个指定线程数量的线程池。
ExecutorService executorService = Executors.newFixedThreadPool(3);
//第3步:调用executorService接口中的submit方法弃用线程
executorService.submit(new MyThread(1));
executorService.submit(new MyThread(2));
executorService.submit(new MyThread(3));
executorService.submit(new MyThread(4));
executorService.submit(new MyThread(5));
//第4步:关闭线程池(一般不使用)
executorService.shutdown();
}
}
(二)当使用Callable接口使用线程池时:
这里的Future表示ExecutorService.submit()返回的结果,实际就是call()方法返回的结果
V get()以阻塞形式等待Future中的异步处理结果( call()的返回值 )
// 1创建Callable对象
Callable<Integer> callable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName() + "开始计算");
int sum = 0;
for(int i=1; i<=100; i++) {
sum += i;
}
return sum;
}
};
// 2创建线程池
ExecutorService executorService = Executors.newFixedThreadPool(1);
// 3调用submit()方法启动线程,并返回Future对象
Future<Integer> future = executorService.submit(callable);
// 4获取结果
Integer result = future.get();
System.out.println("结果是" + result);
// 5关闭线程池
executorService.shutdown();