多线程基础
为什么要使用多线程?
理财软件,一般是按日计息,如果每次访问利息是实时计算的,当用户量大的时候,计算的成本就会增加,一般会在凌晨通过定时任务将前一天的利息计算出来,放到一个汇总表中,之后用户查询时只访问汇总表。定时任务会计算每一个用户的数据,当用户量特别大,预计2小时将所有用户任务执行完成,当我们使用单线程处理的话,可能就不能完成,所以需要多线程同时来处理。
进程和线程
进程可以理解为一个应用程序
比如QQ就是一个进程,可以一边视频、一边传输文件、还可以发送聊天表情,每一个功能都是不同的任务,可以理解为是线程,这些不同的任务或者功能可以同时运行
多线程创建方式
继承自Thread
// 创建线程的第一种方式
public class Demo01CreateThread {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+" 主线程执行");
Thread thread = new CreateThread1();
// 启动线程 start
thread.start();
// 直接调用run() 不会启动新的线程,而是在主线程中执行
// thread.run();
}
// 内部类,为了演示方便,在企业中使用可以将线程类放在外面
static class CreateThread1 extends Thread{
// 需要告诉线程要做什么
// run()中是线程执行的逻辑
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" 执行");
}
}
}
线程类实现Runnable接口
// 创建线程的第二种方式
public class Demo02CreateThread {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+" 主线程执行");
Runnable runnable = new CreateThread2();
// 创建线程类
Thread thread = new Thread(runnable);
thread.start();
}
static class CreateThread2 implements Runnable{
public void run() {
System.out.println(Thread.currentThread().getName()+" 执行");
}
}
}
匿名内部类(其实也是使用Runnable)
// 创建线程的第三种方式
public class Demo03CreateThread {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+" 主线程执行");
// 通过匿名内部类创建线程
Thread thread = new Thread(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName()+" 执行");
}
});
// 启动线程
thread.start();
}
}
线程池方式
// 创建线程的第四种方式
public class Demo04CreateThread {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+" 主线程执行");
// 开启线程池
ExecutorService executorService = Executors.newSingleThreadExecutor();
// 提交线程任务
executorService.submit(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName()+" 执行");
}
});
// 关闭线程池
executorService.shutdown();
}
}
守护线程
非守护线程,当主线程停止后,子线程会继续执行完成
守护线程,在主线程停止后,会立刻停止子线程
public class Demo05DaemonThread {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+" 主线程执行");
Thread thread = new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
});
// 将线程设置为守护线程
thread.setDaemon(true);
thread.start();
System.out.println("主线程运行结束");
}
}
线程安全
多个线程同时操作同一个共享变量,如果每次运行结果与预期一致,那么就是线程安全,否则为线程不安全
卖票案例
public class Demo06SellTicket {
public static void main(String[] args) {
Ticket ticket = new Ticket();
Window window = new Window(ticket);
Thread thread = new Thread(window);
// 给线程设置名称
thread.setName("窗口1");
Thread thread1 = new Thread(window);
thread.start();
thread1.setName("窗口2");
thread1.start();
}
// 票对象
static class Ticket {
// 票总数
public int count = 10;
}
// 卖票窗口
static class Window implements Runnable {
private Ticket ticket;
public Window(Ticket ticket) {
this.ticket = ticket;
}
public void run() {
while (ticket.count>0){
sell();
}
}
// 卖票方法
void sell() {
// 判断票数是否大于0
if (ticket.count > 0) {
System.out.println(Thread.currentThread().getName() + "卖出了第" + ticket.count + "张票");
// 卖出票后将总数减一
ticket.count--;
}
}
}
}
线程同步
线程安全的实现方法:
- 互斥同步
- 非阻塞同步
同步时指在多个线程并发访问共享数据时,保证共享数据在同一个时刻只被一个线程使用。互斥是实现同步的一种手段,互斥是因,同步是果,互斥是方法,同步时目的。
在Java中,最基本的互斥同步手段是synchronized关键字
synchronized
-
同步方法,在需要同步的方法上加上synchronized关键字
public class Demo06SellTicket { public static void main(String[] args) { Ticket ticket = new Ticket(); Window window = new Window(ticket); Thread thread = new Thread(window); // 给线程设置名称 thread.setName("窗口1"); Thread thread1 = new Thread(window); thread.start(); thread1.setName("窗口2"); thread1.start(); } // 票对象 static class Ticket { // 票总数 public int count = 10; } // 卖票窗口 static class Window implements Runnable { private Ticket ticket; public Window(Ticket ticket) { this.ticket = ticket; } public void run() { while (ticket.count>0){ sell(); } } // 卖票方法 synchronized void sell() { // 判断票数是否大于0 if (ticket.count > 0) { System.out.println(Thread.currentThread().getName() + "卖出了第" + ticket.count + "张票"); // 卖出票后将总数减一 ticket.count--; } } } }
-
同步代码块
public class Demo06SellTicket { public static void main(String[] args) { Ticket ticket = new Ticket(); Window window = new Window(ticket); Thread thread = new Thread(window); // 给线程设置名称 thread.setName("窗口1"); Thread thread1 = new Thread(window); thread.start(); thread1.setName("窗口2"); thread1.start(); } // 票对象 static class Ticket { // 票总数 public int count = 10; } // 卖票窗口 static class Window implements Runnable { private Ticket ticket; public Window(Ticket ticket) { this.ticket = ticket; } public void run() { while (ticket.count>0){ // this指的是当前Window对象 synchronized(this){ // todo something sell(); // todo something } } } // 卖票方法 void sell() { // 判断票数是否大于0 if (ticket.count > 0) { System.out.println(Thread.currentThread().getName() + "卖出了第" + ticket.count + "张票"); // 卖出票后将总数减一 ticket.count--; } } } }
synchronized锁定的是同一个对象,才能实现同步,如果锁定的是不同的对象,同步无效
public class Demo06SellTicket {
public static void main(String[] args) {
Ticket ticket = new Ticket();
Window window = new Window(ticket);
Window window1 = new Window(ticket);
Thread thread = new Thread(window);
// 给线程设置名称
thread.setName("窗口1");
Thread thread1 = new Thread(window1);
thread.start();
thread1.setName("窗口2");
thread1.start();
}
// 票对象
static class Ticket {
// 票总数
public int count = 10;
}
// 卖票窗口
static class Window implements Runnable {
private Ticket ticket;
public Window(Ticket ticket) {
this.ticket = ticket;
}
public void run() {
while (ticket.count>0){
// this指的是当前Window对象
synchronized(this){
// todo something
sell();
// todo something
}
}
}
// 卖票方法
void sell() {
// 判断票数是否大于0
if (ticket.count > 0) {
System.out.println(Thread.currentThread().getName() + "卖出了第" + ticket.count + "张票");
// 卖出票后将总数减一
ticket.count--;
}
}
}
}
如果锁定的是类对象,给Class类上锁,对类的所有对象实例起作用
public class Demo06SellTicket {
public static void main(String[] args) {
Ticket ticket = new Ticket();
Window window = new Window(ticket);
Window window1 = new Window(ticket);
Thread thread = new Thread(window);
// 给线程设置名称
thread.setName("窗口1");
Thread thread1 = new Thread(window1);
thread.start();
thread1.setName("窗口2");
thread1.start();
}
// 票对象
static class Ticket {
// 票总数
public int count = 10;
}
// 卖票窗口
static class Window implements Runnable {
private Ticket ticket;
public Window(Ticket ticket) {
this.ticket = ticket;
}
public void run() {
while (ticket.count>0){
// this指的是当前Window对象
synchronized(this.getClass()){
// todo something
sell();
// todo something
}
}
}
// 卖票方法
void sell() {
// 判断票数是否大于0
if (ticket.count > 0) {
System.out.println(Thread.currentThread().getName() + "卖出了第" + ticket.count + "张票");
// 卖出票后将总数减一
ticket.count--;
}
}
}
}
synchronized 加在非静态方法上是给当前对象上锁
synchronized 加在静态方法上是给Class类上锁
synchronized(this) 是给当前对象上锁
synchronized(this.getClass()) 是给Class类上锁,对类的所有对象实例都起作用
Lock
除了使用synchronized,还可以使用JUC提供的可重入锁来实现同步
public class Demo06SellTicket {
public static void main(String[] args) {
Ticket ticket = new Ticket();
Window window = new Window(ticket);
Window window1 = new Window(ticket);
Thread thread = new Thread(window);
// 给线程设置名称
thread.setName("窗口1");
Thread thread1 = new Thread(window);
thread.start();
thread1.setName("窗口2");
thread1.start();
}
// 票对象
static class Ticket {
// 票总数
public int count = 10;
}
// 卖票窗口
static class Window implements Runnable {
private Ticket ticket;
private ReentrantLock lock = new ReentrantLock();
public Window(Ticket ticket) {
this.ticket = ticket;
}
public void run() {
while (ticket.count>0){
// 锁定锁对象
lock.lock();
// todo something
sell();
// todo something
// 业务逻辑执行完成后解锁
lock.unlock();
}
}
// 卖票方法
void sell() {
// 判断票数是否大于0
if (ticket.count > 0) {
System.out.println(Thread.currentThread().getName() + "卖出了第" + ticket.count + "张票");
// 卖出票后将总数减一
ticket.count--;
}
}
}
}
死锁
死锁是一个经典的多线程问题,不同的线程都在等待根本不可能释放的锁,从而导致所有的任务都无法继续执行。
使用jps查看所有运行的虚拟机
jps
# 使用jstack -l 进程ID
jstack -l 9132
线程状态
wait()和notify()
主要是实现线程间的通信,如果没有等待通知机制,就需要在一个线程中通过轮询机制来检测某一条件是否达成,轮询会浪费CPU资源,典型的应用场景是生产者、消费者模式
public class Demo08WaitNotify {
public static void main(String[] args) throws InterruptedException {
Product product = new Product();
Producer producer = new Producer(product);
Consumer consumer = new Consumer(product);
Thread threadPro = new Thread(producer);
Thread threadCon = new Thread(consumer);
threadPro.start();
threadCon.start();
// 主线程休眠3秒
Thread.sleep(15000);
product.add();
synchronized (product){
product.notify();
}
}
// 商品
static class Product{
// 商品数量
int count = 0;
// 生成商品
public synchronized void add(){
System.out.println(Thread.currentThread().getName()+"生成了一个商品");
count++;
// 唤醒在Product上等待的进程
this.notify();
}
// 消费商品
public synchronized void consume(){
if(count>0){
System.out.println(Thread.currentThread().getName()+"消费了一个商品");
count--;
}else{
System.out.println(Thread.currentThread().getName()+"等待生产中...");
try {
// 消费者在Product对象上进行等待,等待需要获取当前对象的对象级别锁
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 生产者线程
static class Producer implements Runnable{
private Product product;
public Producer(Product product) {
this.product = product;
}
public void run() {
for (int i = 0; i < 5; i++) {
product.add();
try {
// 模拟生产商品时间
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 消费者线程
static class Consumer implements Runnable{
private Product product;
public Consumer(Product product) {
this.product = product;
}
public void run() {
// 每隔1秒消费一个商品,这里设置消费者消费速度比生成速度快
while (true){
System.out.println("消费者准备消费...");
product.consume();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
wait和sleep
wait | sleep |
---|---|
Object上的方法 | Thread上的方法 |
作用于所有在对象上等待的线程 | 作用于当前线程 |
等待notify()随机唤醒线程,notifyAll唤醒所有线程 | 超过时间自动恢复执行 |
执行wait()后,当前线程释放锁 | 不会释放锁 |
public class Demo09WaitSleep {
public static void main(String[] args) throws InterruptedException {
Locked locked = new Locked();
WaitSleep waitSleep = new WaitSleep(locked);
Thread threadA = new Thread(waitSleep);
threadA.setName("A");
Thread threadB = new Thread(waitSleep);
threadB.setName("B");
threadA.start();
Thread.sleep(1000);
threadB.start();
}
static class Locked{
public synchronized void exec(){
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+" 执行:"+i);
try {
// Thread.sleep(1000);
if(Thread.currentThread().getName().equals("A")){
wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
static class WaitSleep implements Runnable{
private Locked locked;
public WaitSleep(Locked locked) {
this.locked = locked;
}
public void run() {
locked.exec();
}
}
}
线程停止
使用停止标志
public class Demo10ThreadExit {
// 定义是否停止线程
static boolean exit = false;
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
public void run() {
while (!exit){
System.out.println(Thread.currentThread().getName()+" 正在执行");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
Thread.sleep(1000);
// 将退出标志设置为true
exit = true;
System.out.println("将退出标志设置为true");
}
}
使用interrup()
使用interrupt方法不会终止一个正在运行的线程
需要加上判断条件来终止线程,其实也是使用退出标志
public class Demo11ThreadInterrupt {
public static void main(String[] args) throws InterruptedException {
Thread thread = new ThreadInterrupt();
thread.start();
Thread.sleep(100);
// 调用中断方法
thread.interrupt();
System.out.println("主线程执行中断方法");
}
static class ThreadInterrupt extends Thread{
@Override
public void run() {
while (true){
if(this.isInterrupted()){
System.out.println("已经是停止状态,即将退出");
break;
}
}
}
}
}
如果线程在sleep()或者wait()状态下被调用interrupt(),会进入异常处理
public class Demo12ThreadSleepInterrupt {
public static void main(String[] args) throws InterruptedException {
Thread thread = new SleepInterrupt();
thread.start();
Thread.sleep(1000);
thread.interrupt();
}
static class SleepInterrupt extends Thread{
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("在Sleep中被中断");
}
}
}
}
线程优先级
线程可以划分优先级,优先级越高的线程得到CPU资源较多,而不是优先级高的线程先执行
在Java中,线程的优先级分为1-10这10个等级,默认是5,如果小于1或者大于10,会抛异常
public class Demo13ThreadPriority {
public static void main(String[] args) throws InterruptedException {
List<Thread> list = new ArrayList<Thread>();
for (int i = 1; i <= 10; i++) {
Thread thread = new Priority();
thread.setName("thread"+i);
// 设置优先级
thread.setPriority(i);
list.add(thread);
thread.start();
}
Thread.sleep(3000);
for (Thread thread : list) {
thread.interrupt();
}
}
static class Priority extends Thread{
private int i = 0;
public void run() {
while (!this.isInterrupted()){
i++;
}
System.out.println(currentThread().getName()+"执行次数:"+i);
}
}
}
线程等待join
如果主线程需要等待子线程执行完成后才继续执行,就需要使用join方法
public class Demo14ThreadJoin {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+"主线程开始执行");
// 启动子线程处理任务
Thread thread = new Thread(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName()+"子线程开始执行");
// 模拟子线程执行时间
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"子线程执行结束");
}
});
thread.start();
// 等待子线程执行结束
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"主线程执行结束");
}
}
线程让出yield
yield()作用是当前线程放弃CPU资源,将它让给其他的任务,但放弃的时间不确定,有可能刚刚放弃,马上又获取到CPU资源。
public class Demo15ThreadYield {
public static void main(String[] args) {
Thread thread = new MyThread();
thread.start();
}
static class MyThread extends Thread {
@Override
public void run() {
// 获取开始执行时间
long begin = System.currentTimeMillis();
int count = 0;
for (int i = 0; i < 50000000; i++) {
if (i % 1000 == 0) {
// 让出CPU资源
Thread.yield();
}
count = count + i;
}
long end = System.currentTimeMillis();
System.out.println("总耗时: " + (end - begin) + " 毫秒");
}
}
}
多线程并发的特性
原子性
有序性
指令重排序,指令重排序不会影响单个线程执行结果,但是在多线程中就会引发问题。
在本线程内观察,所有操作都是有序的,在一个线程中观察另外一个线程,所有的操作都是无序的。
可见性
多个线程访问同一个变量,当一个线程修改了变量的值,其他线程能够立刻看到修改后的值
Java内存模型
Java内存模型规定了所有的变量都存储在主内存中
每条线程有自己的工作内存,线程的工作内存中保存了被该线程使用的的变量的主内存副本拷贝
线程对变量的所有操作(读取、赋值)都必须在工作内存中进行,而不能直接读写主内存中的变量
不同的线程之间无法直接访问对方工作内存中的变量
线程间变量值的传递需要通过主内存来完成
可见性问题
public class Demo16Visible {
public static void main(String[] args) throws InterruptedException {
Visible runnable = new Visible();
Thread thread = new Thread(runnable);
thread.start();
Thread.sleep(1000);
Visible.flag = false;
System.out.println("已经修改flag为false");
}
static class Visible implements Runnable{
static boolean flag = true;
public void run() {
System.out.println("子线程执行");
while (flag){
// todo something
}
System.out.println("子线程结束");
}
}
}
使用synchronized关键字来解决可见性问题,synchronized还能解决原子性和有序性问题
public class Demo16Visible {
public static void main(String[] args) throws InterruptedException {
Visible runnable = new Visible();
Thread thread = new Thread(runnable);
thread.start();
Thread.sleep(1000);
Visible.flag = false;
System.out.println("已经修改flag为false");
}
static class Visible implements Runnable{
static boolean flag = true;
public void run() {
System.out.println("子线程执行");
while (flag){
synchronized (this){
// todo something
}
}
System.out.println("子线程结束");
}
}
}
锁优化
在Java虚拟机的实现中每个对象都有一个对象头,用于保存对象的系统信息
对象头中有一个称为Mark Word 的部分,它是实现锁的关键
Mark Word 是一个多功能的数据区,可以存放对象的哈希值、对象年龄、锁的指针等信息。
偏向锁
顾名思义,它会偏向于第一个访问锁的线程,如果在运行过程中,同步锁只有一个线程访问,不存在多线程争用的情况,则线程不需要触发不同,这种情况下,就会给线程加一个偏向锁
如果在运行过程中,遇到了其他线程抢占锁,则只有偏向锁的线程会被挂起,JVM会清除它身上的偏向锁,将锁回复到标准的轻量级锁。