1.多线程
1.1并发和并行
- 并发:两个或多个事件在同一时间段发生
- 并行:两个或多个事件在同一时刻发生
1.2线程和进程
- 进程是程序的一次执行过程,进程是系统运行应用程序的基本单位,一个应用程序可以同时运行多个进程
- 线程是进程中的一个执行单元,一个进程至少由一个线程,负责程序的执行
1.3线程调度的两种方式
- 分时调度:所有线程轮流使用CPU,平均分配每个线程使用CPU的时间
- 抢占式调度:Java让优先级高的线程执行,优先级相同则随机选择一个线程执行
1.4创建多线程的两种方式
-
多个线程在不同的栈空间里执行,互不影响
-
第一种实现方式:创建Thread的子类,并且重写run方法,一般不用
public class Main {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
for (int i = 0; i < 10; i++) {
System.out.println("主线程执行:" + i);
}
}
}
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("自定义线程执行:" + i);
}
}
}
- 第二种实现方式:实现Runnable接口,重写run方法,使用Thread(Runnable target)分配Thread对象
- 实现Runnable接口创建多线程的好处
- 避免单继承的局限性
- 将设置线程任务和开启线程两个过程解耦
public class Main {
public static void main(String[] args) {
RunnableImpl run = new RunnableImpl();
Thread thread = new Thread(run);
thread.start();
new Thread(new RunnableImpl()).start();
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
}
}
class RunnableImpl implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
}
}
1.5获取线程名称的两种方式
String getName()
返回线程名称static Thread currentThread()
返回当前正在执行线程的引用
public class Main {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
for (int i = 0; i < 10; i++) {
System.out.println("主线程执行:" + i);
}
//获取线程名称方法二
String name = Thread.currentThread().getName();
System.out.println(name);//main
}
}
class MyThread extends Thread {
@Override
public void run() {
//获取线程名称方法一
String name = getName();
System.out.println(name);//Thread-0
for (int i = 0; i < 10; i++) {
System.out.println("自定义线程执行:" + i);
}
}
}
2.线程安全
2.1线程同步
-
当使用多线程访问同一资源时,且多个线程对资源有写操作,容易出现线程安全问题,Java中使用同步机制解决
public class Main { public static void main(String[] args) { RunnableImpl runnable = new RunnableImpl(); //三个线程共享一个实现类对象,保证共享资源为100张票 new Thread(runnable).start(); new Thread(runnable).start(); new Thread(runnable).start(); //出现重复的票和不存在的票 } } class RunnableImpl implements Runnable { //定义一个多线程共享的资源 private int ticket = 100; //设置线程任务买票 @Override public void run() { //让买票操作重复执行 while (true) { //先判断票是否存在 if (ticket > 0) { //提高安全问题出现的概率 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } //执行卖票操作 System.out.println(Thread.currentThread().getName() + "-->" + ticket); ticket--; } } } }
-
同步代码块解决线程安全问题:把可能出现安全问题的代码块用synchronized修饰
public class Main { public static void main(String[] args) { RunnableImpl runnable = new RunnableImpl(); //三个线程共享一个实现类对象,保证共享资源为100张票 new Thread(runnable).start(); new Thread(runnable).start(); new Thread(runnable).start(); //出现重复的票和不存在的票 } } class RunnableImpl implements Runnable { //定义一个多线程共享的资源 private int ticket = 100; //创建一个锁对象 Object object = new Object(); //设置线程任务买票 @Override public void run() { //让买票操作重复执行 while (true) { synchronized (object) { //先判断票是否存在 if (ticket > 0) { //提高安全问题出现的概率 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } //执行卖票操作 System.out.println(Thread.currentThread().getName() + "-->" + ticket); ticket--; } } } } }
-
同步方法解决线程安全问题:把可能出现安全问题的代码块形成一个方法并用synchronized修饰该方法
public class Main { public static void main(String[] args) { RunnableImpl runnable = new RunnableImpl(); //三个线程共享一个实现类对象,保证共享资源为100张票 new Thread(runnable).start(); new Thread(runnable).start(); new Thread(runnable).start(); //出现重复的票和不存在的票 } } class RunnableImpl implements Runnable { //定义一个多线程共享的资源 private int ticket = 100; //创建一个同步方法 public synchronized void sellTicket() { //先判断票是否存在 if (ticket > 0) { //提高安全问题出现的概率 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } //执行卖票操作 System.out.println(Thread.currentThread().getName() + "-->" + ticket); ticket--; } } //设置线程任务买票 @Override public void run() { //让买票操作重复执行 while (true) { sellTicket(); } } }
-
锁机制解决线程安全问题:Lock接口实现
public class Main { public static void main(String[] args) { RunnableImpl runnable = new RunnableImpl(); //三个线程共享一个实现类对象,保证共享资源为100张票 new Thread(runnable).start(); new Thread(runnable).start(); new Thread(runnable).start(); //出现重复的票和不存在的票 } } class RunnableImpl implements Runnable { //定义一个多线程共享的资源 private int ticket = 100; //创建一个锁对象 Lock lock = new ReentrantLock(); //设置线程任务买票 @Override public void run() { //让买票操作重复执行 while (true) { lock.lock();//加锁 //先判断票是否存在 if (ticket > 0) { //提高安全问题出现的概率 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } //执行卖票操作 System.out.println(Thread.currentThread().getName() + "-->" + ticket); ticket--; } lock.unlock();//解锁 } } }
3.线程状态
3.1等待唤醒案例:线程之间的通信
-
创建一个消费者线程,调用wait方法之后,放弃CPU进入WAITING状态(无限等待)
-
创建一个生产者线程,调用Notify方法唤醒消费者进程
/** * @ClassName WaitAndNotify * @Description TODO * @Author hulin * @Date 2020/1/23 21:11 * @Version 1.0.0 */ /* 进入到TimeWaiting(计时等待)有两种方式 1.使用sleep(long m)方法,在毫秒值结束之后,线程睡醒进入到Runnable/Blocked状态 2.使用wait(long m)方法,wait方法如果在毫秒值结束之后,还没有被notify唤醒,就会自动醒来,线程睡醒进入到Runnable/Blocked状态 唤醒的方法: void notify() 唤醒在此对象监视器上等待的单个线程。 void notifyAll() 唤醒在此对象监视器上等待的所有线程。 */ public class WaitAndNotify { public static void main(String[] args) { //创建锁对象,保证唯一 Object obj = new Object(); // 创建一个顾客线程(消费者) new Thread(){ @Override public void run() { //一直等着买包子 while(true){ //保证等待和唤醒的线程只能有一个执行,需要使用同步技术 synchronized (obj){ System.out.println("顾客1告知老板要的包子的种类和数量"); //调用wait方法,放弃cpu的执行,进入到WAITING状态(无限等待) try { obj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } //唤醒之后执行的代码 System.out.println("包子已经做好了,顾客1开吃!"); System.out.println("---------------------------------------"); } } } }.start(); // 创建一个顾客线程(消费者) new Thread(){ @Override public void run() { //一直等着买包子 while(true){ //保证等待和唤醒的线程只能有一个执行,需要使用同步技术 synchronized (obj){ System.out.println("顾客2告知老板要的包子的种类和数量"); //调用wait方法,放弃cpu的执行,进入到WAITING状态(无限等待) try { obj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } //唤醒之后执行的代码 System.out.println("包子已经做好了,顾客2开吃!"); System.out.println("---------------------------------------"); } } } }.start(); //创建一个老板线程(生产者) new Thread(){ @Override public void run() { //一直做包子 while (true){ //花了5秒做包子 try { Thread.sleep(5000);//花5秒钟做包子 } catch (InterruptedException e) { e.printStackTrace(); } //保证等待和唤醒的线程只能有一个执行,需要使用同步技术 synchronized (obj){ System.out.println("老板5秒钟之后做好包子,告知顾客,可以吃包子了"); //做好包子之后,调用notify方法,唤醒顾客吃包子 //obj.notify();//如果有多个等待线程,随机唤醒一个 obj.notifyAll();//唤醒所有等待的线程 } } } }.start(); } }
3.2等待唤醒机制
- 多个线程在处理同一个资源且任务不同时,需要线程通信来帮助解决,需要线程通信来帮助解决线程之间对同一个资源的使用和操作。通过等待唤醒机制使各个线程有效的利用资源。
- wait方法和notify方法都属于Object类的方法,必须在同步代码块或者同步函数中使用,必须由同一个锁对象调用
4.线程池
4.1概念
- 容纳多个线程的容器
(ArrayList,LinkedList,HashSet)
,无需反复创建线程 - 好处:降低资源消耗,提高相应速度,提高线程的可管理性
4.2线程池的实现
-
使用线程池的工厂类
Executors
的静态方法生产一个指定数量的线程池 -
创建一个类实现
Runnable
接口,重写run
方法,设置线程任务 -
调用
ExecutorService
的方法submit传递线程任务(实现类),开启线程执行run方法 -
使用
ExecutorService
的方法shutdown
销毁线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPool {
public static void main(String[] args) {
//1. 使用线程池的工厂类Executors的静态方法生产一个指定数量的线程池
ExecutorService es = Executors.newFixedThreadPool(2);
//3. 调用ExecutorService的方法submit传递线程任务(实现类),开启线程执行run方法
es.submit(new RunnableImpl());//pool-1-thread-2
//线程池会一直开启,使用完了会把线程自动归还给线程池
es.submit(new RunnableImpl());//pool-1-thread-2
es.submit(new RunnableImpl());//pool-1-thread-1
//4. 使用ExecutorService的方法shutdown销毁线程池
es.shutdown();
es.submit(new RunnableImpl());//出现异常
}
}
public class RunnableImpl implements Runnable {
//2. 创建一个类实现Runnable接口,重写run方法,设置线程任务
@Override
public void run() {
//获取线程名称
System.out.println(Thread.currentThread().getName());
}
}