多线程
概述
进程:一个应用程序的实例,当启动一个程序时,就创建了该程序的实例,就是开启了一个进程。进程是系统进行资源分配和调度的一个独立单位。
进程是资源分配的基本单位。
线程:运行在进程之中,进程中可以开启线程。
①一个进程中至少有一个线程,通常是主线程。
②一个进程中可以有多个线程,除了主线程外,其他线程都是由主线程中创建的。线程其实就是进程当中的一个 任务执行单元。
③串行:多个任务按照顺序依次执行 。
并发:多个任务在某一时间段内交替执行 。
并行:多个任务在任意时间点上同时执行 。
④多个线程在同时进行—实际是交替进行。并行其实就是最理想最严格的并发状态。
⑤主线程不一定是最后一个执行完毕的,主线程执行完毕不代表程序结束。
⑥如果一个线程报异常,其他不相干的线程继续执行
多线程的好处:
①CPU利用率提高
②用户体验好
③简化开发
线程的实现
创建线程
线程 = 任务 + 执行路径
创建新的执行线程有两种方法:①将类声明为 Thread 的子类。并让子类重写 Thread 类的 run() 方法。
②声明实现Runnable接口的类【推荐】
run():线程体,描述该线程的操作
开启线程
①run()函数不是由我行们来执行, 由JVM来调用执行.
为了开辟一个执路径来运行任务 调用start()方法。
当线程对象调用start()方法时,他只是开辟了一个新的线程,而run()方法由JVM虚拟机在线程开启的时候调用。
package MyTread;
public class Thread01 {
public static void main(String[] args) {
//获取当前线程的对象
Thread mainThread = Thread.currentThread();
//获取线程名字
//System.out.println(mainThread.getName());
//System.out.println(Thread.currentThread().getName());
System.out.println("我是主线程");
ThreadA ta = new ThreadA();
ThreadB tb = new ThreadB();
ta.start();
tb.start();
}
}
class ThreadA extends Thread{
@Override
public void run() {
System.out.println("我是线程A");
for (int i = 0;i < 50;i++){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
class ThreadB extends Thread{
@Override
public void run() {
System.out.println("我是线程B");
for (int i = 0;i < 50;i++){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
继承Thread的弊端:
1.单继承的局限性
2.执行任务和执行路径在一起,增加了耦合度
所以推荐使用下面实现Runnable接口的方式实现多线程。
②
步骤:1.创建类(任务类)实现Runnable接口
2.重写run()方法
3.创建一个Thread对象(执行路径)
4.将执行路径和执行任务关联 new Thread(任务)
5.调用start()方法开始执行
由于这种开启线程的方式是将任务和路径分开,则可以实现一个任务有多个路径来执行,下面举一个卖票的例子说明:
package MyTread;
//卖票
public class Thread03 {
public static void main(String[] args) {
SaleTicketsTask task = new SaleTicketsTask();
Thread t1 = new Thread(task,"窗口1");
Thread t2 = new Thread(task,"窗口2");
Thread t3 = new Thread(task,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
class SaleTicketsTask implements Runnable{
private int number = 100;
@Override
public void run() {
while (true) {
if (number > 0) {
number--;
System.out.println(Thread.currentThread().getName() + "号窗口出票成功,剩余" + number + "张");
}else {
break;
}
}
}
}
线程安全问题
问题:
1.多个线程操作共享数据
2.线程切换具有时间差
3.对共享数据的操作有多条
处理办法:
将对共享数据操作的多条语句进行原子化封装即可,但是封装范围不要过大。
用synchronized代码块进行封装
synchronized (锁对象){
需要封装的代码块
}
锁对象:可以使用任何一个类的独享,但必须保证全局唯一。this也可以当成锁,但需考虑是否是一个对象。
public void run() {
while (true) {
synchronized (lock){
if (number > 0) {
number--;
System.out.println(Thread.currentThread().getName() + "号窗口出票成功,剩余" + number + "张");
}else {
break;
}
}
}
如果一个函数中只有同步代码块,则可以将函数定义为同步函数。同步函数的锁是this。
同步的—线程安全—不会产生共享数据错误的问题
不同步的—线程不安全—会产生共享数据错误
package MyTread;
//多个客户去银行存钱
//多个客户存钱==多个执行任务
public class Thread04 {
public static void main(String[] args) {
Bank bank = new Bank();
Consumer c1 = new Consumer(bank);
Consumer c2 = new Consumer(bank);
Consumer c3 = new Consumer(bank);
Thread t1 = new Thread(c1,"客户1");
Thread t2 = new Thread(c2,"客户2");
Thread t3 = new Thread(c3,"客户3");
t1.start();
t2.start();
t3.start();
}
}
class Bank{
private int sum;
private Object lock = new Object();
public synchronized void add(int num){
sum = sum + num;
System.out.println(Thread.currentThread().getName() + "存钱,sum = " + sum);
}
}
class Consumer implements Runnable{
private Bank bank;
public Consumer(Bank bank){
this.bank = bank;
}
@Override
public void run() {
for (int i = 0;i < 3;i++){
bank.add(100);
}
}
}