单线程程序:即,若有多个任务只能依次执行。当上一个任务执行结束后,下一个任务才开始执行。
多线程程序:即,若有多个任务可以同时执行。多个任务可以并发执行。
Java程序的运行原理
由java命令启动JVM,JVM启动就相当于启动了一个进程,该线程在负责java程序的运行,而且这个线程运行的代码存在于main方法中,我们把这个线程称之为主线程。
- 进程:是正在运行的程序
独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位动态性:进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的并发性:任何进程都可以同其他进程一起并发执行
- 线程:是进程中的单个顺序控制流,是一条执行路径
单线程:一个进程如果只有一条执行路径,则称为单线程程序
多线程:一个进程如果有多条执行路径,则称为多线程程序
并发并行
普通解释:
并发:交替做不同事情的能力
并行:同时做不同事情的能力
专业术语:
并发:不同的代码块交替执行
并行:不同的代码块同时执行
并发和并行的意义:
并发和并行都可以处理“多任务”,二者的主要区别在于是否是“同时进行”多个的任务。但是 涉及到任务分解(有先后依赖的任务就不能做到并行)、任务运行(可能要考虑互斥、锁、共享等)、结果合并
CPU的时间观
为什么要进行“并发、并行、多任务、多线程、异步”等等相关的一些操作,就是为了充分利用CPU的巨大潜能,因为很多任务是需要时间的,比如文件的读写、网络数据流的下载等等,如果让CPU处于一个闲置状态,不充分利用它,程序的效率就很低。
线程的三种创建
Thread类
实现步骤
🍉自定义线程类MyThread继承Thread类
🍉重写run()方法,编写线程执行体
🍉创建线程对象,调用start()方法启动线程
public class Demo1 extends Thread{
@Override
public void run() {
for (int i = 0; i <10; i++) {
if (i%2==1){
System.out.println(i);
}
}
}
}
class Text{
public static void main(String[] args) {
Demo1 a = new Demo1();
Demo1 b = new Demo1();
a.start();
b.start();
}
}
Runnable 接口
推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
实现Runnable接口实现多线程的步骤:
1)定义一个类并实现Runnable接口。
2)重写Runnable接口的run()方法,在run()方法中包含线程需要执行的任务。
3)通过Thread类创建线程对象,并把Runnable接口的实现类对象作为参数传递。
4)调用线程对象的start()方法开启线程,并调用Runnable实现类的run()方法。
public class Demo3 implements Runnable{
@Override
public void run() {
for (int i = 0; i <10; i++) {
System.out.println(Thread.currentThread().getName());
}
}
}
class Text1{
public static void main(String[] args) {
Demo3 d3 = new Demo3();
Thread th1 = new Thread(d3,"线程1");
Thread th2 = new Thread(d3,"线程2");
Thread th3 = new Thread(d3,"线程3");
th1.start();
th2.start();
th3.start();
}
}
Callable接口(了解)
1️⃣ 让类实现Callable接口,确定返回值的类型
2️⃣ 重写call方法
3️⃣ 实例化该类
4️⃣ 实例化FutureTask的对象,在构造函数中把刚刚实例化的类放进去
5️⃣ 实例化Thread类的对象,在构造函数中把刚刚实例化的FutureTask的对象放进去
6️⃣ 调用Thread类的对象的start方法
7️⃣ 通过FutureTask类的get方法获得返回值,该方法是阻塞的
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
//1:实现
public class Demo5 implements Callable<String> {
private int num;
private String name;
public Demo5(int num, String name) {
this.num = num;
this.name = name;
}
//2:重写
@Override
public String call() throws Exception {
for (int i = 1; i <=num; i++) {
Thread.sleep(1000);
System.out.println(name+"在烤肠子烤了"+i+"串");
}
return name+"烤完了";
}
}
class Text2{
public static void main(String[] args) {
//3:实例化
Demo5 da = new Demo5(5,"KXBB");
Demo5 db = new Demo5(8,"WQQ");
//4:实例化FutureTask对象
FutureTask<String> ft1 = new FutureTask<>(da);
FutureTask<String> ft2 = new FutureTask<>(db);
//5:实例化Thread对象
Thread t1 = new Thread(ft1);
Thread t2 = new Thread(ft2);
//6:启动
t1.start();
t2.start();
//7:get()获取返回值
try {
String s1 = ft1.get();
System.out.println(s1);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
try {
String s2 = ft2.get();
System.out.println(s2);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
线程状态
设置线程的优先级
public class Demo6 {
public static void main(String[] args) {
Test3 t1 = new Test3(1,"低级");
Test3 t2 = new Test3(10,"高级");
t1.start();
t2.start();
}
}
class Test3 extends Thread{
private int num;
private String name;
public Test3() {
}
public Test3(int num, String name) {
super(name);
this.setPriority(num);
}
@Override
public void run() {
for (int i = 0; i <10; i++) {
System.out.println(getName()+"执行第"+i+"次");
}
}
}
线程睡眠
public class Test2 {
public static void main(String[] args) {
for(int i = 1; i < 10; i++) {
System.out.println(i);
try {
Thread.sleep(1000); // 对主线程休眠1秒钟
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程让步(不一定让,看cpu决策)
public class Test {
public static void main(String[] args) {
new TestThread("高级", 10).start();
new TestThread("低级", 1).start();
}
}
class TestThread extends Thread {
public TestThread() {}
public TestThread(String name, int pro) {
super(name); // 设置线程名字
this.setPriority(pro); // 设置线程的优先级
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(this.getName() + "线程第" + i + "次执行!");
Thread.yield(); // 线程让步
}
}
}
线程合并
Join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞 ,可以想象成插队
public class Demo2 implements Runnable{
public static void main(String[] args) {
Demo2 d2 = new Demo2();
Thread t2 = new Thread(d2);
t2.start();
for (int i = 0; i <50; i++) {
if (i==20){
try {
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("main"+i);
}
}
@Override
public void run() {
for (int i = 0; i <50; i++) {
System.out.println("join"+i);
}
}
}
守护线程
守护线程的用途:
守护线程通常用于执行一些后台作业,例如在你的应用程序运行时播放背景音乐,在文字编辑器里做自动语法检查、自动保存等功能。Java的垃圾回收也是一个守护线程。守护线的好处就是你不需要关心它的结束问题。例如你在你的应用程序运行的时候希望播放背景音乐,如果将这个播放背景音乐的线程设定为非守护线程,那么在用户请求退出的时候,不仅要退出主线程,还要通知播放背景音乐的线程退出;如果设定为守护线程则不需要了。
public class Demo3 {
public static void main(String[] args) {
t1 t1 = new t1();
t2 t2 = new t2();
t1.start();
t2.setDaemon(true);
t2.start();
}
}
class t1 extends Thread{
@Override
public void run() {
for (int i = 0; i <5; i++) {
System.out.println("主线程执行---"+i);
}
}
}
class t2 extends Thread{
int i = 0;
@Override
public void run() {
while (true){
System.out.println("副线程执行---"+(i++));
}
}
}
//运行以上代码,通过执行的结果可以看出:前台线程是保证执行完毕的,后台线程还没有执行完毕就退出了。
线程同步(解决买票问题)
一:同步代码块
Thread类
public class WindowTest {
public static void main(String[] args) {
Window2 w1 = new Window2();
Window2 w2 = new Window2();
Window2 w3 = new Window2();
w1.start();
w2.start();
w3.start();
}
}
class Window2 extends Thread{
private static int ticket = 100;
//private Object obj = new Object();
private static Object obj = new Object();
@Override
public void run() {
while (true){
//synchronized (this){ //错误,此时的this代表 w1 w2 w3三个对象
synchronized (Window2.class){ //扩展:反射 Class cla = Window2.class
//synchronized (obj){
if (ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName()+":卖票,票号为:"+ticket);
ticket--;
}else {
break;
}
}
}
}
}
Runbable接口
public class RunnableTest {
public static void main(String[] args) {
Window w = new Window();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口一:");
t2.setName("窗口二:");
t3.setName("窗口三:");
t1.start();
t2.start();
t3.start();
}
}
class Window implements Runnable{
private static int ticket = 100;
//Object obj = new Object();
@Override
public void run() {
while (true){
//synchronized(obj){
synchronized(this){ //此时的this:唯一的Window的对象
if (ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":卖票,票号为:"+ticket);
ticket--;
}else {
break;
}
}
}
}
}
银行存钱案例
import javax.swing.plaf.basic.BasicMenuBarUI;
import java.util.Map;
public class Demo1 {
public static void main(String[] args) {
Money money = new Money();
Bank bank = new Bank(money);
new Thread(bank,"wqq").start();
new Thread(bank,"zbk").start();
}
}
class Bank implements Runnable{
Money money;
int num = 3000;
public Bank(Money money) {
this.money = money;
}
@Override
public void run() {
synchronized (this){
if (money.getMoney()>=num) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
money.save(num);
System.out.println(Thread.currentThread().getName() + "成功,余额为" + money.getMoney());
}else System.out.println(Thread.currentThread().getName()+"失败,余额为"+money.getMoney());
}
}
}
class Money {
private int money = 5000;
public int getMoney(){
return money;
}
public void save(int num){
money -= num;
}
}
二:同步方法
Thread类《静态方法》
public class WindowDemo {
public static void main(String[] args) {
Window4 w1 = new Window4();
Window4 w2 = new Window4();
Window4 w3 = new Window4();
w1.start();
w2.start();
w3.start();
}
}
class Window4 extends Thread{
private static int ticket = 100;
@Override
public void run() {
while (true){
show();
}
}
public static synchronized void show(){ 同步监视器 Window4.class
//public synchronized void show(){ 同步监视器 w1 w2 w3
if (ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":卖票,票号为:"+ticket);
ticket--;
}
}
}
Runbale接口《非静态》
//同步方法
public class RunnableDemo {
public static void main(String[] args) {
Window3 w = new Window3();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口一:");
t2.setName("窗口二:");
t3.setName("窗口三:");
t1.start();
t2.start();
t3.start();
}
}
class Window3 implements Runnable{
private static int ticket = 100;
@Override
public void run() {
while (true){
if (ticket > 0){
show();
}else {
break;
}
}
}
//同步方法
private synchronized void show(){ //同步监视器 this
if (ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":卖票,票号为:"+ticket);
ticket--;
}
}
}
银行存钱案例
import javax.swing.plaf.basic.BasicMenuBarUI;
import java.util.Map;
public class Demo1 {
public static void main(String[] args) {
Money money = new Money();
Bank bank = new Bank(money);
new Thread(bank,"wqq").start();
new Thread(bank,"zbk").start();
}
}
class Bank implements Runnable{
Money money;
int num = 3000;
public Bank(Money money) {
this.money = money;
}
@Override
public void run() {
synchronized (this){
if (money.getMoney()>=num) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
money.save(num);
System.out.println(Thread.currentThread().getName() + "成功,余额为" + money.getMoney());
}else System.out.println(Thread.currentThread().getName()+"失败,余额为"+money.getMoney());
}
}
}
class Money {
private int money = 5000;
public int getMoney(){
return money;
}
public void save(int num){
money -= num;
}
}
总结:synchronized关键字使用注意点
1、只能同步方法和代码块,而不能同步变量和类。
2、只有共享资源的读写访问才需要同步,如果不是共享资源,根本没有同步的必要。
3、编写线程安全的代码会使系统的总体效率会降低,并且容易发生死锁的情况。
4、无论是同步代码块还是同步方法,必须获得对象锁才能够进入同步代码块或同步方法进行操作。
5、如果是非静态同步方法,对象锁为方法所在的对象;如果是静态同步方法,对象锁为方法所在的类(唯一)。
6、如果两个线程要执行一个类中的静态同步方法或非静态同步方法,那么一次只能有一个线程能够执行方法,另一个需要等待,直到锁被释放。也就是说,如果一个线程在对象上获得一个锁,就没有任何其它线程可以进入(该对象的)类中的任何一个同步方法。
死锁(不常见)
死锁的理解:不同的线程分别占用对方需要的同步资源不放弃,
都在等待对方放弃自己需要的同步资源,就形成了线程死锁
说明:出现死锁后,不会抛异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
我们使用同步时,要避免出现死锁
public class Demo6 {
public static Object obj1 = new Object();
public static Object obj12 = new Object();
public static void main(String[] args) {
new Thread(new Lock1()).start();
new Thread(new Lock2()).start();
}
}
class Lock1 implements Runnable{
@Override
public void run() {
System.out.println("Lock1开始执行");
while (true){
synchronized (Demo6.obj1){
System.out.println("LockA 锁住 obj1");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (Demo6.obj12){
System.out.println("LockA 锁住 obj2");
}
}
}
}
}
class Lock2 implements Runnable{
@Override
public void run() {
System.out.println("Lock2开始执行");
while (true){
synchronized (Demo6.obj12){
System.out.println("LockB 锁住 obj2");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (Demo6.obj1){
System.out.println("LockB 锁住 obj1");
}
}
}
}
}
产生死锁的四个必要条件:
1. 互斥条件:一个资源每次只能被一个进程使用。
2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
3. 不剥夺条件 : 进程已获得的资源,在末使用完之前,不能强行剥夺。
4. 循环等待条件 : 若干进程之间形成一种头尾相接的循环等待资源关系。
线程通讯
wait() :等待,将正在执行的线程释放其执行资格和执行权,进入阻塞状态,并存储到线程池中。
notify():唤醒,唤醒线程池中 wait()的线程,一次唤醒一个,而且是任意的。
notifyAll():唤醒全部,可以将线程池中的所有 wait() 线程都唤醒。
class Number implements Runnable{
private int number = 1;
@Override
public void run() {
while(true){
synchronized (this){
//唤醒被阻塞的单个线程 notifyAll();唤醒所有线程
notify();
if (number <= 100){
System.out.println(Thread.currentThread().getName()+":"+number);
number++;
try {
//使得调用wait()方法的线程进入阻塞状态,此时释放锁
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
break;
}
}
}
}
}
public class CommTest {
public static void main(String[] args) {
Number number = new Number();
Thread t1 = new Thread(number);
Thread t2 = new Thread(number);
t1.setName("线程一:");
t2.setName("线程二:");
t1.start();
t2.start();
}
}
sleep()和 wait()方法的区别
1、sleep不要求同步;wait必须先同步。
2、sleep在休眠的时间内,不能唤醒;wait在等待的时间内,能唤醒。
sleep不释放锁(如果有锁),不释放 CPU 使用权;wait释放锁,释放 CPU 使用权。
生产者消费者案例(在自己可见中,不太懂)
Lock锁接口(解决同步问题)
直接上代码
// 卖票线程任务类
class TicketRunnable implements Runnable {
// 共享数据,保存火车票的总量
private static int ticketNum = 300;
// 创建一个锁对象
Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
lock.lock(); // 获取锁
try {
// 当票数小于等于0时,窗口停止售票,跳出死循环
if (ticketNum <= 0)
break;
// 当票数大于0时,售票窗口开始售票
Thread.sleep(1); // 模拟切换线程的操作
// 输出售票窗口(线程名)卖出的哪一张票
String name = Thread.currentThread().getName();
System.out.println(name + "--卖出第" + ticketNum + "张票");
// 卖票之后,总票数递减
ticketNum--;
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); // 释放锁
}
}
}
}
// 测试类
public class Test2 {
public static void main(String[] args) {
// 创建线程任务类对象
TicketRunnable tr = new TicketRunnable();
// 开启三个售票线程
new Thread(tr, "窗口1").start();
new Thread(tr, "窗口2").start();
new Thread(tr, "窗口3").start();
}
}
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo3 {
public static void main(String[] args) {
Bank2 bank2 = new Bank2(new Money2());
new Thread(bank2,"wqq").start();
new Thread(bank2,"zbk").start();
}
}
class Bank2 implements Runnable{
public Bank2(Money2 money2) {
this.money2 = money2;
}
int num = 3000;
Money2 money2 = new Money2();
static ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
lock.lock();
try {
if (money2.getMoney()>=num){
Thread.sleep(1);
money2.save(num);
System.out.println(Thread.currentThread().getName()+"成功,余额为"+money2.getMoney());
}else System.out.println(Thread.currentThread().getName()+"失败,余额为"+money2.getMoney());
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
class Money2{
private int money = 5000;
public int getMoney(){
return money;
}
public void save(int num){
money -= num;
}
}
synchronized 与 Lock 的对比
Lock是显式锁(手动开启和关闭锁,别忘记关闭锁)
synchronized是隐式锁,出了作用域自动释放
Lock只有代码块锁,synchronized有代码块锁和方法锁
使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展
性(提供更多的子类)
优先使用顺序:
Lock > 同步代码块(已经进入了方法体,分配了相应资源)> 同步方法(在方
法体之外)
面试记背
相同:二者都是可以解决线程安全问题
不同:synchronized机制在执行完相应的同步代码块以后,自动的释放同步监视器
Lock需要手动的启动同步( lock() ),同时结束同步也需要手动的实现(unlock())