Java多线程(四种创建方法)——笔记
尚硅谷:https://www.bilibili.com/video/BV1Kb411W75N?p=406
1. 创建多线程
1.1 方式一:继承Thread类
-
创建继承Thread的子类
-
重写run()方法
-
创建Thread类的子类的对象
-
通过对象调用start()
class MyThread extends Thread{ public void run(){ } } public class ThreadTest{ public static void main(String[] args){ MyThread my = new MyThread(); my.start(); } }
1.1.2
通过创建Thread类的匿名子类的方式
public class ThreadTest{
public static void main(String[] args){
new Thread(){
public void run(){
}
}.start();
}
}
1.2 方式二:实现Runnable接口
-
创建一个实现了Runnable接口的类
-
实现类去实现Runnable中的抽象方法:run()
-
创建实现类的对象
-
将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
-
通过Thread类的对象调用start()
class MyThread implements Runnable{ public void run(){ } } public class ThreadTest{ public static void main(String[] args){ MyThread myThread = new MyThread(); Thread t1 = new Thread(myThread); t1.start(); } }
new Thread(new Runnable(){ public void run(){ } }).start();
1.3 方式三:实现Callable接口
-
创建一个实现Callable接口的实现类
-
实现call方法,将此线程需要执行的操作声明在call()中
-
创建Callable接口实现类的对象
-
将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
-
将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
-
获取Callable中call方法的返回值
// 1.创建一个实现Callable接口的实现类
class MyThread implements Callable {
// 2.实现call方法,将此线程需要执行的操作声明在call()中
@Override
public Object call() throws Exception {
return null;
}
}
public class CallableTest {
public static void main(String[] args) {
// 3.创建Callable接口实现类的对象
MyThread myThread = new MyThread();
// 4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
FutureTask futureTask = new FutureTask(myThread);
// 5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
new Thread(futureTask).start();
try {
// 6.获取Callable中call方法的返回值
Object o = futureTask.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
Callable接口比Runnable接口创建多线程更强大?
- call()方法有返回值
- call()方法可以抛出异常,并被捕获异常
- Callable支持泛型
1.4 方式四:线程池
好处:
- 提高响应速度(减少了创建线程的时间)
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
- 便于线程管理
- corePoolSize:核心池的大小
- maximumPoolSize:最大线程数
- keepAliveTime:线程没有任务时最多保持多长时间后会终止
class MyThread implements Runnable{
@Override
public void run() {
}
}
public class ThreadPool {
public static void main(String[] args) {
// 1.提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
// 设置线程池的属性
service1.setCorePoolSize(15);
// 2.执行指定的线程的操作
service.execute(new MyThread());// 适用于Runnable
// service.submit(new MyThread());// 适用于Callable
// 3.关闭连接池
service.shutdown();
}
}
2.常用方法
方法 | 说明 |
---|---|
start() | 启动当前进程,调用当前进程的run() |
run() | 通常需要重写Thread类中的此方法,将创建的进程要执行的操作声明在此方法中 |
currentThread() | 静态方法,返回执行当前代码的线程 |
getName() | 获取当前线程的名字 |
setName() | 设置当前线程的名字 |
yield() | 释放当前cpu的执行权 |
join() | 在线程A中调用线程B的join(),此时线程A就进入阻塞状态,直到线程B完全执行完以后,线程a才结束阻塞状态 |
sleep(long millitime) | 让当前线程睡眠指定的millitime毫秒,在指定的时间内,当前线程是阻塞状态。 |
isAlive() | 判断当前线程是否存活 |
3.优先级
1.
- MAX_PRIORITY:10
- MIN_PRIORITY:1
- NORM_PRIORITY:5 默认优先级
2.获取和设置当前线程的优先级
-
getPriority():获取线程的优先级
-
setPriority(int p):设置线程的优先级
thread,setPriority(MAX_PRIORITY)
注意:高优先级的线程要抢占低优先级线程cpu的执行权。但是只是从概率上讲,高优先级的线程高概率的情况下被执行。并不意味着只有当高优先级的线程执行完以后,低优先级的线程才执行。
4. 线程安全
以买票为例,
问题:卖票过程中,出现了重票,错票–>出现了线程的安全问题
原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程进入操作
解决:当一个线程a在操作时,其他线程不能进入,直到a完成
1.方式一:同步代码块
synchronized(同步监视器){
// 需要被同步的代码,即操作共享数据的代码
}
说明:
-
需要被同步的代码:即操作共享数据的代码 代码不能包含多了,也不能少了
-
同步监视器:俗称:“锁“,任何一个类的对象都可以充当锁
-
多个线程必须共用同一把锁
- 一、
// 一、继承Thread private static Object obj = new Object(); // 二、实现Runable Object obj = new Object(); public void run(){ synchronized(obj){ // 使用obj作为同步监视器 // 需要被同步的代码,即操作共享数据的代码 } }
- 二、
// 一、继承Thread 慎用this充当同步监视器,可以考虑使用当前类的对象充当同步监视器 synchronized(MyThread.class){ // Class clazz = MyThread.class ; MyThread.class只会加载一次 // 需要被同步的代码,即操作共享数据的代码 } // 二、实现Runable 可以考虑使用this充当同步监视器 synchronized(this){ // this:唯一的XXX类的对象 // 需要被同步的代码,即操作共享数据的代码 }
-
操作同步代码时,只能有一个线程参与,相当于是一个单线程的过程,效率低。
2.方式二:同步方法
- 仍然涉及同步监视器,不需要显式声明
- 非静态的同步方法,同步监视器是:this
- 静态的同步方法,同步监视器是:当前类本身
// 二、实现Runable
public void run(){
// 调用同步方法
}
// 同步方法
private synchronized void show(){ // 同步监视器:this
// 需要被同步的代码,即操作共享数据的代码
}
// 一、继承Thread
public void run(){
// 调用同步方法
}
// 同步方法
private static synchronized void show(){ // static 唯一 同步监视器:当前类
// 需要被同步的代码,即操作共享数据的代码
}
3.方式三:Lock锁
// 一、Runnable
// 1. 实例化ReentrantLock
private ReentrantLock lock = new ReentrantLock();
public void run(){
try{
// 2.调用锁定方法lock()
lock.lock();
//同步代码
}finally{
// 3.调用解锁方法unlock()
lock.unlock();
}
}
// 二、继承Thread
private static ReentrantLock lock = new ReentrantLock();
5.将单例模式中的懒汉式改为线程安全
class Bank{
private Bank(){}
private static Bank instance = null;
public static synchronized Bank instance(){
if (instance = null){
synchronized(Bank.class){
if (instance = null){
instance = new Bank();
}
}
}
return instance;
}
}
6.死锁
-
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
-
不会报错,不会出现提示,只是所有的线程都处于阻塞状态,无法继续。
解决:
- 专门的算法,原则
- 尽量减少同步资源的定义
- 尽量避免嵌套同步
7.线程通信
java.lang.Object
- wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
- notify():一旦执行此方法,就会唤醒被wait的一个线程;如果有多个线程被wait,按优先级唤醒
- notifyAll():一旦执行此方法,就会唤醒所有被wait的线程
必须使用在同步代码块中。