创建线程
继承Thread类
1.定义一个子类继承Thread类,并重写run方法
2.创建Thread的子类对象
3.调用start方法启动线程(启动线程后,会自动执行run方法中的代码)
public class MyThread extends Thread{
// 1、必须重写Thread类的run方法
@Override
public void run() {
// 描述线程的执行任务。
for (int i = 1; i <= 5; i++) {
System.out.println("子线程MyThread输出:" + i);
}
}
}
// 以下为测试类
// 3、创建MyThread线程类的对象代表一个线程
Thread t = new MyThread();
// 4、启动线程(自动执行run方法的)
t.start();
实现Runnable接口
1.先写一个Runnable接口的实现类,重写run方法(这里面就是线程要执行的代码)
2.再创建一个Runnable实现类的对象
3.创建一个Thread对象,把Runnable实现类的对象传递给Thread
4.调用Thread对象的start()方法启动线程(启动后会自动执行Runnable里面的run方法)
/**
* 1、定义一个任务类,实现Runnable接口
*/
public class MyRunnable implements Runnable{
// 2、重写runnable的run方法
@Override
public void run() {
// 线程要执行的任务。
for (int i = 1; i <= 5; i++) {
System.out.println("子线程输出 ===》" + i);
}
}
}
// 以下为测试类
// 3、创建任务对象。
Runnable target = new MyRunnable();
// 4、把任务对象交给一个线程对象处理。
// public Thread(Runnable target) 构造器
new Thread(target).start();
可使用内部类简化
实现Callable接口
1.先定义一个Callable接口的实现类,重写call方法
2.创建Callable实现类的对象
3.创建FutureTask类的对象,将Callable对象传递给FutureTask
4.创建Thread对象,将Future对象传递给Thread
5.调用Thread的start()方法启动线程(启动后会自动执行call方法)
等call()方法执行完之后,会自动将返回值结果封装到FutrueTask对象中
6.调用FutrueTask对的get()方法获取返回结果
public class MyRunnable implements Callable<Integer>{
Integer n;
public CallableDemo(Integer n) {
this.n = n;
}
// 先定义一个Callable接口的实现类,重写call方法
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i < n; i++) {
sum += i;
System.out.println(sum);
}
return sum;
}
}
// 以下为实现类
// 创建Callable实现类的对象
CallableDemo myRunnable = new MyRunnable(100);
//创建FutureTask类的对象,将Callable对象传递给FutureTask
FutureTask<Integer> task = new FutureTask<>(myRunnable);
// 创建Thread对象,将Future对象传递给Thread
Thread thread = new Thread(task);
// 调用Thread的start()方法启动线程(启动后会自动执行call方法) 等call()方法执行完之后,会自动将返回值结果封装到FutrueTask对象中
thread.start();
// 调用FutrueTask对的get()方法获取返回结果
System.out.println(task.get());
小结
继承Thread类 无法再继承其他类 无法获取返回值
实现Runnable接口 可继承其他类 无法获取返回值
实现Callable接口 可继承可获取返回值
多线程
多线程常用方法
public void start() // 启动线程
public String getName() // 获取当前线程名称
public void setName() // 设置当前线程名称
public static Thread currentThread() // 获取当前执行的线程对象
public static void sleep(long time) // 让线程休眠多少毫秒后再执行
public final void join() // 让调用这个方法的线程先执行完
Thread提供的常见构造器
public Thread(String name) // 为当前线程指定名称
public Thread(Runnable target) // 封装Runnable对象为线程对象
public Thread(Runnable target,String name) //封装Runnable对象为线程对象,并指定名称
public class MyThread extends Thread{
public MyThread(String name){
super(name); // 执行了父类的Thread(String name)构造器,设置名字
}
@Override
public void run(){
Thread t = Thread.currentThread();
System.out.println(t.getName()) // 打印线程名称
}
}
// 测试类中
Thread t = new MyThread() //获取线程对象
Thread t1 = new MyThread() //获取线程对象
t.getName() // 获取线程名称
t.setName() // 设置线程名称
t.sleep(2000) // 线程休眠2秒
t.start() // 启动线程
t.join() // 先执行完t线程
t1.start() // 启动t1线程
线程安全问题
线程安全问题指的是,多个线程同时操作同一个共享资源的时候,可能会出现业务安全问题。
- 创建一个共享的账户类 实现一个修改数据的方法
- 创建一个线程类重写的run方法中调用修改数据的方法
- 创建测试类 new多个线程对象进行修改
线程同步方案
每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动释放锁,然后其他线程才能再加锁进来。
- 同步代码块
- 同步方法
- Lock锁
在操作数据的时候加锁
同步代码块
// 小明 小红线程同时过来的
public void drawMoney(double money) {
// 先搞清楚是谁来取钱?
String name = Thread.currentThread().getName();
// 1、判断余额是否足够
// this正好代表共享资源!
synchronized (this) {
if(this.money >= money){
System.out.println(name + "来取钱" + money + "成功!");
this.money -= money;
System.out.println(name + "来取钱后,余额剩余:" + this.money);
}else {
System.out.println(name + "来取钱:余额不足~");
}
}
}
1.建议把共享资源作为锁对象, 不要将随便无关的对象当做锁对象
2.对于实例方法,建议使用this作为锁对象
3.对于静态方法,建议把类的字节码(类名.class)当做锁对象
同步方法
同步方法就是给整个方法上锁,同时只能有一个线程调用该方法
// 同步方法
public synchronized void drawMoney(double money) {
// 先搞清楚是谁来取钱?
String name = Thread.currentThread().getName();
// 1、判断余额是否足够
if(this.money >= money){
System.out.println(name + "来取钱" + money + "成功!");
this.money -= money;
System.out.println(name + "来取钱后,余额剩余:" + this.money);
}else {
System.out.println(name + "来取钱:余额不足~");
}
}
Lock锁
Lock锁是JDK5版本专门提供的一种锁对象,通过这个锁对象的方法来达到加锁,和释放锁的目的,使用起来更加灵活
1.在成员变量,创建一个Lock接口的实现类对象
private final Lock lk = new ReentrantLock();
2.在需要上锁的地方加入下面的代码
lk.lock(); // 加锁
//...中间是被锁住的代码...
lk.unlock(); // 解锁
线程通信(了解)
notify() // 唤醒任意一线程
notifyAll() // 唤醒所有线程
wait(); // 让当前线程等待
线程池
创建线程池
new ThreadPoolExecutor(核心线程数,最大线程数,临时线程存活时间,存活时间单位,线程队列,线程工厂,拒绝策略)
ExecutorService pool = new ThreadPoolExecutor(
3, //核心线程数有3个
5, //最大线程数有5个。 临时线程数=最大线程数-核心线程数=5-3=2
8, //临时线程存活的时间8秒。 意思是临时线程8秒没有任务执行,就会被销毁掉。
TimeUnit.SECONDS,//时间单位(秒)
new ArrayBlockingQueue<>(4), //任务阻塞队列,没有来得及执行的任务在,任务队列中等待
Executors.defaultThreadFactory(), //用于创建线程的工厂对象
new ThreadPoolExecutor.CallerRunsPolicy() //拒绝策略
);
创建临时线程的时间
新任务提交时,发现核心线程都在忙、任务队列满了、并且还可以创建临时线程,此时会创建临时线程。
什么时候开始拒绝新的任务
核心线程和临时线程都在忙、任务队列也满了、新任务过来时才会开始拒绝任务。
线程池执行Runnable任务
使用execute()执行
ExecutorService pool = new ThreadPoolExecutor(
3, //核心线程数有3个
5, //最大线程数有5个。 临时线程数=最大线程数-核心线程数=5-3=2
8, //临时线程存活的时间8秒。 意思是临时线程8秒没有任务执行,就会被销毁掉。
TimeUnit.SECONDS,//时间单位(秒)
new ArrayBlockingQueue<>(4), //任务阻塞队列,没有来得及执行的任务在,任务队列中等待
Executors.defaultThreadFactory(), //用于创建线程的工厂对象
new ThreadPoolExecutor.CallerRunsPolicy() //拒绝策略
);
Runnable target = new MyRunnable();
pool.execute(target); // 线程池会自动创建一个新线程,自动处理这个任务,自动执行的!
pool.execute(target); // 线程池会自动创建一个新线程,自动处理这个任务,自动执行的!
线程池执行Callable任务
使用submit()执行
// 1、通过ThreadPoolExecutor创建一个线程池对象。
ExecutorService pool = new ThreadPoolExecutor(
3,
5,
8,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(4),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy());
// 2、使用线程处理Callable任务。
Future<String> f1 = pool.submit(new MyCallable(100));
Future<String> f2 = pool.submit(new MyCallable(200));
并发并行
并发
并发就是多条线程交替执行
并行
多个线程同时被CPU调度执行(CPU多核心)
线程的声明周期
NEW: 新建状态,线程还没有启动
RUNNABLE: 可以运行状态,线程调用了start()方法后处于这个状态
BLOCKED: 锁阻塞状态,没有获取到锁处于这个状态
WAITING: 无限等待状态,线程执行时被调用了wait方法处于这个状态
TIMED_WAITING: 计时等待状态,线程执行时被调用了sleep(毫秒)或者wait(毫秒)方法处于这个状态
TERMINATED: 终止状态, 线程执行完毕或者遇到异常时,处于这个状态。