回顾多线程

一、线程简介

  1. 多任务:同一时间有多个任务要去做
  2. 多线程:原来只有一条路,慢慢的因为车太多了,道路阻塞,效率极低,为了提高使用的效率,能够充分利用道路,于是加了多个车道。从此,麻麻再也不用担心道路阻塞了!
  3. 普通方法调用和多线程
    在这里插入图片描述

进程与线程

线程就是独立的执行路径
在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc(垃圾回收)线程
main()称之为主线程,为系统的入口,用于执行整个程序
在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制
线程会带来额外的开销,如cpu调度时间,并发控制开销(让线程排队执行)
每个线程在自己的工作内存交互,内存控制不当会造成数据不一致。

线程实现

线程创建

线程的创建有三种方式:

  1. 继承Thread
  2. 实现Runnable
  3. 实现Callable
    main函数实自己写的,叫用户线程
    gc线程是JVM给的,叫守护线程

继承Thread类(重点)

不建议使用:为了避免OOP单继承局限性
Thread类本身实现了Runnadle接口

  1. 自定义线程类继承Thread类
  2. 重写run方法,编写程序执行体
  3. 创建线程对象,调用start()方法启动线程
    总结:线程开启不一定立即执行,由CPU调度执行,直接调用run方法相当于调用普通方法,不会创建新的线程
public class TestThread1 extends Thread {//继承Thread类
    public static void main(String[] args) {

       TestThread1 thread1=new  TestThread1();
       // thread1.run();//先执行run方法,再执行接下来的代码(没起到多线程的作用)
        thread1.start();//主线程和子线程交替执行,且不可控,每次执行结果都不一样
        for (int i = 0; i < 20; i++) {
            System.out.println("主线程"+i);
        }
    }
    @Override
    public void run() {
       // super.run();
        for (int i = 0; i < 20; i++) {
            System.out.println("子线程"+i);
        }
    }
}

案例多线程下载图片

需要用到commons-io2.6这个jar包,因为我们需要用到commons-io包里面的FileUtils函数
然后把包拷贝到我们的工程下面,右键 Add as library

package MultiThread;

import org.apache.commons.io.FileUtils;
import sun.misc.FileURLMapper;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;

//实现多线程同步下载网图
public class TestThread2 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 TestThread2(String url, String name) {
        this.url = url;
        this.name = name;
    }

    public static void main(String[] args) {
        TestThread2 t1=new TestThread2("https://image.baidu.com/search/detail?ct=503316480&z=0&ipn=d&word=%E5%B0%8F%E7%8B%97%E5%9B%BE%E7%89%87&hs=2&pn=2&spn=0&di=68640&pi=0&rn=1&tn=baiduimagedetail&is=0%2C0&ie=utf-8&oe=utf-8&cl=2&lm=-1&cs=3788223672%2C2933772794&os=3469738239%2C2819470525&simid=0%2C0&adpicid=0&lpn=0&ln=30&fr=ala&fm=&sme=&cg=&bdtype=0&oriquery=%E5%B0%8F%E7%8B%97%E5%9B%BE%E7%89%87&objurl=https%3A%2F%2Fgimg2.baidu.com%2Fimage_search%2Fsrc%3Dhttp%3A%2F%2Fimage.biaobaiju.com%2Fuploads%2F20191105%2F15%2F1572937434-vjDJNuBAxl.jpg%26refer%3Dhttp%3A%2F%2Fimage.biaobaiju.com%26app%3D2002%26size%3Df9999%2C10000%26q%3Da80%26n%3D0%26g%3D0n%26fmt%3Djpeg%3Fsec%3D1613979841%26t%3D6b74022d98f53152b4e7143934ed2714&fromurl=ippr_z2C%24qAzdH3FAzdH3Fooo_z%26e3Bktw5kwt37_z%26e3Bv54AzdH3Fi5g2geiwtztAzdH3Fl08nc_z%26e3Bip4s&gsm=3&islist=&querylist=","dog.jpeg");
        TestThread2 t2=new TestThread2("https://image.baidu.com/search/detail?ct=503316480&z=0&ipn=d&word=%E5%B0%8F%E7%8B%97%E5%9B%BE%E7%89%87&hs=2&pn=5&spn=0&di=26840&pi=0&rn=1&tn=baiduimagedetail&is=0%2C0&ie=utf-8&oe=utf-8&cl=2&lm=-1&cs=2057173425%2C3288346039&os=3534193242%2C1744020036&simid=4255926711%2C726475710&adpicid=0&lpn=0&ln=30&fr=ala&fm=&sme=&cg=&bdtype=0&oriquery=%E5%B0%8F%E7%8B%97%E5%9B%BE%E7%89%87&objurl=https%3A%2F%2Fgimg2.baidu.com%2Fimage_search%2Fsrc%3Dhttp%3A%2F%2Fimg.wohaoyun.com%2Fimg_600%2FM00%2F07%2FB8%2FwKjg2lvW-dGAERdjAAEeYGzmpUA906.jpg%26refer%3Dhttp%3A%2F%2Fimg.wohaoyun.com%26app%3D2002%26size%3Df9999%2C10000%26q%3Da80%26n%3D0%26g%3D0n%26fmt%3Djpeg%3Fsec%3D1613979935%26t%3Dbd8042b4b6488deae4c73854e0c4dd6e&fromurl=ippr_z2C%24qAzdH3FAzdH3Fooo_z%26e3Bo5iw5y7g_z%26e3Bv54AzdH3Ff3AzdH3F15g2o7ktzitAzdH3Fda8bAzdH3F88AzdH3F8988_z%26e3Bip4s&gsm=6&islist=&querylist=","dog2.jpeg");
        TestThread2 t3=new TestThread2("https://image.baidu.com/search/detail?ct=503316480&z=0&ipn=d&word=%E5%B0%8F%E7%8B%97%E5%9B%BE%E7%89%87&hs=2&pn=4&spn=0&di=13750&pi=0&rn=1&tn=baiduimagedetail&is=0%2C0&ie=utf-8&oe=utf-8&cl=2&lm=-1&cs=2136279165%2C2845293579&os=1684394320%2C374773828&simid=3423840844%2C312231934&adpicid=0&lpn=0&ln=30&fr=ala&fm=&sme=&cg=&bdtype=0&oriquery=%E5%B0%8F%E7%8B%97%E5%9B%BE%E7%89%87&objurl=https%3A%2F%2Fgimg2.baidu.com%2Fimage_search%2Fsrc%3Dhttp%3A%2F%2Fwww.cpnic.com%2FUploadFiles%2Fimg_0_3308088708_3867205912_26.jpg%26refer%3Dhttp%3A%2F%2Fwww.cpnic.com%26app%3D2002%26size%3Df9999%2C10000%26q%3Da80%26n%3D0%26g%3D0n%26fmt%3Djpeg%3Fsec%3D1613980066%26t%3D73944a43d2a1181ed0e4aeb6cd59da1e&fromurl=ippr_z2C%24qAzdH3FAzdH3Fooo_z%26e3Bvrgtv_z%26e3Bv54AzdH3FrtvAzdH3F%25Ec%25Ba%25bF%25Ec%25lE%25bB%25E0%25bB%25l0%25Ec%25ln%25b8%25E0%25A0%25bD%25Ec%25A9%25A0%25Ec%25bc%25Ab%25Ec%25bF%25bA%25Ec%25lB%25BE%25E0%25bl%25b0%25Ec%25bF%25bA%25E9%25BB%25B0%25Em%25Aa%25BCAzdH3F&gsm=5&islist=&querylist=","dog3.jpeg");
        t1.start();
        t2.start();
        t3.start();

    }
}
//下载器
class WebDownLoader{
    //下载方法
    public void downloader(String url,String name)  {
        try {
            FileUtils.copyURLToFile(new URL(url),new File(name));//copyURLToFile,把url变成文件
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO异常,downloader方法出现问题");
        }
    }
}

实现Runnable接口(重点)

Java是单继承,推荐使用Runnable接口,方便同一个独享被多个线程使用
避免了单继承的局限性:即在Java中一个类只能使用extends继承一个父类,如果继承多个父类,而父类有同名方法时就不知道调用哪一个方法了,另外回事两个类的耦合性增加,如果父类有改动时会直接影响子类。

  1. 定义MyRunnable类实现Runnable接口
  2. 实现Run()方法,编写程序执行体
  3. 创建线程对象,调用start()方法启动线程
package MultiThread;

public class TestThread3 implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("子线程"+i);
        }
    }

    public static void main(String[] args) {
        //创建Runnable接口实现类的对象
        TestThread3 testThread3 = new TestThread3();
//创建线程类对象,通过线程对象开启我们的线程(代理)
//        Thread thread=new Thread(testThread3);
//        thread.start();
        new Thread(testThread3).start();
        for (int i = 0; i < 20; i++) {
            System.out.println("主线程"+i);
        }
    }
}

初始并发问题

多个线程操作同一个资源的情况下出现不同的线程抢到同一张票,线程不安全,数据会紊乱

package MultiThread;
//多个线程操作同一个对象
//买火车票
//多个线程操作同一个资源的情况下出现不同的线程抢到同一张票,线程不安全,数据紊乱
public class TestTheread4 implements Runnable{

    int ticketNums=100;//票数
    public static void main(String[] args) {
        TestTheread4 testTheread4 = new TestTheread4();
        new Thread(testTheread4,"1").start();//1,2,3是线程名字
        new Thread(testTheread4,"2").start();
        new Thread(testTheread4,"3").start();
    }
    @Override
    public void run() {
        while (true)
        {
           if(ticketNums<=0)
           {
               break;
           }
            try {
                Thread.sleep(200);//模拟延时
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"-->拿到了第"+ticketNums--+"张票");
        }
   }
}


在这里插入图片描述

龟兔赛跑
/*
1.首先来个赛道距离,然后要离终点越来越近
2.判断比赛是否结束
3.打印出胜利者
4.龟兔赛跑开始
5.故事中是乌龟赢的,兔子需要睡觉,所以我们来模拟兔子睡觉
6.终于,乌龟赢得比赛
 **/
public class Race implements Runnable{
    private static String winner;//用static,保证只有一个胜利者

    public static void main(String[] args) {
        Race Rubbit=new Race();
        Race tortise=new Race();
        new Thread(Rubbit,"兔子").start();
        new Thread(tortise,"乌龟").start();
    }

    @Override
    public void run() {
        for (int i = 0; i <= 1000; i++) {
            //模拟兔子休息,每10步休息一下
if(Thread.currentThread().getName().equals("兔子")&&i%10==0)//注意不要用==
{
    try {
        Thread.sleep(10);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}


            //判断比赛是否结束
            boolean flag=gameOver(i);
            //如果比赛结束,停止程序
            if(flag)
            {
                break;
            }

            System.out.println(Thread.currentThread().getName()+"-->跑了"+i+"步");
        }
    }
    //判断是否完成比赛
    private boolean gameOver(int steps)
    {
        if(winner!=null)
        {
            return true;
        }
        {
            if(steps>=1000) {
                winner= Thread.currentThread().getName();
                System.out.println("winneer is"+winner);
                return true;
            }
        }
        return false;
    }
}

线程状态

五大状态
在这里插入图片描述
创建状态:new
就绪状态:start
阻塞状态:sleep(sleep只是其中一个)
死亡状态:正常执行完
在这里插入图片描述
在这里插入图片描述
停止线程
不推荐使用jdk提供的stop(),destory()方法,建议使用一个标志位进行终止变量,当flag=false,则线程终止运行

public class Teststop implements Runnable {
//1.线程中定义线程体使用的标识
private boolean flag = true;
@Override
public void run (){
/ /2 .线程体使用该标识
while (flag) {
systepaoit.println ( "run. . . Thread" );
}
}
//3.对外提供方法改变标识
public void stop(){
this.flag = false;
}
}


public class TestStop implements Runnable {
    private boolean flag=true;
    @Override
    public void run() {
        int i=0;
        while (flag){
            System.out.println("run.....Thread"+(i++));
        }
    }
    public void stop(){
        this.flag=false;
    }public static void main(String[] args) {
        TestStop testStop = new TestStop();
        new Thread(testStop).start();
        for (int i = 0; i <1000 ; i++) {
            System.out.println("main"+i);
            if (i==900){
                testStop.stop();
                System.out.println("run线程停止了!");
            }
        }
    }
}

线程休眠(sleep)

sleep(时间)指定当前线程阻塞的毫秒数;
sleep存在异常InterruptedException;
sleep时间达到后线程进入就绪状态;
sleep可以模拟网络延时(放大问题的发生性,比如多线程卖票,一票多卖),倒计时等。
每一个对象都有一个锁,sleep不会释放锁。

package MultiThread;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.SimpleFormatter;

//模拟倒计时
public class TestSleep implements Runnable {

    public static void main(String[] args) throws InterruptedException {
        tenDown();
        //打印当前系统时间
        Date startTime=new Date(System.currentTimeMillis());
        while(true)
        {
            Thread.sleep(1000);
            System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
            startTime=new Date(System.currentTimeMillis());

        }
    }
    public static void tenDown() throws InterruptedException {//模拟倒计时
        int num=10;
        while(true)
        {
          Thread.sleep(1000);
            System.out.println(num--);
            if(num<=0)
            {
                break;
            }
        }
    }

    @Override
    public void run() {
    }
}

线程礼让(yield)

礼让线程,让当前正在执行的线程暂停,但不阻塞
将线程从运行状态转为就绪状态
让CPU重新调度,礼让不一定成功

package MultiThread;

public class TestYield {
    public static void main(String[] args) {
        MyYield myYield=new MyYield();
        new Thread(myYield,"a").start();
        new Thread(myYield,"b").start();
    }
}
class MyYield implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"线程开始执行");
        Thread.yield();
        System.out.println(Thread.currentThread().getName()+"线程结束执行");
    }
}

线程同步

多个线程操作同一个对象的安全问题(买火车票)
现实生活中,我们会遇到”同一个资源,多个人都想使用”的问题,比如,食堂排队打饭﹐每个人都想吃饭﹐最天然的解决办法就是﹐排队﹒一个个来.

处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象.这时候我们就需要线程同步,线程同步其实就是一种等待机制。多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用

线程同步

由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可,存在以下问题:

一个线程持有锁会导致其它所有需要此锁的线程挂起;

在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题;

如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题。

线程同步形成条件:队列+锁

三大不安全案例

①买火车票
线程不安全

package MultiThread.syn;
//不安全的买票
public class UnsafeBuyTicket {

    public static void main(String[] args) {
        BuyTicket buyTicket = new BuyTicket();
        new Thread(buyTicket,"1").start();
        new Thread(buyTicket,"2").start();
        new Thread(buyTicket,"3").start();
    }
}

class BuyTicket implements  Runnable{

    private int tickNums=10;
    boolean flag=true;//标志位,用于线程的外部停止方式
    @Override
    public void run() {
        //买票
        while(flag)
        {
            buy();
        }
    }
    private void buy() {
        //判断是否有票
        if (tickNums <= 0)
        {
            flag=false;
            return;
        }
        //模拟延时
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }


        //买票
        System.out.println(Thread.currentThread().getName()+"拿到"+tickNums--);
    }
}

线程安全

//synchronized同步方法,锁的是this
    private synchronized void buy() throws InterruptedException {
        //判断是否有票
        if (tickNums <= 0)
        {
            flag=false;
            return;
        }
        //模拟延时
           // Thread.sleep(10);
        //买票
        System.out.println(Thread.currentThread().getName()+"拿到"+tickNums--);
    }

② 银行取钱

package MultiThread.syn;
//不安全取钱
public class UnsafeBank {
    public static void main(String[] args) {
        //取钱首先得有账户
        Account account=new Account(100,"结婚基金");
        Drawing you=new Drawing(account,50,"你");
        Drawing girl=new Drawing(account,100,"girlFriend");
        you.start();
        girl.start();


    }

}

class Account{//账户
    int money;//余额
    String name;//卡名

    public Account(int money, String name) {
        this.money = money;
        this.name = name;
    }
}
//银行:模拟取款
class Drawing extends Thread{
    Account account;//账户
    int drawingMoney;//取多少钱
    int nMoney;//现在有多少钱

    public Drawing(Account account, int drawingMoney, String name) {
        super(name);//调用父类的方法
        this.account = account;
        this.drawingMoney = drawingMoney;
       // this.nMoney = nMoney;

    }

    @Override
    public void run() {
        //super.run();
        //判断有没有钱
        if(account.money-drawingMoney<0)
        {
            System.out.println(Thread.currentThread().getName()+"钱不够,取不了");
            return;
        }
        //模拟延时
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //卡内余额
        account.money=account.money-drawingMoney;
        //现在手里的钱
        nMoney=nMoney+drawingMoney;
        System.out.println(account.name+"余额为:"+account.money);
       //this就是调用当前方法的对象,Drawing继承了Thread,所以this也是一个线程对象,可以调用Thread的getName方法来获取线程的名字
       //Thread.currentThread().getName()=this.getName()
        System.out.println(this.getName()+"手里的钱"+nMoney);
    }
}

线程安全

@Override
    public  void run() {
        //super.run();
        //判断有没有钱
        synchronized(account)
        {
            if(account.money-drawingMoney<0)
            {
                System.out.println(Thread.currentThread().getName()+"钱不够,取不了");
                return;
            }
            //模拟延时
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //卡内余额
            account.money=account.money-drawingMoney;
            //现在手里的钱
            nMoney=nMoney+drawingMoney;
            System.out.println(account.name+"余额为:"+account.money);
            //this就是调用当前方法的对象,Drawing继承了Thread,所以this也是一个线程对象,可以调用Thread的getName方法来获取线程的名字
            //Thread.currentThread().getName()=this.getName()
            System.out.println(this.getName()+"手里的钱"+nMoney);
        }

    }

锁的对象就是多个线程共享的对象,如果每个线程的锁对象都不一样synchronized就没有用,锁谁取决于共同操作的对象是谁(且这个对象是要改变的)
③线程不安全的集合

import java.util.ArrayList;
import java.util.List;

//线程不安全的集合
public class UnsafeList {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                list.add(Thread.currentThread().getName());
            }).start();
        }
          Thread.sleep(100);//休息一会再打印,电脑运行速度太快了。线程里add还没执行完就执行主线程的打印语句。会导致打印结果偏小,但最后实际结果其实还是10000

        System.out.println(list.size());
    }
}

同步方法

由于我们可以通过private关键字来保证数据对象只能被方法访问﹐所以我们只需要针对方法提出一套机制。这套机制就是synchronized关键字,它包括两种用法:synchronized方法和synchronized块.

同步方法:public synchronized void method(int args)f

synchronized方法控制对“对象”的访问,每个对象对应一把锁﹐每个synchronized方法都必须获得调用该方法的对象的锁才能执行﹐否则线程会阻塞,方法一旦执行﹐就独占该锁,直到该方法返回才释放锁﹐后面被阻塞的线程才能获得这个锁,继续执行

缺陷:若将一个大的方法申明为synchronized将会影响效率

只读代码是不需要加锁的,只有需要进行修改的代码才需要加锁(所以产生了同步方法和同步代码块)
方法里面需要修改的内容才需要锁,锁得太多,浪费资源

同步块

synchronized(Obj){}
Obj称之为同步监视器
Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
注意监视的对象是需要增删改查的对象
同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class
同步监视器的执行过程:

第一个线程访问,锁定同步监视器,执行其中代码
第二个线程访问,发现同步监视器被锁定,无法访问
第一个线程访问完毕,皆出同步监视器
第二个线程访问,发现同步监视器没有锁

线程安全的集合:CopyOnWriteArrayList

我没有用lambda表达式创建线程,而是实现Runnable接口,结果证明这是可以的,且线程名字也存进了list

package MultiThread.syn;

import java.util.concurrent.CopyOnWriteArrayList;

public class testJuuc implements Runnable {
    static CopyOnWriteArrayList<String>list=new CopyOnWriteArrayList<String>();
    public static void main(String[] args) throws InterruptedException {

        testJuuc thread=new testJuuc();
        for (int i = 0; i < 10; i++) {
            new Thread( thread).start();
        }

        Thread.sleep(3000);
        System.out.println(list.size());
        System.out.println(list.toString());
    }

    @Override
    public void run() {
        list.add(Thread.currentThread().getName());
    }
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值