Part01:多线程
1、进程和线程的概念
- 进程:正在运行的程序叫做进程;
- 线程:是进程中的一个执行单元,来完成进程中的某个功能;一个进程中至少有一个线程;
2、多线程:多线程是指一个进程中有多个线程在同时执行;
3、程序运行原理
- 分时调度:所有的线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间
- 抢占式调度:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性)
- Java使用的是抢占式调度;
- 大部分操作系统都支持多进程并发运行,感觉好像多个进程在同一时刻运行着,实际上,CPU(中央处理器)使用抢占式调度模式在多个线程之间进行着高速的切换。对应CPU的一个核而言,某个时刻,只能执行一个线程,而CPU在多个线程间切换的速度相对我们的感觉要快,看上去就是在同一时刻运行;
注意⚠️:多线程程序并不能提高程序的运行速度,但能够提高程序的运行效率,让CPU的使用率更高;
4、主线程:
- main方法所在的线程称为主线程
- Java是多线程程序,但是JVM默认只创建一个线程(主线程)
- 在同一个线程中的方法是至上而下运行的
5、Thread类:
-
Thread类就是我们说的线程类,提供了一堆和线程有关的方法;
-
构造:public Thread( );//创建一个默认名字的线程对象
public Thread(String name);//创建一个指定名字的线程对象 -
创建线程的一种方式:继承Thread类,重写run方法;具体步骤如下
//1.创建一个类MyThread继承线程类Thread public class MyThread extends Thread { //2.重写父类的run方法 @Override public void run() { //3.把任务代码放入run方法中 for (int i = 0;i<20;i++){ System.out.println("线程"+getName()+i+"正在执行"); } } } public class ThreadDemo { public static void main(String[] args){ //4.创建子类线程对象 MyThread thread = new MyThread(); //5.开启线程 thread.start(); for (int i =0;i<20;i++){ System.out.println("主线程"+Thread.currentThread().getName()+i+"正在执行"); } } }
6、Runnable接口:
-
创建线程的另一种方式:实现Runnable接口,实现run方法;具体步骤如下:
//1.实现Runnable接口 public class MyRunnable implements Runnable { //2.重写run方法 @Override public void run() { //写任务代码 for (int i = 0;i<20;i++){ System.out.println("线程"+getName()+i+"正在执行"); } } } public class RunnableDemo { public static void main(String[] args){ //3.创建实现类对象 MyRunnable mr = new MyRunnable(); //4.创建Thread对象,并把实现类对象作为参数传递 Thread td = new Thread(mr); //5.启动这个线程 td.start(); for (int i =0;i<20;i++){ System.out.println("主线程"+Thread.currentThread().getName()+i+"正在执行"); } } }
7、两种创建线程方式比较:
-
从耦合性分析:
- 继承Thread类:线程和任务是紧紧联系在一起的,耦合性高
- 实现Runnable接口:线程是线程,任务是任务,他们之间没有必然的联系,给一个线程传递哪个任务那么该线程就执行哪个任务,耦合性低;
-
从代码的扩展性:
- 继承Thread类:由于是继承Thread类,Java只支持类的单继承,所以子类线程不能再继承别的类
- 实现Runnable接口:由于是实现接口,实现类同时可以继承别的类
因此:在开发中,我们一般用实现Runnable接口的方式创建线程
8、使用匿名内部类创建线程对象
-
继承方式:
new Thread(){ @Override public void run() { for (int i =0;i<20;i++){ System.out.println("线程"+getName()+i+"正在执行"); } } }.start();
-
实现方式:
new Thread(new Runnable(){ @Override public void run() { for (int i =0;i<20;i++){ System.out.println("线程"+Thread.currentThread().getName()+i+"正在执行"); } } }).start();
Part02:线程安全
1、线程安全
当有多个线程在同时运行,这些线程同时运行一段代码(即同一个任务,同一个run方法),操作同一个共享数据时,这时候可能就会出现线程的安全问题,即线程不安全;
注意⚠️:如果是单线程,或者是多线程操作的是不同数据,那么一般是没有问题的
2、解决线程安全的方式:
-
同步代码块、给代码加上同步锁
//格式: synchronized(锁对象){ 代码 } //锁对象可以是任意对象 //以卖票为例 public class SaleTicketDemo { public static void main(String[] args) { //3个线程必须是同一个任务 TicketsRunable t = new TicketsRunable(); new Thread(t, "窗口1").start(); new Thread(t, "窗口2").start(); new Thread(t, "窗口3").start(); } } class TicketsRunable implements Runnable { private int tickets = 100; private Object obj = new Object(); @Override public void run() { while (true) { //同步锁 synchronized (obj) { if(tickets > 0) { System.out.println(Thread.currentThread().getName() + "卖出第" + tickets + "张票"); tickets--; } } } } }
-
同步方法
//格式: public synchronized void 方法名(){ 代码 } //以卖票为例 public class SaleTicketDemo { public static void main(String[] args) { //3个线程必须是同一个任务 TicketsRunable t = new TicketsRunable(); new Thread(t, "窗口1").start(); new Thread(t, "窗口2").start(); new Thread(t, "窗口3").start(); } } class TicketsRunable implements Runnable { private int tickets = 100; private Object obj = new Object(); @Override public void run() { while (true) { sale(); } } public synchronized void sale() { System.out.println(Thread.currentThread().getName() + "卖出第" + tickets + "张票"); tickets--; } } }
注意⚠️:
同步代码块和同步方法的原理一样,都是具有锁对象,哪个线程来执行,那么该线程就持有这个锁对象;同步方法使用的锁对象是当前对象 this,如果同步方法是静态方法,那么他的锁对象是当前类.class
- Lock接口方式:
-
我们实际用的是他的一个实现类:ReentrantLock
-
Lock接口中的两个方法:lock( );//上锁、unlock( );//释放锁
//以卖票为例 import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class SaleTicketDemo { public static void main(String[] args) { //3个线程必须是同一个任务 TicketsRunable t = new TicketsRunable(); new Thread(t, "窗口1").start(); new Thread(t, "窗口2").start(); new Thread(t, "窗口3").start(); } } class TicketsRunable implements Runnable { private int tickets = 1000; Lock l = new ReentrantLock(); @Override public void run() { while (true) { l.lock(); if (tickets > 0) { System.out.println(Thread.currentThread().getName() + "卖出第" + tickets + "张票"); tickets--; } l.unlock(); } } }
-
3、线程的状态图解