多线程笔记

多线程

创建线程

创建线程三种方式

  1. 继承Thread类,重写run方法
  2. 实现Runnable接口
  3. 实现Callable接口

继承Thread类

继承Thread类,重写run方法

package Thread;

public class MyThread extends Thread{
    @Override
    public void run() {
        for(int i = 0;i<100;i++){
            System.out.println("子线程..........."+i);
        }
    }
}
package Thread;

public class TestThread {
    public static void main(String[] args) {
        //创建线程对象
        MyThread myThread = new MyThread();
        //2启动线程,使用run方法相当于对象调用run方法,单线程
        myThread.start();
        //主线程执行
        for (int i = 0;i < 50;i++){
            System.out.println("主线程============"+i);
        }
    }
}

获取线程名称

获取线程ID和线程名称

1.在Thread的子类中调用this.getId()或this.getName()

2.使用Thread.currentThread().getId()和Thread.currentThread ().getName ()

3.修改线程名称

调用线程对象的setName()方法。

使用线程子类的构造方法赋值。

    public void run() {
        for(int i = 0;i<100;i++){
            //this.getId获取线程Id
            //this.getName获取线程名称
            //第一种方法,需要继承Thread类
            System.out.println("线程id:"+this.getId()+"线程名称"+getName()+"子线程..........."+i);
        }
            //第二种方式Thread.currentThread()  获取当前线程
            
            System.out.println("线程id:"+Thread.currentThread()+"线程名称:"+Thread.currentThread().getName());        

获取和修改线程名称

修改线程名称

调用线程对象的setName()方法。

使用线程子类的构造方法赋值。

//修改线程名称
MyThread myThread = new MyThread();
myThread.setName("我的子线程1");
myThread.start();

MyThread myThread2 = new MyThread();
myThread2.setName("我的子线程2");
myThread2.start();

第二种方法:用super把name传给Thread类

//添加构造方法
public MyThread(){

}
public MyThread(String name){
    super(name);//用super把name传给Thread类
}

MyThread myThread = new MyThread("我的子线程1");
MyThread myThread2 = new MyThread("我的子线程2");

示例

使用继承Thread类实现4个窗口各卖100张票

package Thread;

public class TicketWin extends Thread{
    //添加构造方法,添加后可以简去getName
    public TicketWin(){}
    public TicketWin(String name){
        super(name);
    }

    private int ticket = 100;//票数
    @Override
    public void run() {
        //卖票功能
        while(true){
            if(ticket<=0){
                break;
            }
            System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
            ticket--;
        }
    }
}
package Thread;

public class TestWin {
    public static void main(String[] args) {

        //创建四个窗口
        TicketWin w1 = new TicketWin("窗口1");
        TicketWin w2 = new TicketWin("窗口2");
        TicketWin w3 = new TicketWin("窗口3");
        TicketWin w4 = new TicketWin("窗口4");
        //启动线程
        w1.start();
        w2.start();
        w3.start();
        w4.start();
    }
}

创建线程(2)

第二种方式:实现Runnable接口

package Thread;

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0;i < 100;i++){
            System.out.println(Thread.currentThread().getName()+"...."+i);
        }
    }
}
package Thread;

public class TestRunnable {
    public static void main(String[] args) {
        //1创建MyRunnable对象,表示要执行的功能
        MyRunnable runnable = new MyRunnable();
        //2创建线程对象
        Thread thread = new Thread(runnable,"我的线程1");
        //3启动
        thread.start();
        for (int i = 0;i<50;i++){
            System.out.println("main....."+i);
        }
    }
}

匿名内部类

        Runnable runnable = new Runnable() {
            @Override
            public void run() {
        for (int i = 0;i<50;i++){
            System.out.println("main....."+i);
               }
            }
        };
        //创建线程对象
        Thread thread = new Thread(runnable,"我的线程1");
        thread.start();
    }
}

Runnable案例1

1.实现4个窗口共卖100张票

package Thread2;

public class Ticket implements Runnable{
    private int ticket = 100;//100张票

    @Override
    public void run() {
        while(true){
            if (ticket<=0){
                break;
            }
            System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
            ticket--;
        }
    }
}
package Thread2;

public class TestTicket {
    public static void main(String[] args) {
        //1创建票对象
        Ticket ticket = new Ticket();
        //2创建线程对象
        Thread w1 = new Thread(ticket,"窗口1");
        Thread w2 = new Thread(ticket,"窗口2");
        Thread w3 = new Thread(ticket,"窗口3");
        Thread w4 = new Thread(ticket,"窗口4");
        //3启动线程
        w1.start();
        w2.start();
        w3.start();
        w4.start();
    }
}

问题:四个窗口会抢夺100张票

Runnable案例2

2.你和你女朋友共用一张银行卡,你向卡中存钱,你女朋友从卡中取钱,使用程序

模拟过程?

创建两个类,分别为存钱和取钱

package Thread2;

public class AddMoney implements Runnable{
    private BankCard card;//BankCard类的card

    public AddMoney(BankCard card){
        this.card = card;
    }

    @Override
    public void run() {
        for (int i = 0 ; i<10;i++){
            card.setMoney(card.getMoney()+1000);
            System.out.println(Thread.currentThread().getName()+"存了1000,余额是:"+card.getMoney());
        }
    }
}
package Thread2;

public class SubMoney implements Runnable{
    private BankCard card;
    public SubMoney(BankCard card){
        this.card = card;
    }

    @Override
    public void run() {
        for (int i = 0;i<10;i++){
            if(card.getMoney()>=1000){
                card.setMoney(card.getMoney()-1000);
                System.out.println(Thread.currentThread().getName()+"取了1000,余额是:"+card.getMoney());
            }else{
                System.out.println("余额不足,请赶快存钱");
                i++;
            }
        }
    }
}

启动

package Thread2;

public class TestBankCard {
    public static void main(String[] args) {
        //1创建一张银行卡
        BankCard card = new BankCard();
        //2创建存钱,取钱
        AddMoney add = new AddMoney(card);
        SubMoney sub = new SubMoney(card);
        //3创建两个线程
        Thread zhangge = new Thread(add,"张哥");
        Thread jingjing = new Thread(sub,"静静");
        //4启动线程
        zhangge.start();
        jingjing.start();
        //有线程安全问题
    }
}

简介写反,用匿名内部类

package Thread2;

public class TestBankCard2 {
    public static void main(String[] args) {
        BankCard card = new BankCard();
        //存钱
        Runnable add = new Runnable() {
            @Override
            public void run() {
                for (int i = 0 ; i<10;i++){
                    card.setMoney(card.getMoney()+1000);
                    System.out.println(Thread.currentThread().getName()+"存了1000,余额是:"+card.getMoney());
                }
            }
        };
        //取钱
        Runnable sub = new Runnable() {
            @Override
            public void run() {
                for (int i = 0;i<10;i++){
                    if(card.getMoney()>=1000){
                        card.setMoney(card.getMoney()-1000);
                        System.out.println(Thread.currentThread().getName()+"取了1000,余额是:"+card.getMoney());
                    }else{
                        System.out.println("余额不足,请赶快存钱");
                        i++;
                    }
                }
            }
        };
        //创建线程对象,并启动
        new Thread(add,"明明").start();
        new Thread(sub,"小红").start();
    }
}

线程状态(基本)

在这里插入图片描述

线程休眠

休眠:

public static void sleep(long millis)

当前线程主动休眠 millis 毫秒

package Thread2;

public class TestSleep extends Thread{
    public static void main(String[] args) {

        SleepThread s1 = new SleepThread();
        s1.start();
        SleepThread s2 = new SleepThread();
        s2.start();

    }
    @Override
    public void run() {
        for (int i = 0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+"..."+i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }//子类不能抛出比父类更广的异常,要用try/catch
        }
    }
}

放弃:

public static void yield()

当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片。

package Thread2;

public class YieldThread extends Thread{

    @Override
    public void run() {
        for (int i = 0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+"..."+i);
            //主动放弃cpu
            Thread.yield();
        }
    }
}

线程加入:

public final void join()

允许其他线程加入到当前线程中

j1.join();//加入当线程(main),并阻塞当前线程,直到加入线程执行完毕
package Thread2;

public class TestJoin extends Thread{
    public static void main(String[] args) {

        
        JoinThread j1 = new JoinThread();
        j1.start();

        try {
            j1.join();//加入当线程(main),并阻塞当前线程,直到加入线程执行完毕
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //for  主线程
        for(int i = 0;i<20;i++){
            System.out.println(Thread.currentThread().getName()+"======"+i);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void run() {
        for(int i = 0;i<30;i++){
            System.out.println(Thread.currentThread().getName()+"..."+i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

设置线程优先级,守护线程

优先级:

线程对象.setPriority(),
线程优先级为1-10,默认为5,优先级越高,表示获取CPU机会越多。

守护线程:

线程对象.setDaemon(true);设置为守护线程
线程有两类:用户线程(前台线程)、守护线程(后台线程)。
如果程序中所有前台线程都执行完毕了,后台线程会自动结束。
垃圾回收器线程属于守护线程。

package Thread2;

public class PriorityThread extends Thread{
    @Override
    public void run() {
        for (int i = 0;i<50;i++){
            System.out.println(Thread.currentThread().getName()+"===="+i);
        }
    }

    public static void main(String[] args) {
        PriorityThread p1 = new PriorityThread();
        p1.setName("p1");
        PriorityThread p2 = new PriorityThread();
        p2.setName("p2");
        PriorityThread p3 = new PriorityThread();
        p3.setName("p3");

        p1.setPriority(1);
        p3.setPriority(10);

        //启动
        p1.start();
        p2.start();
        p3.start();
    }
}

守护线程

package Thread2;

public class TestDeamon {
    public static void main(String[] args) {

        //创建线程(默认前台线程)
        DeamonThread d1 = new DeamonThread();

        //设置为守护线程前台线程(d1)执行完之后,后台线程(main)会结束
        d1.setDaemon(true);
        d1.start();

        for (int i = 0;i<10;i++){
            System.out.println("主线程-------------");
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

线程的状态(等待)

在这里插入图片描述

线程安全问题

在这里插入图片描述

package Thread2;

import java.util.Arrays;

public class ThreadSafe {
    private static int index = 0;
    public static void main(String[] args) throws Exception {
        //创建数组
        String[] s = new String[5];
        //创建两个操作
        Runnable runnableA = new Runnable() {

            @Override
            public void run() {
                s[index] = "hello";
                index++;
            }
        };
        Runnable runnableB = new Runnable() {
            @Override
            public void run() {
                s[index] = "world";
                index++;
            }
        };
        //创建两个线程对象
        Thread a = new Thread(runnableA,"A");
        Thread b = new Thread(runnableB,"B");
        a.start();
        b.start();
        a.join();
        b.join();
        System.out.println(Arrays.toString(s));
    }
}

有可能一条线程并没有执行完毕,另一条线程就执行覆盖了上一条线程的结果

线程安全

同步方式

synchronized(临界资源对象){//对临界资源对象枷锁

//代码(原子操作),先运行

}

每个对象都有一个互斥锁标记,用来分配给线程的。
只有拥有对象互斥锁标记的线程,才能进入对该对象加锁的同步代码块。线程退出同步代码块时,会释放相应的互斥锁标记。

我的理解是,加锁之后要等代码(原子操作)执行完成后再有下一步操作

加锁之后别的线程只能等正在运行的代码完成后才能运行

Runnable runnableA = new Runnable() {

    @Override
    public void run() {
        //同步代码块
        synchronized (s){
            s[index] = "hello";
            index++;
        }
    }
};
Runnable runnableB = new Runnable() {
    @Override
    public void run() {
        synchronized (s){
            s[index] = "world";
            index++;
        }
    }
};

同步代码块使用(1)

@Override
public void run() {
    while(true){
        if (ticket<=0){
            break;
        }
        System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
        ticket--;
    }
}

分析:有4个窗口,100张票。假如当第1个窗口拿到CPU后会执行,卖出第100张票,但可能还没ticket - -,时间片段就到了,这时候ticket还是100张,这时候第2个窗口就拿到了CPU,卖出的也是第100张票 ,以此类推,会出现重复。

public class Ticket implements Runnable{
    private int ticket = 100;//100张票
    //创建锁
    private Object obj = new Object();

    @Override
    public void run() {
        while(true){
            synchronized (obj){
                if (ticket<=0){
                    break;
                }
            }
            System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
            ticket--;
        }
    }
}

不能在锁里的临界资源对象处创建对象,synchronized(new Object()),这样等于每次每个线程进来都创建一个锁。相当于一个房子,一个人(线程)只能有一个锁,用完出去之后才能换另外一个人(线程),如果都创建一个锁,别的人(线程)还是会进入。

可以

private int obj = new Object();

synchronized(new Object())

也可以

synchronized(this);

这个this就是ticket,也是唯一的

同步代码块使用(2)

package Thread2;

public class TestBankCard {
    public static void main(String[] args) {
        BankCard card = new BankCard();
        Object obj =new Object();
        //存钱
        Runnable add = new Runnable() {
            @Override
            public void run() {
                    for (int i = 0 ; i<10;i++){
                        synchronized (obj){
                        card.setMoney(card.getMoney()+1000);
                        System.out.println(Thread.currentThread().getName()+"存了1000,余额是:"+card.getMoney());
                    }
                }
            }
        };
        //取钱
        Runnable sub = new Runnable() {
            @Override
            public void run() {
                for (int i = 0;i<10;i++){
                    synchronized (obj){
                        if(card.getMoney()>=1000){
                            card.setMoney(card.getMoney()-1000);
                            System.out.println(Thread.currentThread().getName()+"取了1000,余额是:"+card.getMoney());
                        }else{
                            System.out.println("余额不足,请赶快存钱");
                            i--;
                        }
                    }
                }
            }
        };
        new Thread(add,"明明").start();
        new Thread(sub,"小红").start();
    }
}

同步方法

同步方法:
synchronized 返回值类型方法名称(形参列表0){ //对当前对象(this)加锁

//代码(原子操作)

}

只有拥有对象互斥锁标记的线程,才能进入该对象加锁的同步方法中。

线程退出同步方法时,会释放相应的互斥锁标记。

public class Ticket implements Runnable {
    private int ticket = 100;//100张票
    //创建锁
    //private Object obj = new Object();

    @Override
    public void run() {
        while (true) {
            if(!sale()){
                break;
            }
        }
    }
    //卖票
    public synchronized boolean sale() {//锁 this  静态方法 锁 Ticket.class
        if (ticket <= 0) {
            return false;
        }
        System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");
        ticket--;
        return true;
    }
}

同步规则

注意:
只有在调用包含同步代码块的方法,或者同步方法时,才需要对象的锁标记。

如调用不包含同步代码块的方法,或普通方法时,则不需要锁标记,可直接调用。

已知JDK中线程安全的类:
StringBuffer

Vector

Hashtable

以上类中的公开方法,均为synchonized修饰的同步方法。

死锁

死锁:

当第一个线程拥有A对象锁标记,并等待B对象锁标记,同时第二个线程拥有B对象锁标记,并等待A对象锁标记时,产生死锁。

一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已经拥有的锁标记,由此可能造成死锁。

public class Boy extends Thread{
    @Override
    public void run() {
        synchronized (MyLock.a) {
            System.out.println("男孩拿到了a");
            synchronized (MyLock.b) {
                System.out.println("男孩拿到了b");
                System.out.println("男孩可以吃东西了");
            }
        }
    }
}
public class Girl extends Thread{
    @Override
    public void run() {
        synchronized (MyLock.b) {
            System.out.println("女孩拿到了b");
            synchronized (MyLock.a) {
                System.out.println("女孩拿到了a");
                System.out.println("女孩可以吃东西了");
            }
        }
    }
}
public class MyLock extends Thread{
    public static Object a = new Object();
    public static Object b = new Object();

    public static void main(String[] args) {
        Boy boy = new Boy();
        Girl girl = new Girl();
        boy.start();
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        girl.start();
    }
}

线程通信

等待:
public final void wait()

public final void wait(long timeout)

必须在对obj加锁的同步代码块中。在一个线程中,调用obj.wait() 时,此线程
放其拥有的所有锁标记。同时此线程阻塞在o的等待队列中。释放锁,进入等待队列。

通知:

public final void notify()

public final void notifyAl1()

存钱取钱

Add

package Thread3;

public class Add implements Runnable{
    private BankCard card;
    //构造
    public Add(BankCard card){
        this.card = card;
    }

    @Override
    public void run() {
        for (int i = 0;i<10;i++){
            card.save(1000);

        }
    }
}

Sub

package Thread3;

public class Sub implements Runnable{
    private BankCard card;
    public Sub (BankCard card){
        this.card = card;
    }
    @Override
    public void run() {
        for (int i = 0;i<10;i++){
            card.take(1000);

        }
    }
}

BankCard

package Thread3;

public class BankCard {
    //余额
    private double money;
    //标记
    private boolean flag = false;//ture 表示有钱可以取钱,false没钱 可以存取

    //存钱
    public synchronized void save(double m){//锁 this
        if (flag) {//有钱
            try {
                this.wait();//进入等待队列,同事释放锁和cpu
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        money = money +m;
        System.out.println(Thread.currentThread().getName()+"存了"+m+"余额是"+money);
        //修改标记
        flag = true;
        //唤醒取钱线程
        this.notify();
    }

    //取钱
    public synchronized void take(double m ){
        if (!flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        money = money - m;
        System.out.println(Thread.currentThread().getName()+"取了"+m+"余额是"+money);
        flag = false;
        //唤醒存钱
        this.notify();
    }
}

Test

package Thread3;

public class TestBankCard {
    public static void main(String[] args) {
        //1创建银行卡
        BankCard card = new BankCard();
        //2创建操作
        Add add = new Add(card);
        Sub sub = new Sub(card);

        //3创建线程对象
        Thread chenchen = new Thread(add,"晨晨");
        Thread bingbing = new Thread(sub,"冰冰");
        //4启动
        chenchen.start();
        bingbing.start();
    }
}

多存夺取问题分析

会紊乱

public class BankCard {
    //余额
    private double money;
    //标记
    private boolean flag = false;//ture 表示有钱可以取钱,false没钱 可以存取

    //存钱
    public synchronized void save(double m){//锁 this
        while (flag) {//有钱,默认先是true?
            try {
                this.wait();//进入等待队列,同事释放锁和cpu
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        money = money +m;
        System.out.println(Thread.currentThread().getName()+"存了"+m+"余额是"+money);
        //修改标记
        flag = true;
        //唤醒取钱线程
        this.notify();
    }

    //取钱
    public synchronized void take(double m ){
        while (!flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        money = money - m;
        System.out.println(Thread.currentThread().getName()+"取了"+m+"余额是"+money);
        flag = false;
        //唤醒存钱
        this.notify();
    }
}

把if换成while,可以紊乱

但也可能四个线程都进入等待

在这里插入图片描述

把notify();改成notifyAll();

全部唤醒后,whlie,符合的进入循环

问题解决

生产者消费者

生产者、消费者:
若干个生产者在生产产品,这些产品将提供给若干个消费者去消费,为了使生产者和消费者能并发执行,在两者之间设置一个能存储多个产品的缓冲区,生产者将生产的产品放入缓冲区中,消费者从缓冲区中取走产品进行消费,显然生产者和消费者之间必须保持同步,即不允许消费者到一个空的缓冲区中取产品,也不允许生产者向一个满的缓冲区中放入产品。

面包类

package Thread3;

public class Bread {
    private int id ;
    private String productName;
    public Bread(){

    }
    public Bread(int id, String productName) {
        this.id = id;
        this.productName = productName;
    }



    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getProductName() {
        return productName;
    }

    public void setProductName(String productName) {
        this.productName = productName;
    }


    @Override
    public String toString() {
        return "Bread{" +
                "id=" + id +
                ", productName='" + productName + '\'' +
                '}';
    }
}

生产者类

package Thread3;

public class Product implements Runnable{
    private BreadCon con;

    public Product(BreadCon con){
        super();
        this.con = con;
    }

    @Override
    public void run() {
        for (int i = 0;i<30;i++){
            con.input(new Bread(i,Thread.currentThread().getName()));
        }
    }
}

消费者类

package Thread3;

public class Consume implements Runnable{
    private BreadCon con;

    public Consume(BreadCon con){
        super();
        this.con = con;
    }

    @Override
    public void run() {
        for (int i = 0;i < 30;i++){
            con.output();
        }
    }
}

面包容器类

package Thread3;

public class BreadCon {
    //存放面包的数组
    private Bread[] cons = new Bread[6];
    //存放面包的位置
    private int index = 0;

    public synchronized void input(Bread b){//锁 this
        //判断容器有没有满
        while(index>=6){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        cons[index]=b;
        System.out.println(Thread.currentThread().getName()+"生产了"+b.getId()+"");
        index++;
        this.notifyAll();
    }
    //取出面包
    public synchronized void output(){//锁 this
        while(index<=0){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        index--;
        Bread b = cons[index];
        System.out.println(Thread.currentThread().getName()+"消费了"+b.getId()+"生产者"+b.getProductName());
        cons[index] = null;
        //唤醒生产者
        this.notifyAll();
    }
}

测试类

package Thread3;

public class Test {
    public static void main(String[] args) {

    //容器
    BreadCon con = new BreadCon();
    //生产和消费
    Product product = new Product(con);
    Consume consume = new Consume(con);
    //创建线程对象
    Thread chenchen = new Thread(product,"晨晨");
    Thread bingbing = new Thread(consume,"消费");
    //启动线程
    chenchen.start();
    bingbing.start();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值