文章目录
一、线程简介
1、任务
多任务:如现实生活中边吃饭边玩手机、边上厕所边玩手机等等,看似是同时进行,本质上大脑一时间还是只处理一件事情
2、进程Process
程序:指令和数据的有序集合,是一个静态的概念
进程:在操作系统中运行的程序就是进程,一个进程可以有多个线程,如视频中同时有声音、图形、弹幕。是系统资源分配的单位
3、线程Thread
线程:CPU调度和执行的单位
程序运行时,即使没有自己创建线程也会有多个线程
如果开辟了多个线程,线程的运行由调度器安排
4、多线程
多线程:一条路中任务多了,效率低,为了提高效率,所以使用多线程
普通方法:只有主线程一条执行路径
调用多线程:多条执行路径,主线程和子线程并行交替执行
二、线程实现(重点)
1、线程创建
三种创建方式:
Thread class | 继承Thread类(重点) |
---|---|
Runnable 接口 | 实现Runnable接口(重点) |
Callable 接口 | 实现Callable接口(了解) |
2、Thread 类创建:
-
自定义线程类并继承Thread类
public class Thread01 extends Thread{}
-
重写run()方法,方法体内写此线程的执行代码
@Override public void run() { for (int i = 0; i < 20; i++) { System.out.println("子线程"+i); } }
-
创建对象并调用start()方法启动线程
Thread01 thread01 = new Thread01(); thread01.start();
3、实现Runnable接口
由于java单继承性,建议使用Runnable接口,方便同一个对象被多个线程使用
-
定义MyRunnable类实现Runnable接口
public class MyRunnable implements Runnable{}
-
实现run()方法,方法体内写此线程的执行代码
@Override public void run() { for (int i = 0; i < 200; i++) { System.out.println("子线程"+i); } }
-
创建线程对象,调用start()方法启动线程
MyRunnable myRunnable =new MyRunnable();//创建实现类对象 Thread thread = new Thread(myRunnable);//创建代理类对象 thread.start();
4、实现Callable接口
-
实现Callable接口,需要有返回值
public class Demo04 implements Callable<Boolean> {}
-
重写call方法并抛出异常
@Override public Boolean call() throws Exception { for (int i = 0; i < 20; i++) { System.out.println("子线程"+i); } return false; }
-
创建目标对象
Demo04 demo04 = new Demo04();
-
创建执行服务
//创建执行服务,参数1为开启的线程个数 ExecutorService ser = Executors.newFixedThreadPool(1);
-
提交执行
Future<Boolean> r1 = ser.submit(demo04);
-
获取结果
boolean rs1 =r1.get();
-
关闭服务
ser.shutdownNow();
优点:
- 可以设置返回值
- 可以抛出异常
5、线程使用示例
- 从网页下载图片
public class Demo02 extends Thread{
private String url;
private String name;
@Override
public void run() {
WebDownLoader webDownLoader = new WebDownLoader();
webDownLoader.downloader(url,name);
System.out.println("下载了文件"+name);
}
public Demo02(String url, String name) {
this.url = url;
this.name = name;
}
public static void main(String[] args) {
Demo02 p1 = new Demo02("https://img2.baidu.com/it/u=4084621093,2971972319&fm=253&fmt=auto&app=120&f=JPEG?w=889&h=500","风景1.jpg");
Demo02 p2 = new Demo02("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fup.enterdesk.com%2Fedpic%2Fe5%2Fee%2F39%2Fe5ee394171595fe762878968bf537f0c.jpg&refer=http%3A%2F%2Fup.enterdesk.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1659679207&t=00070b6889cf07d08416aec421e18162","风景2.jpg");
Demo02 p3 = new Demo02("https://img1.baidu.com/it/u=357253313,4109281191&fm=253&fmt=auto&app=120&f=JPEG?w=889&h=500","风景3.jpg");
p1.start();
p2.start();
p3.start();
/*
运行结果
下载了文件风景2.jpg
下载了文件风景3.jpg
下载了文件风景1.jpg
*/
}
}
class WebDownLoader{
public void downloader(String url,String name){
try {
//从URL下载文件,并命名为name
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("下载器异常");
}
}
}
该例程表明:线程并不一定立即执行,会由CPU
- 龟兔赛跑
public class Race implements Runnable{
private String winner ;
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
if (gameover(i)){
break;
}
//让兔子睡觉
if(Thread.currentThread().getName().equals("兔子")&&i%20==0){
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"跑了"+i+"步");
}
}
//判断比赛是否产生胜者
private boolean gameover(int i){
if(i>=100){
System.out.println("winner is "+(winner = Thread.currentThread().getName()));
return true;
}
if (winner!=null){
return true;
}
return false;
}
public static void main(String[] args) {
Race race = new Race();
new Thread(race,"兔子").start();
new Thread(race,"乌龟").start();
}
}
- 买票
public class Tick implements Runnable{
private int ticknum = 100;
private int i=0;
private int j=0;
private int k=0;
boolean flag1=true;
boolean flag2=true;
//买票方法
public void get(){
if (ticknum <=0){
getnum();
if(flag1){//统计三人共拿到多少票
System.out.println("共拿到"+(i+j+k)+"张票");
}
flag1=false;
flag2= false;
return;
}
try {
//防止资源分配过快,全给一个线程,所以使用sleep延时
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//Thread.currentThread().getName():获得当前线程名字
switch (Thread.currentThread().getName()){
case "小明":i++;break;
case "小红":j++;break;
case "小华":k++;break;
}
System.out.println(Thread.currentThread().getName()+"拿到了第---->"+(ticknum--)+"张票");
}
@Override
public void run() {
while (flag2)
{
get();
}
}
//统计三人分别拿了多少票
public void getnum(){
switch (Thread.currentThread().getName()){
case "小明":
System.out.println("小明拿到"+i+"票");
break;
case "小红":
System.out.println("小红拿到"+j+"票");
break;
case "小华":
System.out.println("小华拿到"+k+"票");
}
}
public static void main(String[] args) {
Tick tick = new Tick();
Thread thread1=new Thread(tick,"小明");
Thread thread2=new Thread(tick,"小红");
Thread thread3=new Thread(tick,"小华");
thread1.start();
thread2.start();
thread3.start();
}}
-
银行取款
public class SynchronizedBank { public static void main(String[] args) { Account account = new Account("小金库",500); Bank you = new Bank(account,300,"you"); Bank girlFriend = new Bank(account,300,"girlFriend"); you.start(); girlFriend.start(); } } //创建一个账户 class Account{ String name; int money; public Account(String name,int money){ this.name=name; this.money=money; } } class Bank extends Thread{ final Account account; int getMoney; int nowMoney; public Bank(Account account, int getMoney, String name){ //父类的构造方法中有有参构造name,子类的构造方法直接使用super将name传递给父类, // 此时相当于子类也有参数name,具体参考oop笔记中的继承 super(name); this.account=account; this.getMoney = getMoney; } @Override public void run() { synchronized (account){ if (account.money- getMoney <0){ System.out.println("余额不足,"+this.getName()+"取款失败"); return; } try { Thread.sleep(200); } catch (InterruptedException e) { throw new RuntimeException(e); } //取钱 account.money-= getMoney; nowMoney += getMoney; System.out.println(account.name+"余额:"+account.money); System.out.println(this.getName()+"手里的钱为:"+this.nowMoney); } } }
-
结合list
public class SynchronizedList { public static void main(String[] args) { List<String> list = new ArrayList<String>(); for (int i = 0; i < 1000; i++) { new Thread(()->{ list.add(Thread.currentThread().getName()); }).start(); } try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(list.size()); } } /*结果:999*/
3.4.5例程表明:多个线程操作同一个资源时,线程不安全,数据紊乱,及并发问题
6、lambda表达式
其实质属于函数式编程的概念
作用:
- 避免匿名内部类定义过多
- 使代码看起来更简洁
- 去掉一堆没有意义的代码,只剩下核心逻辑
函数式接口function interface的定义:
任何接口如果只包含一个抽象方法,那么他就是函数式接口,对于函数式接口,我们可以使用lambda表达式来创建该接口的对象
使用:
现有如下接口:
//必须为函数式接口
interface Obj {
public void say();
}
原实现方法:
//创建类实现接口并重写其中方法
class Objs implements Obj{
@Override
public void say() {
System.out.println("lambda1");
}
}
public class Demo06 {
public static void main(String[] args) {
Objs obj1 = new Objs();
obj1.say();
lambda表达式简写:
public class Demo06 {
public static void main(String[] args) {
//无需新建类,直接写实现的方法体即可
Obj obj2=()-> System.out.println("lambda2");
obj2.say();
}
}
7、静态代理模式
为什么用代理模式:
- 一个对象不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
- 提供额外的处理或者不同的操作而在实际对象与调用者之间插入一个代理对象。
代理模式使用:
-
创建代理主题
//抽象角色,真实对象和代理对象的共同接口 interface School{ public void goschool(); }
-
创建真实角色
class Student implements School {//真实角色实现代理主题
public void goschool(){
System.out.println("去上学");//真实角色做的事情
}
}
- 创建代理角色
class Proxy implements School{//代理角色实现代理主题
private School school;//表示被代理人(真实角色)
public Proxy(School school){//传入被代理人(真实角色)
this.school=school;
}
//实现接口的方法
@Override
public void goschool() {
System.out.println("代理之前");//代理角色的额外业务
school.goschool();//被代理人真正的业务
System.out.println("代理之后");//代理角色的额外业务
}
}
-
测试类
public class Demo07 { public static void main(String[] args) { Student student1 = new Student(); student1.goschool();//运行结果只有真实角色的行为 Proxy proxy = new Proxy(student1);//代理角色代理真实角色 proxy.goschool();//运行结果有代理角色和真实角色的行为 } }
三、线程状态
五大线程状态:
常用方法:
方法 | 说明 |
---|---|
thread.setPriority(1); | 更改线程优先级;参数:优先级 |
Thread.sleep(100); | 让当前正在执行的线程休眠;参数:休眠毫秒数 |
thread.join(); | 强制执行 |
Thread.yield(); | 线程礼让,暂停当前正在执行的线程对象,并执行其他线程 |
thread.interrupt(); | 中断线程,最好别用 |
thread.isAlive(); | 测试线程是否处于活动状态 |
getState(); | 观测线程状态 |
thread.getPriority(); | 获取线程优先级; |
1、线程停止
- 建议线程自己停止
- 不推荐使用jdk的stop、destroy方法
- 建议使用一个标志位flag进行终止变量,当flag==false时终止线程运行
标志位停止方法:
public class Demo08 implements Runnable{
private boolean falg = true;//创建falg对象作为标志位
int i=0;
@Override
public void run() {
while (falg){//标志位为ture时线程运行,为fales时停止
System.out.println("run....."+(i++));
}
}
public void stop(){//公共方法控制标志位
falg=false;
}
public static void main(String[] args) {
Demo08 demo08 = new Demo08();
new Thread(demo08).start();
for (int i = 0; i < 1000; i++) {
if (i==900){
demo08.stop();//通过stop方法改变标志位进而停止线程运行
System.out.println("线程停止运行");
break;
}
System.out.println(i);
}
}
}
2、线程休眠——sleep
- sleep(time)指定当前线程阻塞毫秒数
- sleep存在异常interruptedexception
- sleep时间结束后线程进入就绪状态
- sleep可以模拟网络延时(放大问题的发生性),倒计时等
- 每一个对象都有一把锁,sleep不会释放锁
例程:倒计时
public class Demo09{
static void tendown() throws InterruptedException {
int num = 10;
while (true){
Thread.sleep(1000);
System.out.println(num--);
if(num<=0){
break;
}
}
}
public static void main(String[] args) throws InterruptedException {
tendown();
}
}
3、线程礼让——yield
- 让当前正在执行的线程暂停但不阻塞,而是使其运行状态变为就绪,让CPU重新调度,下一次调度还可能调度它,所以礼让不一定成功
使用:
public class Demo10 implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "执行开始");
Thread.yield();//礼让
System.out.println(Thread.currentThread().getName() + "执行结束");
}
public static void main(String[] args) {
Demo10 demo10 = new Demo10();
new Thread(demo10, "a").start();
new Thread(demo10, "b").start();
/*运行结果
a执行开始
b执行开始
b执行结束
a执行结束
*/
}
}
4、线程强制执行——join
- 可以想象为插队,该线程先执行完后,再执行其他线程,其他线程此时阻塞
public class Demo11 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("vip线程"+i);
}
}
public static void main(String[] args) throws InterruptedException {
Demo11 demo11 = new Demo11();
Thread vip = new Thread(demo11);
vip.start();
for (int i = 0; i < 10; i++) {
System.out.println("main线程"+i);
if(i==5){
vip.join();//当i==5时,线程vip强制执行
}
}
/*运行结果
main线程0
main线程1
main线程2
main线程3
main线程4
main线程5
vip线程0
vip线程1
vip线程2
vip线程3
vip线程4
main线程6
main线程7
main线程8
main线程9
*/
}
}
5、观测线程状态——state
线程五个状态:
线程状态监测例程:
public class Demo12 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()-> {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("/*************************/");
});
Thread.State state=thread.getState();
System.out.println(state);//NEW
thread.start();
state= thread.getState();
System.out.println(state);//RUNNABLE
//Thread.State.TERMINATED:表示线程状态为结束
while (state!=Thread.State.TERMINATED){
Thread.sleep(100);
System.out.println(state = thread.getState());//TIMED_WAITING
}
}
}
6、线程优先级——priority
- 调度器根据优先级选择调度线程
- 优先级用数字表示,范围从1到10,默认为5;
- 优先级高不代表优先调度,而是意味该线程权重大,调度概率大。
Tick tick = new Tick();
int priority;
Thread thread1=new Thread(tick);
thread1.setPriority(10);//设置优先级为10
thread1.start();//先设置优先级再启动
priority = thread1.getPriority();//获取优先级
7、守护线程——daemon
- 线程分为用户线程和守护线程
- 虚拟机必须确保用户线程执行完毕,不必等待守护线程执行完毕
- 如:后台记录操作日志、监控内存、垃圾回收等等
实例:
public class Demo13 {
public static void main(String[] args) {
God god = new God();
You you = new You();
Thread thread_god = new Thread(god);
Thread thread_you = new Thread(you);
thread_god.setDaemon(true);//设置为守护线程。默认为false,即默认为用户线程
thread_god.start();
thread_you.start();
}
}
class God implements Runnable{//创建守护线程
@Override
public void run() {
while (true){
System.out.println("上帝保佑你");
}
}
}
class You implements Runnable{//创建用户线程
@Override
public void run() {
for (int i = 0; i < 36500; i++) {
System.out.println("你开心的活着");
}
System.out.println("/*********你与世长辞*********/");
}
}
四、线程同步(重点)
线程同步:线程同步是一种等待机制,多个需要同时访问此对象的线程进入这个**对象的等待池**形成队列,等待前面的线程使用完毕,下一个线程再使用。
线程同步形成条件:队列+锁
1、同步及同步块
- 同步(synchronized):
- 优点:安全
- 缺点:
- 一个线程持有锁就会独占该锁,直至该方法返回才释放锁,此时会导致其他线程阻塞
- 若给一个大的方法上锁,会导致出现性能问题
- 线程同步方法:定义方法时,给方法“上锁”,即给方法加上修饰词synchronized,synchronized默认锁的是this.
以上文线程使用示例3、买票为例,只需将public void get()
变为public synchronized void get()
- 同步块(synchronized(Obj)):
- Obj称为同步监视器
- Obj可以是任何对象,但最好是共享资源
- 同步方法中无需指定监视器,因为监视器就是this
- 同步监视器的执行过程
- 第一个线程访问,锁定同步监视器,执行其中代码
- 第二个线程访问,发现同步监视器被锁,无法访问
- 第一个线程访问完毕,解锁同步监视器
- 第二个线程访问,发现没锁,然后锁定并访问
- 同步块方法:将给共享资源对象作为Obj即可
以上文线程使用示例4、银行取款为例,只需将public void run() {...}
变为public void run() {synchronized (account){...}}
- 同步和同步块:
- 同步锁的是this类,同步块可以锁任何类
- 锁的类应为需要进行增删改操作的类,而不是数据发生了变化的类、
2、死锁
通俗的说就是多个线程相互抱着对方需要的资源,并且都在等待对方释放而形成僵持,在程序当中应避免死锁
实例:小花拿着口红想要镜子,小丽拿着镜子想要口红
public class DeadLock {
public static void main(String[] args) {
Makeup g1 = new Makeup(0,"小花");
Makeup g2 = new Makeup(1,"小丽");
g1.start();
g2.start();
}
}
class Makeup extends Thread{
//使用static保证资源只有一个
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
int choice;
String name;
public Makeup(int choice,String name){
this.choice=choice;
this.name=name;
}
@Override
public void run() {
makeup();
}
private void makeup() {
if (choice==0){
synchronized (lipstick){//获得口红的锁
System.out.println(this.name+"获得口红");
synchronized (mirror){//获得镜子的锁
System.out.println(this.name+"获得镜子");
}
}
}else {
synchronized (mirror){//获得镜子的锁
System.out.println(this.name+"获得镜子");
synchronized (lipstick){//获得口红的锁
System.out.println(this.name+"获得口红");
}
}
}
}
}
class Lipstick{}
class Mirror{}
3、lock锁
- 作用:同synchronized,为了保证线程安全性
- 使用方法:
public class LockTick {
public static void main(String[] args) {
Tick tick = new Tick();
new Thread(tick).start();
new Thread(tick).start();
new Thread(tick).start();
}
}
class Tick implements Runnable {
int tickNum = 10;
//定义lock锁
private final ReentrantLock Lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
Lock.lock();//加锁
if (tickNum > 0) {
Thread.sleep(200);
System.out.println(tickNum--);
} else {
break;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
Lock.unlock();//解锁
}
}
}
}
- lock与synchronized对比
- lock是显式锁,需手动开启和关闭,synchronized是隐式锁,出了作用域自动释放
- lock性能更好
- 优先使用lock
五、线程通信问题
通信方法:
方法 | 说明 |
---|---|
wait | 表示线程一直等待直到其他线程通知。会释放锁 |
wait(long timeout) | 等待指定毫秒 |
notify() | 唤醒一个处于等待状态的线程 |
notifyAll() | 唤醒同一个对象上所有调用wait方法的线程 |
生产者消费者
问题:
- 假设仓库只能存放一件产品,生产者生产后存放到仓库,然后消费者从仓库取走
- 若仓库有产品,则生产者停止生产直到消费者取走产品
- 若仓库没有产品,则消费者停止取产品直到生产者生产出产品
解决方法1:传统方法
public class PC {
public static void main(String[] args) {
Date date = new Date();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
date.increment();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
date.decrement();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"B").start();
}
}
class Date{
private int num = 0;
public synchronized void increment() throws InterruptedException {
while (num!=0){//使用while不用if是为了防止虚假唤醒,换句话说,等待应总是在while中
//判断如果不为0则等待
this.wait();
}
//为0的话num++
num++;
System.out.println(Thread.currentThread().getName()+"=>"+num);
//num++后该唤醒别的线程运作了
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException {
while (num==0){
//判断如果为0则等待
this.wait();
}
//不为0的话num--
num--;
System.out.println(Thread.currentThread().getName()+"=>"+num);
//num--后该唤醒别的线程运作了
this.notifyAll();
}
}
解决方法2:JUC版
public class PC2 {
public static void main(String[] args) {
Date2 date = new Date2();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
date.increment();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
date.decrement();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"B").start();
}
}
class Date2{
/***************区别传统方法********************/
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
/*********************************************/
private int num = 0;
public void increment() throws InterruptedException {
lock.lock();
try {
while (num!=0){//使用while不用if是为了防止虚假唤醒
//判断如果不为0则等待
condition.await();
}
//为0的话num++
num++;
System.out.println(Thread.currentThread().getName()+"=>"+num);
//num++后该唤醒别的线程运作了
condition.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void decrement() throws InterruptedException {
lock.lock();
try {
while (num==0){//使用while不用if是为了防止虚假唤醒
//判断如果为0则等待
condition.await();
}
//不为0的话num--
num--;
System.out.println(Thread.currentThread().getName()+"=>"+num);
//num--后该唤醒别的线程运作了
condition.signalAll();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
}
六、高级主题
线程池
好处:
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池线程,不用每次都创建)
- 便于线程管理
使用方法:
public class Demo {
public static void main(String[] args) {
//创建线程池,参数为线程池大小
ExecutorService service = Executors.newFixedThreadPool(10);
//启动线程
service.execute(new Thread(()-> System.out.println(Thread.currentThread().getName())));
service.execute(new Thread(()-> System.out.println(Thread.currentThread().getName())));
service.execute(new Thread(()-> System.out.println(Thread.currentThread().getName())));
service.execute(new Thread(()-> System.out.println(Thread.currentThread().getName())));
//关闭线程池
service.shutdown();
}
}