持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第5天,点击查看活动详情
多线程基础学习
普通方法调用和多线程方法
执行run方法,只有主线程一条路径执行
执行start方法,则是主线程和子线程并行交替执行
【注意】run方法就和普通的成员方法一样,而start方法则使用启动一个线程,使线程处于就绪状态
创建线程方法
继承Thread类
public class ExtendsThreadTest extends Thread {
@Override
public void run() {
for (int i = 0; i < 200; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("继承Thread实现多线程" + i);
}
}
public static void main(String[] args) {
ExtendsThreadTest test = new ExtendsThreadTest();
test.start();
for (int i = 0; i < 200; i++) {
System.out.println("多线程测试" + i);
}
}
}
实现Runnable接口
public class ImplRunnableTest implements Runnable {
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println("继承Thread实现多线程" + i);
}
}
public static void main(String[] args) {
// 创建Runnable接口的实现类对象
ImplRunnableTest test = new ImplRunnableTest();
// 创建一个线程对象,通过线程对象来开启我们的线程,这里使用了代理模式
new Thread(test).start();
for (int i = 0; i < 200; i++) {
System.out.println("多线程测试" + i);
}
}
}
实现Callable接口(了解)
1、实现Callable接口,需要返回值类型
2、 重写call方法,需要抛出异常
3、创建目标对象
4、创建执行任务:ExcutorService ser Excutors.newFixedThreadPool()
5、提交执行:Future result1 = ser.submit(t1)
6、获取结果:Boolean r1 =result1.get()
7、关闭服务:ser.shutdownNow()
public class ImplCallableTest implements Callable<Boolean> {
// 返回值同Callable接口的泛型一致
@Override
public Boolean call() throws Exception {
System.out.println("实现callable接口创建线程方法成功");
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
ImplCallableTest test = new ImplCallableTest();
// 创建执行服务
ExecutorService service = Executors.newCachedThreadPool();
// 提交执行
Future<Boolean> result = service.submit(test);
// 获取结果
Boolean r1 = result.get();
System.out.println(r1);
// 关闭服务
service.shutdown();
}
}
静态代理模式
真实类和代理类要实现同一个接口,并且在代理类中要实例化一个真实类对象
代理模式不同于工厂模式,代理类中可以进行扩展,工厂模式知识返回一个实例对象
// 接口
public interface Rent {
void rent();
}
// 真实类
public class FangDong implements Rent {
@Override
public void rent() {
System.out.println("房东出租房屋");
}
}
// 代理类
public class StaticProxy implements Rent {
private FangDong fangDong = new FangDong();
@Override
public void rent() {
before();
this.fangDong.rent();
rentMoney();
}
public void rentMoney() {
System.out.println("代理收中介费");
}
public void before() {
System.out.println("代理帮忙出租房屋");
}
}
// 使用代理类
public class Customer {
public static void main(String[] args) {
StaticProxy staticProxy = new StaticProxy();
staticProxy.rent();
}
}
JDK动态代理
动态代理实际上是通过传递参数来确定生成哪一个类的代理,它的实现需要两个重要的类
InvocationHandler:调用处理程序
Proxy
// 接口
public interface Rent {
void rent();
}
// 真实类
public class Host implements Rent {
@Override
public void rent() {
System.out.println("房东租房");
}
}
// 代理类
public class ProxyInvocationHandler implements InvocationHandler {
private Rent rent;
public void setRent(Rent rent) {
this.rent = rent;
}
public Object getProxy() {
/**
* public static Object newProxyInstance(ClassLoader loader,
* Class<?>[] interfaces,
* InvocationHandler h)
* 该方法的三个参数:
* loader:一个classloader对象,定义了由哪个classloader对象对生成的代理类进行加载
* interfaces:一个interface对象数组,表示我们将要给我们的代理对象提供一组什么样的接口,如果我们提供了这样一个接口对象数组,那么也就是声明了代理类实现了这些接口,代理类就可以调用接口中声明的所有方法。
* h:一个InvocationHandler对象,表示的是当动态代理对象调用方法的时候会关联到哪一个InvocationHandler对象上,并最终由其调用
*/
return Proxy.newProxyInstance(this.getClass().getClassLoader(),rent.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
seeHouse();
Object result = method.invoke(rent, args);
fare();
return result;
}
public void seeHouse() {
System.out.println("代理带领租客看房");
}
public void fare() {
System.out.println("代理收押金");
}
}
// 使用代理类
public class Client {
public static void main(String[] args) {
// 真实角色
Host host = new Host();
// 代理实例的调用处理程序
ProxyInvocationHandler pih = new ProxyInvocationHandler();
// 将真实角色类放置进去,说明此调用处理程序为该角色的代理实例的调用处理程序
// 这里传入的角色不同,生成的代理类也不同
pih.setRent(host);
// 动态生成代理类
Rent proxy = (Rent) pih.getProxy();
proxy.rent();
}
}
线程状态
五大状态
线程方法
停止线程
1、不推荐使用JDK提供的stop()、destroy()方法
2、推荐线程自己停下来,利用次数,不建议循环
3、建议使用一个标志位进行终止变量,当flag=false时,终止线程
线程休眠
1、sleep(时间)指定当前线程休眠的毫秒数
2、sleep存在异常InterruptedException
3、sleep时间达到后线程进入就绪态
4、sleep可以模拟网络时延,倒计时等
5、每一个对象都有一个锁,sleep不会释放锁
线程礼让
1、礼让线程,让当前正在执行的程序暂停,但不阻塞
2、线程由运行态进入就绪态
3、让CPU重新进行调度
线程强制执行(插队)
1、Join合并线程,待此线程执行完成后,在执行其他线程,其他线程阻塞
线程优先级
守护(daemon)线程
1、线程分为用户线程和守护线程
2、虚拟机必须确保用户线程执行完毕
3、虚拟机不用等待守护线程执行完毕
4、如后台记录操作日志、监控内存、垃圾回收等待,均为守护线程
同步
同步方法
synchronized关键字机制,包括两种用法:synchronized方法和synchronized块
同步方法:public synchronized void method(int args){}
synchronized方法控制对“对象”的访问,每个对象都对应一把锁,每个synchronized方法都必须获得调用方法的对象的锁才能执行,否则会阻塞进程,方法一旦执行,就独占该锁,直到该方法返回才能释放锁,后面被阻塞的线程才能获得这个锁,继续执行
缺陷:若将一个大的方法声明为synchronized将会影响效率
同步块
同步块:synchronized(Obj){}
Obj称之为同步监视器,就是被锁的公共资源(要修改的量)
- Obj可以是任何对象,但是推荐使用**共享资源**作为同步监视器
- 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class
同步监视器的执行过程
1. 第一个线程访问,锁定同步监视器,执行其中代码
2. 第二个线程访问,发现同步监视器被锁定,无法访问
3. 第一个线程访问完毕,解锁同步监视器
4. 第二个线程访问,发现同步监视器没有锁,然后锁定并访问
死锁
A同步块1中包含同步块2,同步块2需要B中占有的资源
B同步块3中包含同步块4,同步块4需要A中占有的资源
这就形成死锁
解决方法:将同步块2和4移动到同步块外
死锁条件:
互斥条件
请求并保持条件
不剥夺条件
循环等待条件
Lock锁
显式定义同步锁对象来实现同步,类似于PV原语
java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁,释放锁
代码实现
// 注意使用lock锁的try-catch-finally的格式
class LockThread implements Runnable {
private int ticket = 10;
// Lock锁,可重入锁
private final ReentrantLock reentrantLock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
// 显式加锁
reentrantLock.lock();
if (ticket > 0) {
System.out.println(ticket--);
} else {
break;
}
} catch (Exception e) {
System.out.println(e);
} finally {
// 显式释放锁
reentrantLock.unlock();
}
}
}
}
和synchronized比较
- Lock是显式锁(手动开启和关闭),synchronized是隐式锁,出了作用域自动释放
- Lock只有代码块锁,synchronized有代码块锁和方法锁
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好,并且具有更好的扩展性(提供更多的子类)
- 优先使用顺序
- Lock > 同步代码块(已经进入了方法体,分配了相应资源,分配了相应资源)> 同步方法(在方法体之外)
线程通信
线程池
ExecutorService
真正的线程池接口,常见子类ThreadPoolExecutor
- void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable
- Future submit(Callable task):执行任务,有返回值,一般用来执行Callable
- void shutdown():关闭连接池
Executors
工具类、线程池的工厂类,用于创建并返回不同类型的线程池
最后
互联网寒冬虽寒,但永远缺真正的高级程序员,卷吧。