首先明确几个概念:
- 进程与线程
进程:简单来讲,一个运行的程序可称为一个进程,每个进程在内存中都有一个独立的运行空间。
线程:是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行.。一个进程最少 有一个线程。
- 并行与并发
并行:指两个或多个事件在同一时刻发生(同时发生)。
并发:指两个或多个事件在同一个时间段内发生。
- 同步与异步
同步:排队执行 , 效率低但是安全.
异步:同时执行 , 效率高但是数据不安全.
- 分时调度与抢占式调度
分时调度:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
抢占式调度:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),在java中多线程采用的是抢占式调度。
**在java中创建多线程有三种方式:**1、继承Thread重写run()方法;2、实现Runnable接口重写run()方法;3、实现Callable接口重写cll()方法,创建FutureTask对象。
Thread
java.lang.Thread类的构造方法:
public Thread()
public Thread(Runnable target)
public Thread(String name)
public Thread(Runnable target, String name)
常用方法:
方法 | 描述 |
---|
public static native void sleep(long millis); | 线程睡眠指定毫秒 | public static void sleep(long millis, int nanos); | 线程睡眠指定毫秒+纳秒 | public synchronized void start(); | 启动线程 | public void interrupt(); | 中断线程 | public static boolean interrupted(); | 测试线程是否是总中断的 | public final void setPriority(int newPriority); | 设置线程优先级 | public final String getName(); | 获取线程名称 | public State getState(); | 获取线程状态 | public static native Thread currentThread(); | 返回当前线程 | public final void setDaemon(boolean on); | 将此线程标记为守护线程或用户线程 |
举例如下:
public class ThreadDemo {
private static final int LOOP_NUMBER = 10;
public static void main(String[] args) {
Thread thread = new MyThread();
thread.start();
for (int i = 0; i < LOOP_NUMBER; i++) {
System.out.println(Thread.currentThread().getName() + "汗滴禾下土" + i);
}
}
}
class MyThread extends Thread {
private static final int LOOP_NUMBER = 10;
@Override
public void run() {
for (int i = 0; i < LOOP_NUMBER; i++) {
System.out.println(Thread.currentThread().getName() + "锄禾日当午" + i);
}
}
}
Runnable
实现Runnable接口重写run()方法,将创建实现对象,创建Thread传入实现对象。
public class RunnableDemo {
private static final int LOOP_NUMS = 10;
public static void main(String[] args) {
Thread thread = new Thread(() -> {
for (int i = 0; i < LOOP_NUMS; i++) {
System.out.println(Thread.currentThread().getName() + "窗前明月光" + i);
}
});
thread.start();
for (int i = 0; i < LOOP_NUMS; i++) {
System.out.println(Thread.currentThread().getName() + "疑是地上霜" + i);
}
}
}
Callable
java.util.concurrent.Callable接口使用步骤:
1. 编写类实现Callable接口 , 实现call方法
class XXX implements Callable {
@Override
public call() throws Exception {
return T;
}
}
2. 创建FutureTask对象 , 并传入第一步编写的Callable类对象
FutureTask future = new FutureTask<>(callable);
3. 通过Thread,启动线程
new Thread(future).start()
Runnable与Callable相同点
-
都是接口 都可以编写多线程程序 -
都采用Thread.start()启动线程
**Runnable 与 Callable的不同点 **
Callable获取返回值 Callalble接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执 行,如果不调用不会阻塞。
守护线程
java中的线程分为用户线程和守护线程,当最后一个用户线程死亡是,守护线程自动死亡。
设置线程为守护线程:
public static void main(String[] args) {
Thread thread = new MyThread();
thread.setDaemon(true);
}
线程的状态
Thread.State 线程有6中状态,6种状态可以转换。
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
线程安全问题
多线程可能会造成数据不安全问题,因此需要采用同步的方式。
卖票案例:如果不采用同步技术,会造成线程不安全,最终余票会出现为负。
public class SynchronizedThreadDemo {
public static void main(String[] args) {
Ticket ticket = new Ticket(10);
new Sale(ticket).start();
new Sale(ticket).start();
}
}
class Sale extends Thread {
Ticket ticket;
Sale(Ticket ticket) {
this.ticket = ticket;
}
@Override
public void run() {
while(true) {
if(ticker.nums>0){
System.out.println(Thread.currentThread().getName() + "正在卖票");
ticket.nums--;
System.out.println("卖出1张票,余票" + ticket.nums);
}else{
break;
}
}
}
}
class Ticket {
int nums;
public Ticket(int nums) {
this.nums = nums;
}
}
同步代码块
对以线程不安全代码采用同步代码块进行线程同步处理:
public class SynchronizedThreadDemo {
public static void main(String[] args) {
Ticket ticket = new Ticket(50);
new Sale(ticket).start();
new Sale(ticket).start();
}
}
class Sale extends Thread {
Ticket ticket;
Sale(Ticket ticket) {
this.ticket = ticket;
}
@Override
public void run() {
while (true) {
synchronized (ticket) {
if (ticket.nums > 0) {
System.out.println(Thread.currentThread().getName() + "正在卖票");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket.nums--;
System.out.println("卖出1张票,余票" + ticket.nums);
} else {
break;
}
}
}
}
}
class Ticket {
int nums;
public Ticket(int nums) {
this.nums = nums;
}
}
同步方法
synchronized 修饰的方法为同步方法,可保证线程安全。
例如StringBuffer类,所有方法同是同步方法,是线程安全的。而StringBuilder是异步的,是线程不安全的。
StringBuffer{
public synchronized int compareTo(StringBuffer another) {
return super.compareTo(another);
}
}
-------------------------------------------------------------------------------------
StringBuilder{
public int compareTo(StringBuilder another) {
return super.compareTo(another);
}
}
对以线程不安全代码采用同步方法进行线程同步处理:
public class SynchronizedThreadDemo {
public static void main(String[] args) {
Ticket ticket = new Ticket(50);
new Sale(ticket).start();
new Sale(ticket).start();
}
}
class Sale extends Thread {
Ticket ticket;
Sale(Ticket ticket) {
this.ticket = ticket;
}
@Override
public void run() {
while (true) {
if (!ticket.sale()) {
break;
}
}
}
}
class Ticket {
int nums;
public Ticket(int nums) {
this.nums = nums;
}
public synchronized boolean sale() {
if (nums > 0) {
System.out.println(Thread.currentThread().getName() + "正在卖票");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
nums--;
System.out.println("卖出1张票,余票" + nums);
return true;
} else {
return false;
}
}
}
显式锁Lock类
java.util.concurrent.locks.Lock 接口,Lock实现提供了比使用synchronized方法和语句可以获得的更广泛的锁定操作。
常用方法:
方法 | 描述 |
---|
void lock(); | 获取锁 | boolean tryLock(); | 只有在调用时锁是空闲的时,才获取锁。 | boolean tryLock(long time, TimeUnit unit); | 如果在给定的等待时间内锁是空闲的,并且当前线程没有被中断,则获取锁。 | void unlock(); | 释放锁。 |
Lock为接口,使用其实现类 java.util.concurrent.locks.ReentrantLock
ReentrantLock类的构造方法
public ReentrantLock()
public ReentrantLock(boolean fair)
对以线程不安全代码采用Lock对象就行处理:
public class SynchronizedThreadDemo {
public static void main(String[] args) {
Ticket ticket = new Ticket(50);
new Sale(ticket).start();
new Sale(ticket).start();
}
}
class Sale extends Thread {
Ticket ticket;
Sale(Ticket ticket) {
this.ticket = ticket;
}
@Override
public void run() {
while (true) {
try {
ticket.lock.lock();
if (ticket.nums > 0) {
System.out.println(Thread.currentThread().getName() + "正在卖票");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket.nums--;
System.out.println("卖出1张票,余票" + ticket.nums);
} else {
break;
}
} catch (Exception e) {
} finally {
ticket.lock.unlock();
}
}
}
}
class Ticket {
int nums;
Lock lock;
{
lock = new ReentrantLock();
}
public Ticket(int nums) {
this.nums = nums;
}
}
公平与非公平锁
能保证每个线程都能拿到锁,那么这个锁就是公平锁。相反,如果保证不了每个线程都能拿到锁,也就是存在有线程饿死,那么这个锁就是非公平锁。
构造公平锁:
public ReentrantLock(boolean fair)
死锁
多线程可能会发生死锁,举个栗子:
public class DeadLockDemo {
public static void main(String[] args) {
Key1 key1 = new Key1();
Key2 key2 = new Key2();
Door1 door1 = new Door1(key1, key2);
Door2 door2 = new Door2(key1, key2);
new Thread(door1).start();
new Thread(door2).start();
}
}
class Door1 implements Runnable {
Key1 key1;
Key2 key2;
Door1(Key1 key1, Key2 key2) {
this.key1 = key1;
this.key2 = key2;
}
@Override
public void run() {
while (true) {
key1.lock1.lock();
System.out.println("Door1打开的,等Door2关门后关门");
key2.lock2.lock();
System.out.println("全部关闭");
key2.lock2.unlock();
key1.lock1.unlock();
}
}
}
class Door2 implements Runnable {
Key1 key1;
Key2 key2;
Door2(Key1 key1, Key2 key2) {
this.key1 = key1;
this.key2 = key2;
}
@Override
public void run() {
while (true) {
key2.lock2.lock();
System.out.println("Door2打开的,等Door1关门后关门");
key1.lock1.lock();
System.out.println("全部关闭");
key1.lock1.unlock();
key2.lock2.unlock();
}
}
}
class Key1 {
Lock lock1;
{
lock1 = new ReentrantLock();
}
}
class Key2 {
Lock lock2;
{
lock2 = new ReentrantLock();
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hqTL4uvf-1628783021802)(C:\Users\TanLong\AppData\Roaming\Typora\typora-user-images\image-20210812225600513.png)]
多线程之间的通信
采用Object类的以下方法;
方法 | 描述 |
---|
public final void wait(); | 使当前线程等待直到被唤醒 | public final native void wait(long timeoutMillis); | 导致当前线程等待,直到它被唤醒,或直到经过一定数量的实时时间。 | public final native void notify(); | 唤醒在此对象的监视器上等待的单个线程。如果有多个线程正在等待这个对象,那么随机其中一个线程将被唤醒 | public final native void notifyAll(); | 唤醒所有等待线程 |
多线程之间通信的经典问题:消费者与生产者问题
public class ProcucerAndComsumer {
public static void main(String[] args) {
Plate plate = new Plate();
new Thread(() -> {
int j = 10;
while (j > 0) {
synchronized (plate) {
if (j % 2 == 0) {
plate.setFood("鱼香肉丝");
} else {
plate.setFood("宫保鸡丁");
}
System.out.println("厨司做好了" + plate.getFood());
j--;
plate.notify();
try {
plate.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "厨司").start();
new Thread(() -> {
int i = 10;
while (i > 0) {
synchronized (plate) {
System.out.println("顾客吃" + plate.getFood());
plate.setFood("");
i--;
plate.notify();
try {
plate.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "顾客").start();
}
}
class Plate {
private String food;
public void setFood(String food) {
this.food = food;
}
public String getFood() {
return food;
}
}
线程池
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程 就会大大降低 系统的效率,因为频繁创建线程和销毁线程需要时间. 线程池就是一个容纳多个线程的容 器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。
线程池的好处 降低资源消耗。 提高响应速度。 提高线程的可管理性。
工厂和工具方法Executor, ExecutorService, ScheduledExecutorService , ThreadFactory ,和Callable此包中定义的类。该类支持以下几种方法:
- 使用常用配置设置创建和返回ExecutorService(ExecutorService.html)设置的方法。
- 创建和返回ScheduledExecutorService(ScheduledExecutorService.html)设置的方法,使用常用的配置设置。
- 创建并返回“包装”ExecutorService的方法,通过使特定于实现的方法不可访问来禁用重新配置。
- 创建并返回将新创建的线程设置为已知状态的ThreadFactory的(ThreadFactory.html)方法。
- 创建并返回一个方法Callable(Callable.html)出来的其他闭包形式,这样他们就可以在需要的执行方法使用Callable
Java中的四种线程池 . ExecutorService
缓存线程池
ExecutorService service = Executors.newCachedThreadPool();
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:" + Thread.currentThread().getName());
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:" + Thread.currentThread().getName());
}
});
定长线程池
ExecutorService service = Executors.newFixedThreadPool(2);
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:" + Thread.currentThread().getName());
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:" + Thread.currentThread().getName());
}
});
单线程线程池
ExecutorService service = Executors.newSingleThreadExecutor();
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:" + Thread.currentThread().getName());
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:" + Thread.currentThread().getName());
}
});
周期性任务定长线程池
ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
service.schedule(new Runnable() {
@Override
public void run() {
System.out.println("俩人相视一笑~ 嘿嘿嘿");
}
}, 5, TimeUnit.SECONDS);
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("俩人相视一笑~ 嘿嘿嘿");
}
},5,2, TimeUnit.SECONDS);
|