一、回顾多线程内容
1.1 线程的实现方式(3种):继承Thread、实现Runnable、匿名内部类
- 创建自定义类
new 自定义类()
,继承Thread
类,重写run()
方法
package day0709;
public class MyEx {
public static void main(String[] args) {
TestThread tt = new TestThread() ;
tt.start();
}
}
class TestThread extends Thread{//自定义类
public void run() {
System.out.println("方式一:自定义线程类继承Thread类,并重写run()方法");
}
}
- 创建自定义类
new 自定义类()
,实现Runnable
接口,重写run()
,new Thread(Runnable对象)
package day0709;
public class MyEx2 {
public static void main(String[] args) {
Thread t = new Thread(new TestThread2());
t.start();
}
}
class TestThread2 implements Runnable{
@Override
public void run() {
System.out.println("方式二:自定义类实现Runnable接口,重写run(),在Thread中传入Runnable对象");
}
}
- 结合方式一和二,创建匿名内部类
package day0709;
public class MyEx3 {
public static void main(String[] args) {
//方式一的匿名内部类
Thread t1 = new Thread() {
public void run() {
System.out.println("方式一的匿名内部类");
}
};
//方式二的匿名内部类
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.print("方式二的匿名内部类");
}
});
t1.start();
t2.start();
}
}
1.2 常用方法
//开启线程
start()
//获得当前运行的线程
Thread.currentThread()
//设置线程名字
setName()
//获得线程名字
getName()
//获得线程ID
getId()
//获得线程优先级
getPriority()
//设置线程优先级
setPriority(1~10)
//判断线程是否为守护线程
isDaemon()
//设置线程为守护线程
setDaemon(true)
//归还CPU时间片
Thread.yield()
二、多线程2
2.1 线程同步、异步概念
- 线程同步:排队执行
- 线程异步:多个线程使用同一个资源,抢占资源
2.2 常用方法(join())
void join()
:等待线程死亡
package day0709;
public class ThreadDemo02 {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for(int i = 0; i <= 100; i++) {
System.out.println(i+"%");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("加载完成!");
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("等待加载");
//等待第一个线程执行完,再继续当前t2线程
/*while(t1.isAlive()) {
Thread.yield();
}*/
//效率太低
try {
t1.join();//等待第一个线程死亡(执行完)
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("显示完成!");
}
});
t1.start();
//join() 不能放这里,这里表示必须等待t1全部结束
/*try {
t1.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}*/
t2.start();
}
}
2.3 多线程可能会出现的问题——线程异步
- 案例:两个人,共用同一个桌子 Table,桌上有20个豆子 bean = 20,2个人从桌子上拿豆子。用多线程实现。
package day0709;
/*
* 两个人,共用同一个桌子 Table
* 桌上有20个豆子 bean = 20
* 2个人从桌子上拿豆子
*/
public class ThreadDemo03 {
public static void main(String[] args) {
Table ta = new Table(20);
Thread t1 = new Thread(new Runnable() {
int num = 0;
@Override
public void run() {
while(ta.bean != 0) {//可能会出现直接越过临界点的现象,导致程序出现问题
ta.getBean();
num ++;
System.out.println(Thread.currentThread().getName()+ ":" + num);
}
}
});
Thread t2 = new Thread(new Runnable() {
int num2 = 0;
@Override
public void run() {
while(ta.bean != 0) {
ta.getBean();
num2 ++;
System.out.println(Thread.currentThread().getName() + ":" + num2);
}
}
});
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ta.bean);
}
}
2.4 synchronized——锁 (wait()、notify()、notifyAll())
2.4.1 锁的特点
- 锁的范围越小越好,尽量不要锁整个方法,
- 锁在静态方法上,锁类(
class
) - 千万别用 - 可以锁方法,锁代码块 -> 一个对象有一个锁,并且锁只能被一个线程获得
- 锁代码块是,通常使用的对象是
this
- 锁的对象:共享的对象
2.4.2 锁的使用
- 线程的等待与唤醒,必须得使用线程所共享的锁,即每个线程都要有相同的锁对象
- 给方法上锁,是为保证线程运行该方法时,不被其他线程打断,线程中未上锁的其他代码方法仍会被打断
package day0709;
public class ThreadDemo05 {
public static void main(String[] args) {
Object o = new Object();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
// synchronized(o) { //锁住方法,使线程运行该方法时,不被其他线程打断
System.out.println("1——开始加载图片...");
for(int i = 0; i <= 100; i++) {
System.out.println(i+"%");
}
System.out.println("2——加载图片完成...");
// }
synchronized(o) { //使用线程等待和唤醒方法,必须得使用锁对象,保证等待和唤醒之间的关系
o.notify();
// o.notifyAll();
}
System.out.println("5——开始下载图片...");
for(int i = 0; i <= 100; i++) {
System.out.println(i + "%");
}
System.out.println("6——图片下载完成...");
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("3——开始显示图片...");
synchronized(o) {//锁必须共享,每个线程都要有锁才行
try {
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("4——显示图片完成...");
}
});
t1.start();
t2.start();
}
}
2.4.3 给集合上锁——Collections.synchronizedList(list);
package day0709;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class ThreadDemo06 {
public static void main(String[] args) {
List<Integer> list = new ArrayList<Integer>();
List<Integer> sList = Collections.synchronizedList(list);//给集合上锁
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for(int i = 0; i < 10; i++) {
sList.add(i);
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for(int i = 0; i < 10; i++) {
sList.add(i);
}
}
});
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
三、线程池
3.1 线程池特点
- 线程对象
ExecutorService
- 不再手动创建单线程,即
new Thread()
- 好处:实现线程对象的重复使用,提高效率
3.2 创建线程池的方式(工具类:Executors)
- 创建一个可重用固定线程数的线程池:
static ExecutorService newFixedThreadPool(int nThreads)
- 创建一个可根据需要创建新线程的线程池:
static ExecutorService newCachedThreadPool()
- 创建一个使用单个
worker
线程的Executor
:static ExecutorService newSingleThreadExecutor()
- 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行:
static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
代码实现:
package day0709;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
public class ThreadDemo07 {
public static void main(String[] args) {
//创建出来的线程池中有3个线程对象,都标记为空闲状态
ExecutorService pool = Executors .newFixedThreadPool(3);//线程中指定有3个线程执行10个任务
ExecutorService pool2 = Executors.newCachedThreadPool();//线程根据任务数量生成10个线程
ExecutorService pool3 = Executors.newSingleThreadExecutor();//线程池中只有1个线程,去执行10个任务
ScheduledExecutorService pool4 = Executors.newScheduledThreadPool(3);//线程中指定有3个线程,可定时执行
//多任务只有1个,而线程池中有3个空闲线程,则将任务交给线程池,随机指派一个线程对象来执行
for(int i = 0; i < 10; i++) {
pool.execute(new Runnable() {
@Override
public void run() {
for(int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " : " + i);
}
}
});
}
//任务执行结束,将线程对象规划给线程池,并且重新标记为空闲状态
//shutdown() 不执行,线程池还在等待任务,控制台一直显示运行状态
pool.shutdown();
}
}
3.3 ExecutorService(接口)——Executors工具类的返回类型
3.3.1 常用方法
- 运行线程池:
execute()
- 关闭线程池:
shutdown()
- 提交一个返回值的任务用于执行,返回一个表示任务的未决结果的 Future:
<T> Future<T> submit(Callable<T> task)
- 提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future:
Future<?> submit(Runnable task)