JAVA 多线程1
方式1,继承Thread类
定义一类继承Thread,并重写run方法
public class MyThread extends Thread{
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("线程开启了"+i);
}
}
}创建线程对象,并调用start方法开启线程
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.start();
t2.start();
}
方式2,实现Runnable接口
定义一类实现Runnable,并重写run方法
public class MyRunnable implements Runnable {
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("第二种方式实现多线程"+i);
}
}
}创建Thread对象,并将Runnable对象作为构造方法的参数进行传递
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr);
t1.start();
方式3,实现Callable接口
定义类实现Callable接口(指定泛型,重写call方法)
public class MyCallable implements Callable<String>{
public String call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println("跟女孩表白");
}
return "答应";
}
}创建Callable实现类对象,并将其作为参数传递给FutureTask,最后将FutureTask对象作为参数传递给Thread类的构造
public class Demo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable mc = new MyCallable();
FutureTask<String> ft = new FutureTask<>(mc);
Thread t1 = new Thread(ft);
t1.start();
String result = ft.get(); // get方法带有阻塞效果,如果获取不到,会一直等待,后续代码无法执行
System.out.println(result);
}
}
注意:ft.get(); 必须在开启线程后调用
三种实现方式的对比
编码的复杂度
继承Thread最简单
程序的扩展性
实现接口的方式扩展性相对更好
是否可以使用Thread的方法
第一种方式可以直接使用Thread的方法
获取和设置线程的名称
线程的默认的名称
规则:Thread-编号(编号是从0开始的一个数字)
获取名字
Thread类提供了String getName()方法
设置名称
- setName(String name);
- 通过有参构造传递名称,需要手动重写有参构造
获取当前线程对象
Thread提供的静态方法
Thread对象 Thread.currentThread();
哪个线程在执行这行代码,就返回哪个线程所对应的对象
线程的休眠
Thread类中提供sleep方法·
Thread.sleep(long 毫秒值);
哪个线程在执行这行代码,哪个线程就休眠指定毫秒
线程的调度模型
java中的多线程是一种抢占式的模型。多个线程争夺CPU的执行权。线程中存在一个优先级的概念,优先级越高的线程抢夺到CPU的执行权的概率理论上更高。
线程的默认优先级
默认是5(线程的优先级是通过数字来表示的,取值范围是1-10)
如何设置线程的优先级
setPriority(int priority);
如何获取线程的优先级
int getPriority();
守护线程
备胎线程:用于守护普通线程的,当普通线程直接结束,守护线程会自动结束执行。
如何将一个线程变成守护线程。
setDaemon(true);
多线程的安全问题
产生的本质原因:
多个线程在同时操作共享数据
同步
同步代码块解决
语法:
synchronized(锁对象){ |
基本原理:
- 默认锁是打开的
- 当有线程进入synchronized代码块后锁会自动锁上
- 当synchronized中的操作共享数据的代码执行完毕后,锁会自动打开
同步方法解决
同步方法
访问修饰符 synchronized 返回值 方法名(){
操作共享数据的代码
}锁:this
同步静态方法
访问修饰符 static synchronized 返回值 方法名(){
操作共享数据的代码
}锁:当前类.class(当前类字节码对象)
Lock锁
语法
创建Lock锁对象ReentranctLock
操作共享数据之前获取锁
lock();
操作完共享数据要释放锁
unlock();
try{ |
示例:
public class Ticket implements Runnable { |
消费者生产者(等待唤醒机制)
假设:消费者一共只能吃10个汉堡
消费者线程
- 判断桌子上是否有汉堡
- 如果桌子上没有就等待
- 如果桌子上有就开吃
- 通知生产者生成汉堡
- 汉堡的数量减1
生产者线程
判断桌子上是否有汉堡
如果有就等待
如果没有就生产汉堡,将汉堡放在桌子上
通知消费者线程吃汉堡
阻塞队列
队列:一种先进先出的数据结构
阻塞队列:在传统的队列的基础上,加入了两个附加操作
- 当获取元素的线程,发现队列为空,会自动等待,等到队列不为空为止
- 当添加元素的线程,发现队列已满,会自动等待,等待队列可用为止
对象:
ArrayBlockingQueue
依赖数组实现,有界的
LinkedBlockingQueue
依赖链表实现的,无界的,(最大长度是int的最大值)
方法:
存
put(元素对象)
取
队列顶端的元素 take();
复习
能够知道什么是进程什么是线程
进程
操作系统中正在执行的程序。每一个程序开始运行后,至少会有一个进程。
线程
线程是某一个进程中的执行逻辑。一个进程中可能包含多个线程。某一个线程只可能属于某一个进程。
并发和并行的
并行
在同一时刻,有多个指令在多个CPU上同时执行。
并发
在同一时刻,有多个指定在单个CPU上交替执行
线程3种创建方式
- 继承Thread类
- 写一个类继承Thread,重写run方法
- 实现Runnable接口
- 写一个类实现Runnable接口,重写run方法
- 创建Runnable的对象,并将其作为参数传递给Thread的构造方法
- 实现Callable接口
- 写一个类实现Callable接口,重写call方法
- 创建Callable的对象,并将其作为参数传递给FutureTask的构造方法
- 将FutureTask对象作为参数传递给Thread的构造方法
- 继承Thread类
线程中的常见的方法
获取线程的名称
getName(); 默认名字:Thread-编号
设置线程的名称
setName(String name);
有参构造方法
获取当前线程对象
Thread Thread.currentThread();哪个线程执行到这行代码,就返回该线程对象
线程的睡眠
Thread.sleep(long 毫秒值),哪个线程执行到这行代码,哪个线程就睡眠
设置线程的优先级
setPriority(int priority)
参数的取值范围:1-10,值越大优先级越大
获取线程的优先级
int getPriority(); 默认值是5
守护线程
可以理解为备胎线程,用于守护其它的普通线程而存在。如果普通线程都执行结束,守护线程也会自动结束执行。
怎么将线程设置为守护线程:
setDaemon(true);
线程的安全问题
什么是安全问题:
多个线程在并发操作共享数据时,会出现数据错乱的问题。
产生的本质原因
多个线程在在同时操作共享数据
解决思想
在同一个时刻,只能让一个线程操作功效数据,当这个线程操作完毕之后,其它线程才可以操作。
解决方式
同步
同步代码块
synchronized(锁对象){
操作共享数据的代码
}锁对象是任意的。多个线程需要使用同一个锁对象。
同步方法
访问修饰符 synchronized 返回值 方法名(){
}
访问修饰符 static synchronized 返回值 方法名(){
}同步方法使用this作为锁对象,同步静态方法使用 当前类.class作为锁对象
Lock锁
创建锁对象
ReentrantLock
在操作之前获取锁
lock();
操作之后释放锁
unlock(); // 一般写在finally语句块中
死锁
多个线程出现相互等待的情况,造成程序无法继续。
出现的原因:锁的嵌套
生产者和消费者
等待唤醒机制
让当前线程等待
Object类提供了一个wait();
唤醒其它线程
Object类提供两个方法
notify();
随机换当前锁对象绑定的处于等待状态的某一个线程。
notifyAll();
唤醒当前锁对象绑定的所有处于等待状态的线程。