一、什么是进程、什么是线程
进程是指⼀个内存中运⾏的应⽤程序,每个进程都有⼀个独⽴的内存空间,⼀个应⽤程序可以同时运⾏多个进程。进程之间相互独立,互不影响,系统通过分配不同的端口号判断运行的程序。
线程是进程中的基本单位,一个进程由一个或者多个线程组成。线程之间数据共享,运行时通过抢占cpu来执行。
二、并行和并发
并发:并发是指一个处理器同时处理多个任务,任务之间交替执行
并行:并行是指多个处理器或者是多核的处理器同时处理多个任务
三、创建线程
因为Java是单继承机制,因此创建线程多用实现接口的方式
3.1、继承Thread类创建
1.创建一个继承于Thread类的子类 2.重写Thread类的run()方法 3.创建Thread类的子类的对象 4.作为参数放入new Thread()对象中 5.通过此对象调用start()执行线程
//核心代码
package study01;
class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(i);
}
}
}
public class Test01 {
public static void main(String[] args) {
MyThread myThread = new MyThread();
new Thread(myThread).start();
}
}
3.2、实现Runnable接口(多用)
1.创建一个实现了Runnable接口的类 2.实现类去重写Runnable中的抽象方法:run() 3.创建实现类的对象 4.将此对象作为参数传递到new Thread()对象中 5.通过Thread类的对象调用start()
//核心代码
1、实现Runnable接口写法:
class MyThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(i);
}
}
}
public static void main(String[] args) {
MyThread1 myThread = new MyThread();
//在创建`Thread`时作为参数传递并启动。
new Thread(myThread).start();
}
2、使用匿名内部类式写法:
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(i);
}
}
}).start();
}
四、线程方法
4.1、构造方法
Thread() 分配一个新的Thread对象
Thread(Runnable target) 分配一个新的Thread对象
Thread(Runnable target, String name) 分配一个新的Thread对象,并起一个名字
4.2、成员方法
static Thread currentThread() 返回对当前正在执行的线程对象的引用
String getName() 返回此线程的名称
void setName(String name) 将此线程的名称更改为等于参数name
//更改优先级不太稳定,因为更改优先级后还是抢占式运行
//不方便使用(了解即可):
int getPriority() 返回此线程的优先级
void setPriority(int newPriority) 更改此线程的优先级
//睡眠线程无法确定合适的睡眠时间
static void sleep(long millis) 使正在执行的线程以指定的毫秒数暂停
五、线程同步
当多个线程同时请求并修改痛一个数据的时候,会导致数据不准确的情况,相互之间产生问题。容易出现线程安全的问题。
因为在同一个线程内部,无法确定多个线程的执行顺序,例如若是多个线程同时获取某个属性的值并抢占式执行修改该属性的程序,就可能出现属性的值混乱的情况。
因此线程同步至关重要,对于多线程的程序来说,同步指的是在一定的时间内只允许某一个线程访问某个资源,这样就可以避免数据不准确的情况。
创建相同线程类的多个线程:
new Thread(saleTicket, "线程1").start();
new Thread(saleTicket, "线程2").start();
5.1、同步方法
只能有一个线程进入到方法中,其他线程在方法的外面等待
同步方法(使用synchronized修饰重写方法):
class SaleTicket implements Runnable {
@Override
public synchronized void run () {
}
但是这个方法过于极端,虽然只允许一个线程进入,但是只有一个线程能进入
不过可以将synchronized改写为外置方法,而后调用,就可以避免上边的极端:
class SaleTicket implements Runnable {
@Override
public void run() {
while (true) {
test();
if (ticket <= 0) {
break;
}
}
}
}
public synchronized void test() {
}
5.2、同步代码块
将一段代码放到synchronized 然后括起来。就会对这段代码加上锁。
同步代码块(代码块写在循环里,每个线程都有机会进入):
class SaleTicket implements Runnable {
@Override
public void run() {
while (true) {
synchronized (this) {
}
}
}
}
六、Java中的锁
synchronized被成为隐式锁,会自动释放,是一个非公平的锁。
Lock锁被成为显示锁。
两个锁都可以解决线程同步的问题。但是synchronized 更加强大,更加粒度化,更加灵活。所以一般开发时候用synchronized。
6.1、Lock锁
Lock是一个接口,实现ReentrantLock接口。
有两个重要方法:lock() 和 unlock()。
class SaleTicket implements Runnable {
ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
}
}
}
七、死锁
开发中禁止出现死锁!
并发场景,多个线程,线程之间在共享数据的时候是互不相让。
死锁是一种状态,当两个线程互相持有对象所需要的资源的时候,这两个线程又都不主动释放资源就会导致死锁。代码无法正常执行。
八、守护线程
守护线程是用来守护非守护线程的,当非守护线程停止运行时,守护线程也会停止。
在在 Java 中,每次程序运⾏⾄少启动两个线程。⼀个是 main 线程,⼀个是垃圾处理线程。
守护线程必须在主线程的前面启动才会和主线程抢占运行
class MyThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("守护线程");
}
}
}
public static void main(String[] args) {
//获取当前线程
Thread thread1 = Thread.currentThread();
Thread thread2 = new Thread(new MyThread8());
//设置thread2为守护线程
thread2.setDaemon(true);
//启动thread2线程
thread2.start();
for (int i = 0; i < 20; i++) {
System.out.println("主线程");
}
}
九、线程的生命周期
1.线程的创建启动 使用new关键字创建了一个线程之 线程调用start()方法启动 2.线程可运行状态 抢占cpu开始准备、等待分配cpu 3.线程运行状态 已抢占到cpu、执行run()方法 4.线程的阻塞 wait、sleep、锁 5.线程的消亡 destroy
十、Object类下面的和线程相关的方法
wait()方法可以使当前线程等待
直到另一个线程调用该对象的notify()方法或者notifyAll()方法
//作为对象型数据
class Message {
}
//创建等待线程
class WaitThread implements Runnable {
private Message message;
//构造方法
public NotifyThread(Message message ) {
this.message = message;
}
@Override
public void run() {
synchronized (message) {
message.wait();
}
}
}
//创建唤醒线程
class NotifyThread implements Runnable {
private Message message;
//构造方法
public NotifyThread(Message message ) {
this.message = message;
}
@Override
public void run() {
synchronized (message) {
message.notifyAll();
}
}
}
//主线程
public class Demo {
public static void main(String[] args) {
new Thread(waitThread1, "wait2线程").start();
new Thread(waitThread2, "wait1线程").start();
new Thread(notifyThread, "notify线程").start();
}
}
十一、join方法
作用:让父线程等待,一直等到子线程结束以后,父线程才会执行。
class FatherThread implements Runnable {
//父线程中启动子线程
Thread thread = new Thread(new SonThread());
thread.start();
//父线程等待
thread.join();
}
class SonThread implements Runnable {
}
public class Demo {
public static void main(String[] args) {
new Thread(new FatherThread()).start();
}
}
十二、生产者消费者模式
消费者需要购买产品,如果生产者有,则直接购买,如果没有,则唤醒生产者生产,消费者等待生产者生产。生产者生产产品后唤醒消费者,消费者购买(可以循环)
//产品类
class Goods {
}
//消费者线程
class Customer implements Runnable {
private Goods goods;
//构造方法
public Customer(Goods goods) {
this.goods = goods;
}
@Override
public void run() {
while (true) {
synchronized (goods) {
if (!goods.isProduct()) {
购买;
//设置生产者需要生产
goods.setProduct(true);
//唤醒生产者生产
goods.notify();
} else {
//没有产品,等待
goods.wait();
}
}
}
}
}
//生产者线程
class Productor implements Runnable {
private Goods goods;
//构造方法
public Productor(Goods goods) {
this.goods = goods;
}
@Override
public void run() {
while (true) {
synchronized (goods) {
生产;
//设置生产者不需要生产
goods.setProduct(false);
//唤醒消费者
goods.notify();
} else {
//产品有剩余,等待生产
goods.wait();
}
}
}
}
十三、线程池
线程池一个容纳了多个线程的容器,其中的线程可以反复的使用。无需反复创建线程而消耗更多的资源。
ThreadPoolExecutor方法线程池是最原始、也是最推荐的手动创建线程池的方式(一共有其中创建线程池的方法,但是开发只用这一种),它一共有七个参数。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
1、核心线程数corePoolSize
2、最大线程数maximumPoolSize
3、空闲线程存活时间keepAliveTime
4、时间单位unit
5、阻塞队列workQueue
(6、7变量可以不写)
排队策略: 1、无界队列: 无界队列是一个没有预定义容量的队列,LinkedBlockingQueue将导致新任务一直在等待,不会有超过核心线程数的线程被创建,因此最大线程数是不起作用的。 LinkedBlockingQueue: 5个任务————核心5个线程 任务不会放到队列中排队 6个任务————核心5个线程 队列中一个只要线程执行完五个以后,最后一个任务随机从5个线程中抽取一个然后执行最后的线程 12个任务————核心5个线程 队列中有七个线程,五个五个地执行,最后两个任务随机从5个线程中抽取一个然后执行最后的线程 2、有界队列: 有界队列能在有限的最大线程数内防止资源耗尽,队列的大小和最大线程数可以互相替换,但是它也难调整和控制。 new ArrayBlockingQueue<>(10): 5个任务————核心5个线程 任务不会放到队列中排队 8个任务————核心5个线程 有界对列中存三个 16个任务————核心5个线程 有界对列存10个 有界对列可以放10个,再开2个核心线程