Loading

14、多线程

1、什么是进程

2、什么是线程

线程,又称轻量级进程(Light Weight Process )。进程中的一条执行路径,也是CPU的基本调度单位。

一个进程由一个或多个线程组成,彼此间完成不同的工作,同时执行,称为多线程。

3、进程和线程的区别

1、进程是操作系统资源分配的基本单位,而线程是CPU的基本调度单位

2、一个程序运行后至少有一个进程。

3、一个进程可以包含多个线程,但是至少需要有一个线程,否则这个进程是没有意义。

4、进程间不能共享数据段地址,但同进程的线程之间可以

4、线程的组成

任何一个线程都具有基本的组成部分:

  CPU时间片:操作系统(OS)会为每个线程分配执行时间。

运行数据:

  堆空间:存储线程需使用的对象,多个线程可以共享堆中的对象。

  栈空间:存储线程需使用的局部变量,每个线程都拥有独立的栈。

线程的逻辑代码。

5、线程的特点

1、线程抢占式执行

  效率高

  可防止单一线程长时间独占CPU

2、在单核CPU中,宏观上同时执行,微观上顺序执行。

6、创建线程

创建线程三种方式

1、【继承Thread类,重写run方法】

2、【实现Runnable接口】

3、实现Callable接口

6.1、创建线程(1)

第一种方式: 继承Thread类

获取线程ID和线程名称

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

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

修改线程名称

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

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

/**
 * 线程类
 *
 */
public class MyThread extends Thread {
    
    public MyThread() {
    }

    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        for(int i=0;i<100;i++) {
            //this.getId获取线程Id 
            //this.getName获取线程名称
            //第一种方式 this.getId和this.getName();
            //System.out.println("线程id:"+this.getId()+" 线程名称:"+this.getName()+" 子线程............."+i);

            //第二种方式 Thread.currentThread() 获取当前线程
            System.out.println("线程id:"+Thread.currentThread().getId()+" 线程名称:"+Thread.currentThread().getName()+" 子线程。。。。。。。"+i);
        }
    }
}
public class TestThread {
    public static void main(String[] args) {
        //1创建线程对象
        MyThread myThread=new MyThread("我的子线程1");
        //2启动线程,不能使用run方法
        //修改线程名称
        //myThread.setName("我的子线程1");
        myThread.start();//myThread.run()
        
        //创建第二个线程对象
        MyThread myThread2=new MyThread("我的子线程2");
        //myThread2.setName("我的子线程2");
        myThread2.start();

        //主线程执行
        for(int i=0;i<50;i++) {
            System.out.println("主线程======================"+i);
        }
    }
}

6.2、买票案例

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

/**
 * 卖票窗口类(线程类)
 *
 */
public class TicketWin extends Thread{

    private int ticket=100;//票
    
    public TicketWin() {
    }
    
    public TicketWin(String name) {
        super(name);
    }
    
    @Override
    public void run() {
        //卖票功能
        while(true) {
            if(ticket<=0) {
                break;
            }
            System.out.println(Thread.currentThread().getName()+"卖了第"+ticket+"张票");
            ticket--;
        }
    }
}
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();
    }
}

6.3、创建线程(2)

第二种方式:实现Runnable接口

public class MyRunnable implements Runnable{

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

}
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<100;i++) {
                    System.out.println(Thread.currentThread().getName()+" ........."+i);
                }
            }
        };
        //创建线程对象
        Thread thread=new Thread(runnable, "我的线程1");
        //启动线程
        thread.start();
        
        for(int i=0;i<50;i++) {
            System.out.println("main............"+i);
        }
    }
}

6.4、4个窗口共卖100张票

/**
 * 票类(共享资源)
 *
 */
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--;
        }
        
    }
}
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();
    }
}

(2)存钱和取钱

方式一

/**
 * 银行卡
 *
 */
public class BankCard{
    private double money;//余额

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }

}
/**
 * 存钱
 *
 */
public class AddMoney implements Runnable {

    private 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());
        }
    }
}
/**
 * 取钱
 *
 */
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--;
            }
        }
    }

}
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 chenchen=new Thread(add, "晨晨");
        Thread bingbing=new Thread(sub, "冰冰");
        //4启动线程
        chenchen.start();
        bingbing.start();
    }
}

方式二

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();
    }
}

7、线程的状态

(1)基本

(2)等待

(3)阻塞

 

8、常见方法

8.1、休眠

  public static void sleep(long millis)。

  当前线程主动休眠millis毫秒。

/**
 * 线程类
 *
 */
public class SleepThread extends Thread{
    @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();
            }
        }
    }
}
public class TestYield {
    public static void main(String[] args) {
        YieldThread y1=new YieldThread();
        YieldThread y2=new YieldThread();
        
        y1.start();
        y2.start();
    }
}

8.2、放弃

  public static void yield()

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

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 class TestYield {
    public static void main(String[] args) {
        YieldThread y1=new YieldThread();
        YieldThread y2=new YieldThread();
        
        y1.start();
        y2.start();
    }
}

8.3、加入

  public final void join()

/**
 * join方法的使用
 */
public class JoinThread extends Thread {
    @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();
            }
        }
    }
}
public class TestJoin {
    public static void main(String[] args) {
        JoinThread j1=new JoinThread();
        j1.start();
        
        try {
            j1.join();//加入当前线程(main),并阻塞当前线程,直到加入线程执行完毕
        } catch (InterruptedException e1) {
            // TODO Auto-generated catch block
            e1.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();
            }
        }
    }
}

8.4、优先级

  线程对象.setPriority()

  线程优先级为1-10,默认为5,优先级越高,表示获取CPU机会越多

/**
 * 优先级
 *
 */
public class PriorityThread extends Thread{
    @Override
    public void run() {
        for(int i=0;i<50;i++) {
            System.out.println(Thread.currentThread().getName()+"============"+i);
        }
    }
}
public class TestPriority {
    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();
    }
}

8.5、守护线程

  线程对象.setDaemon(true);设置为守护线程

  线程有两类:用户线程(前台线程)、守护线程(后台线程)。

  如果程序中所有前台线程都执行完毕了,后台线程会自动结束。

  垃圾回收器线程属于守护线程

public class DeamonThread extends Thread {
    @Override
    public void run() {
        for(int i=0;i<50;i++) {
            System.out.println(Thread.currentThread().getName()+"----------"+i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class TestDeamon {
    public static void main(String[] args) {
        //创建线程(默认前台线程)
        DeamonThread d1=new DeamonThread();
        //设置线程为守护线程
        d1.setDaemon(true);
        d1.start();
        
        for(int i=0;i<10;i++) {
            System.out.println("主线程:----------"+i);
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}

9、线程安全问题

 

 

需求:

  A线程将“Hello”存入数组;B线程将“World”存入数组。

多线程安全问题:

  当多线程并发访问临界资源时,如果破坏原子操作,可能会造成数据不一致。

  临界资源:共享资源(同一对象),一次仅允许一个线程使用,才可保证其正确性。

  原子操作:不可分割的多步操作,被视作一个整体,其顺序和步骤不可打乱或缺省。

/**
 * 线程安全问题:
 * 两个线程同时向一个数组中存放数据
 *
 */
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() {
                //同步代码块
                synchronized (s) {
                    s[index]="hello";
                    index++;
                }
            }
        };

        Runnable runnableB=new Runnable() {
            @Override
            public void run() {
                synchronized (s) {
                    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));
    }
}

10、同步代码块

synchronized(临界资源对象){//对临界资源对象加锁
//代码(原子操作)
}

每个对象都有一个互斥锁标记,用来分配给线程的。

只有拥有对象互斥锁标记的线程,才能进入对该对象加锁的同步代码块。

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

(1)锁对象

/**
 * 票类
 *
 */
public class Ticket implements Runnable{
    
    private int ticket=100;
    //创建锁
    //private Object obj=new Object();
    
    @Override
    public void run() {
        
        while(true) {
            synchronized (this) {//this ---当前对象
                if(ticket<=0) {
                    break;
                }
                System.out.println(Thread.currentThread().getName()+"卖了第"+ticket+"票");
                ticket--;
            }
            
        }
    }
    
}
public class TestTicket {
    public static void main(String[] args) {
        //创建Ticket
        Ticket ticket=new Ticket();
        //创建线程对象
        Thread w1=new Thread(ticket, "窗口1");
        Thread w2=new Thread(ticket, "窗口2");
        Thread w3=new Thread(ticket, "窗口3");
        Thread w4=new Thread(ticket, "窗口4");
        
        //启动线程
        w1.start();
        w2.start();
        w3.start();
        w4.start();
        
    }
}

(2)锁对象

public class BankCard {
    private double money;

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }
    
}
public class TestBankCard {
    public static void main(String[] args) {
        //1创建银行卡
        BankCard card=new BankCard();

        //2创建两个操作
        Runnable add=new Runnable() {
            @Override
            public void run() {
                for(int i=0;i<10;i++) {
                    synchronized (card) {
                        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 (card) {
                        if(card.getMoney()>=1000) {
                            card.setMoney(card.getMoney()-1000);
                            System.out.println(Thread.currentThread().getName()+"取了1000,余额是:"+card.getMoney());
                        }else {
                            System.out.println("余额不足,请存取");
                            i--;
                        }
                    }
                    
                }
            }
        };
        
        //3创建两个线程对象
        Thread xiaoli=new Thread(add, "小李");
        Thread xiaoyue=new Thread(sub,"小月" );

        xiaoli.start();
        xiaoyue.start();
        
    }
}

11、同步方法

synchronized 返回值类型  方法名称(形参列表0){//对当前对象(this)加锁
//代码(原子操作)
}

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

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

(1)方法

/**
 * 票类
 *
 */
public class Ticket implements Runnable{
    
    private  int ticket=100;
    
    @Override
    public void run() {
        
        while(true) {
            if(!sale()) {
                break;
            }
        }
    }
    
    //卖票(同步方法)
    public  synchronized  boolean sale() {//锁 this  
            if(ticket<=0) {
                return false;
            }
            System.out.println(Thread.currentThread().getName()+"卖了第"+ticket+"票");
            ticket--;
            return true;
    }
    
}

(2)静态方法

/**
 * 票类
 *
 */
public class Ticket implements Runnable{
    
    private static int ticket=100;
    //创建锁
    //private Object obj=new Object();
    
    @Override
    public void run() {
        
        while(true) {
            if(!sale()) {
                break;
            }
        }
    }
    //卖票(同步方法)
    public   static boolean sale() {//锁 this  静态方法 锁 Ticket.class
        synchronized (Ticket.class) {
            if(ticket<=0) {
                return false;
            }
            System.out.println(Thread.currentThread().getName()+"卖了第"+ticket+"票");
            ticket--;
            return true;
        }
    }
}

12、同步规则

注意:

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

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

已知JDK中线程安全的类:

  StringBuffer

  vector

  Hashtable

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

13、死锁

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

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

/**
 * 创建两个锁对象
 *
 */
public class MyLock {
    //两个锁(两个筷子)
    public static Object a=new Object();
    public static Object b=new Object();
}
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 TestDeadLock {
    public static void main(String[] args) {
        Boy boy=new Boy();
        Girl girl=new Girl();
        girl.start();
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
        boy.start();
    }
}

14、线程通信

等待:

  public final void wait()

  public final void wait(long timeout)

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

通知:

  public final void notify(). public final void notifyAll()

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

    //存钱
    public synchronized void save(double m) { //this
        while(flag) {//有钱
            try {
                this.wait();//进入等待队列,同时释放锁 ,和cpu
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        
        money=money+m;
        System.out.println(Thread.currentThread().getName()+"存了"+m+" 余额是"+money);
        //修改标记
        flag=true;
        //唤醒取钱线程
        this.notify();
    }
    
    //取钱
    public synchronized void take(double m) {//this
        while(!flag) {
            try {
                this.wait();//进入等待队列,同时释放锁 ,和cpu
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        
        money=money-m;
        System.out.println(Thread.currentThread().getName()+"取了"+m+" 余额是"+money);
        //修改标记
        flag=false;
        //唤醒存钱线程
        this.notify();
    }
    
}
public class AddMoney implements Runnable{

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

}
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++) {
            card.take(1000);
        }
    }
}
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 chenchen=new Thread(add, "晨晨");
        Thread bingbing=new Thread(sub, "冰冰");
        
        Thread mingming=new Thread(add, "明明");
        Thread lili=new Thread(sub, "莉莉");
                
        //4启动
        chenchen.start();
        mingming.start();
        bingbing.start();
        lili.start();
    }
}

15、生产者、消费者

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

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) {
                // TODO Auto-generated catch block
                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) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        index--;
        Bread b=cons[index];
        System.out.println(Thread.currentThread().getName()+"消费了"+b.getId()+" 生产者:"+b.getProductName());
        cons[index]=null;
        //唤醒生产者
        this.notifyAll();
    }
}
/**
 * 面包
 *
 */
public class Bread {
    private int id;
    private String productName;
    public Bread() {
        // TODO Auto-generated constructor stub
    }
    public Bread(int id, String productName) {
        super();
        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 + "]";
    }
}
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();
        }
    }
}
public class Prodcut implements Runnable {

    private BreadCon con;
    
    public Prodcut(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()));
        }
    }
    
}
public class Test {
    public static void main(String[] args) {
        //容器
        BreadCon con=new BreadCon();
        //生产和消费
        Prodcut prodcut=new Prodcut(con);
        Consume consume=new Consume(con);
        //创建线程对象
        Thread chenchen=new Thread(prodcut, "晨晨");
        Thread bingbing=new Thread(consume, "消费");
        Thread mingming=new Thread(prodcut, "明明");
        Thread lili=new Thread(consume, "莉莉");
        //启动线程
        chenchen.start();
        bingbing.start();
        mingming.start();
        lili.start();
    }
}

16、总结

线程的创建:

  方式1:继承Thread类

  方式2:实现Runnable接口(一个任务Task),传入给Thread对象并执行。

线程安全:

  同步代码块:为方法中的局部代码(原子操作)加锁。

  同步方法:为方法中的所有代码(原子操作)加锁。

线程间的通信:

  wait() / wait(long timeout):等待

  notify() / notifyAll():通知

17、线程池

17.1、概念

问题:

  线程是宝贵的内存资源、单个线程约占1MB空间,过多分配易造成内存溢出。

  频繁的创建及销毁线程会增加虚拟机回收频率、资源开销,造成程序性能下降。

线程池:

  线程容器,可设定线程分配的数量上限。

  将预先创建的线程对象存入池中,并重用线程池中的线程对象。

  避免频繁的创建和销毁。

17.2、线程池原理

  将任务提交给线程池,由线程池分配线程、运行任务,并在当前任务结束后复用线程。

17.3、创建线程池

常用的线程池接口和类(所在包java.util.concurrent)

  Executor:线程池的顶级接口。

  ExecutorService:线程池接口,可通过submit (Runnable task)提交任务代码。

  Executors工厂类:通过此类可以获得一个线程池。

  通过newFixedThreadPool(int nThreads)获取固定数量的线程池。参数:指定线程池中线程的数量。

  通过newCachedThreadPool()获得动态数量的线程池,如不够则创建新的,没有上限

/**
 * 演示线程池的创建
 * Executor:线程池的根接口,execute()
 * ExecutorService:包含管理线程池的一些方法,submit shutdown
 *       ThreadPoolExecutor
 *         ScheduledThreadPoolExecutor
 * Executors:创建线程池的工具类
 *         (1)创建固定线程个数线程池
 *         (2)创建缓存线程池,由任务的多少决定
 *         (3)创建单线程池
 *         (4)创建调度线程池  调度:周期、定时执行
 *
 */
public class Demo1 {
    public static void main(String[] args) {
        //1.1创建固定线程个数的线程池
        //ExecutorService es=Executors.newFixedThreadPool(4);
        //1.2创建缓存线程池,线程个数由任务个数决定
        ExecutorService es=Executors.newCachedThreadPool();
        
        //1.3创建单线程线程池
        //Executors.newSingleThreadExecutor();
        //1.4创建调度线程池  调度:周期、定时执行
        //Executors.newScheduledThreadPool(corePoolSize)
        Executors.newScheduledThreadPool(3);
        //2创建任务
        Runnable runnable=new Runnable() {
            private int ticket=100;
            @Override
            public void run() {
                while(true) {
                    if(ticket<=0) {
                        break;
                    }
                    System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
                    ticket--;
                }
            }
        };
        //3提交任务
        for(int i=0;i<5;i++) {
            es.submit(runnable);
        }
        //4关闭线程池
        es.shutdown();//等待所有任务执行完毕 然后关闭线程池,不接受新任务。
    }
}

18、Callable接口(创建线程3)

public interface Callable<V> {
    public V call() throws Exception;
}

JDK5加入,与Runnable接口类似,实现之后代表一个线程任务。

Callable具有泛型返回值、可以声明异常。

/**
 * 演示Callable接口的使用
 * Callable和Runnable接口的区别
 * (1)Callable接口中call方法有返回值,Runnable接口中run方法没有返回值
 * (2)Callable接口中call方法有声明异常,Runnable接口中run方法没有异常
 *
 */
public class Demo2 {
    public static void main(String[] args) throws Exception{
        //功能需求:使用Callable实现1-100和
        //1创建Callable对象
        Callable<Integer> callable=new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                System.out.println(Thread.currentThread().getName()+"开始计算");
                int sum=0;
                for(int i=1;i<=100;i++) {
                    sum+=i;
                    Thread.sleep(100);
                }
                return sum;
            }
        };
        
        //2把Callable对象 转成可执行任务
        FutureTask<Integer> task=new FutureTask<>(callable);
        
        //3创建线程
        Thread thread=new Thread(task);
        
        //4启动线程
        thread.start();
        
        //5获取结果(等待call执行完毕,才会返回)
        Integer sum=task.get();
        System.out.println("结果是:"+sum);
    }
}
/**
 * 使用线程池计算1-100的和
 */
public class Demo3 {
    public static void main(String[] args) throws Exception{
        //1创建线程池
        ExecutorService es=Executors.newFixedThreadPool(1);

        //2提交任务 Future:表示将要执行完任务的结果
        Future<Integer> future=es.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                System.out.println(Thread.currentThread().getName()+"开始计算");
                int sum=0;
                for(int i=1;i<=100;i++) {
                    sum+=i;
                    Thread.sleep(10);
                }
                return sum;
            }
        });
        
        //3获取任务结果,等待任务执行完毕才会返回.
        System.out.println(future.get());
        
        //4关闭线程池
        es.shutdown();
    }
}

19、Future接口

  表示ExecutorService.submit()所返回的状态结果,就是call()的返回值

  方法:v get()以阻塞形式等待Future中的异步处理结果(call()的返回值)

  Future:表示将要完成任务的结果。

  需求:使用两个线程,并发计算1~50、51~100的和,再进行汇总统计。

/**
 * 使用两个线程,并发计算1~50、51~100的和,再进行汇总统计。
 *
 */
public class Demo4 {
    public static void main(String[] args) throws Exception{
        //1创建线程池
        ExecutorService es=Executors.newFixedThreadPool(2);
        //2提交任务
        Future<Integer> future1=es.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int sum=0;
                for(int i=1;i<=50;i++) {
                    sum+=i;
                }
                System.out.println("1-50计算完毕");
                return sum;
            }
        });

        Future<Integer> future2=es.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int sum=0;
                for(int i=51;i<=100;i++) {
                    sum+=i;
                }
                System.out.println("51-100计算完毕");
                return sum;
            }
        });

        //3获取结果
        int sum=future1.get()+future2.get();
        System.out.println("结果是:"+sum);

        //4关闭线程池
        es.shutdown();
    }
}

20、线程的同步

  形容一次方法调用,同步一旦开始,调用者必须等待该方法返回,才能继续。

21、线程的异步

  形容一次方法调用,异步一旦开始,像是一次消息传递,调用者告知之后立刻返回。二者竞争时间片,并发执行。

22、Lock接口

JDK5加入,与synchronized比较,显示定义,结构更灵活。

提供更多实用性方法,功能更强大、性能更优越。

常用方法:

  void lock()//获取锁,如锁被占用,则等待。

  boolean tryLock() / /尝试获取锁(成功返回true。失败返回fals不阻塞)

  void unlock()//释放锁

23、重入锁

  ReentrantLock: Lock接口的实现类,与synchronized一样具有互斥锁功能。

/**
 * ReentrantLock的使用
 */
public class MyList {
    //创建锁
    private Lock lock=new ReentrantLock();
    private String[] str= {"A","B","","",""};
    private int count=2;
    
    public void add(String value) {
        lock.lock();
        try {
            str[count]=value;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            count++;
            System.out.println(Thread.currentThread().getName()+"添加了"+value);
        } finally {
            lock.unlock();
        }
    }
    
    public String[] getStr() {
        return str;
    }
    
}
public class TestMyList {
    public static void main(String[] args) throws Exception {
        MyList list=new MyList();
        Runnable runnable=new Runnable() {
            @Override
            public void run() {
                list.add("hello");
            }
        };

        Runnable runnable2=new Runnable() {
            @Override
            public void run() {
                list.add("world");
            }
        };
        
        Thread t1=new Thread(runnable);
        Thread t2=new Thread(runnable2);
        
        t1.start();
        t2.start();
        
        t1.join();
        t2.join();
        System.out.println(Arrays.toString(list.getStr()));
    }
}
public class Ticket implements Runnable{
    private int ticket=100;
    private Lock lock=new ReentrantLock();
    @Override
    public void run() {
        while(true) {
            lock.lock();
            try {
                if(ticket<=0) {
                    break;
                }
                System.out.println(Thread.currentThread().getName()+"卖了第"+ticket+"张票");
                ticket--;
            } finally {
                lock.unlock();
            }
            
        }
    }
    
}

24、读写锁

ReentrantReadWriteLock:

  一种支持一写多读的同步锁,读写分离,可分别分配读锁、写锁。

  支持多次分配读锁,使多个读操作可以并发执行。

互斥规则:

  写-写:互斥,阻塞。

  读-写:互斥,读阻塞写、写阻塞读。

  读-读:不互斥、不阻塞。

 在读操作远远高于写操作的环境中,可在保障线程安全的情况下,提高运行效率。

 

 

 

public class ReadWriteDemo {
    //创建读写锁
    private ReentrantReadWriteLock rrl=new ReentrantReadWriteLock();
    //获取读锁
    private ReadLock readLock=rrl.readLock();
    //获取写锁
    private WriteLock writeLock=rrl.writeLock();
    
    //互斥锁
    private ReentrantLock lock=new ReentrantLock();
    
    private String value;
    
    //读取
    public String getValue() {
        //使用读锁上锁
        lock.lock();
        try {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("读取:"+this.value);
            return this.value;
        }finally {
            lock.unlock();
        }
    }

    //写入
    public void setValue(String value) {
        lock.lock();
        try {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("写入:"+value);
            this.value=value;
        }finally {
            lock.unlock();
        }
    }
    
}
public class TestReadWriteLock {
    public static void main(String[] args) {
        ReadWriteDemo readWriteDemo=new ReadWriteDemo();
        //创建线程池
        ExecutorService es=Executors.newFixedThreadPool(20);
        
        Runnable read=new Runnable() {
            @Override
            public void run() {
                readWriteDemo.getValue();
            }
        };

        Runnable write=new Runnable() {
            @Override
            public void run() {
                readWriteDemo.setValue("张三:"+new Random().nextInt(100));
            }
        };


        long start=System.currentTimeMillis();
        //分配2个写的任务
        for(int i=0;i<2;i++) {
            es.submit(write);
        }

        //分配18读取任务
        for(int i=0;i<18;i++) {
            es.submit(read);
        }
        
        es.shutdown();//关闭
        while(!es.isTerminated()) {//空转
            
        }
        long end=System.currentTimeMillis();
        System.out.println("用时:"+(end-start));
    }
}

25、集合

25.1、Collection体系集合

  Collection体系集合中,除Vector以外的线程安全集合。

 

 

25.2、Map安全集合体系

 

 

25.3、Collections中的工具方法

Collections工具类中提供了多个可以获得线程安全集合的方法

public static <T>Collection<T> synchronizedCollection(Collection<T> c)

public static<T> List<T> synchronizedList(List<T> list)

public static <T> Set<T> synchronizedSet(Set<T> s)

public static <K,V> Map<K,V> synchronizedMap (Map<K,V> m)

public static<T> SortedSet<T> synchronizedSortedSet(SortedSet<T> s)

public static<K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m)

JDK1.2提供,接口统一、维护性高,但性能没有提升,均以synchonized实现。

/**
 *    
 * 演示:使用多线程操作线程不安全集合
 */
public class Demo1 {
    public static void main(String[] args) {
        //1创建arraylist
        ArrayList<String> arrayList=new ArrayList<>();
        //1.1使用Collections中的线程安全方法转成线程安全的集合
        List<String> synList=Collections.synchronizedList(arrayList);
        //1.2使用CopyOnWriteArrayList
        //CopyOnWriteArrayList<String> arrayList=new CopyOnWriteArrayList<>(); 
        
        //2创建线程
        for(int i=0;i<20;i++) {
            int temp=i;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for(int j=0;j<10;j++) {
                        synList.add(Thread.currentThread().getName()+"===="+temp+"===="+j);
                        System.out.println(synList.toString());
                    }
                }
            }).start();
        }
    }
}

25.4、CopyOnWriteArrayList

线程安全的ArrayList,加强版的读写分离。

写有锁,读无锁,读写之间不阻塞,优于读写锁。

写入时,先copy一个容器副本、再添加新元素,最后替换引用。

使用方式与ArrayList无异。

/**
 * 使用多线程操作CopyOnWriteArrayList
 *
 */
public class Demo2 {
    public static void main(String[] args) {
        //1创建集合
        CopyOnWriteArrayList<String> list=new CopyOnWriteArrayList<>();
        //2使用多线程操作
        ExecutorService es=Executors.newFixedThreadPool(5);
        //3提交任务
        for(int i=0;i<5;i++) {
            es.submit(new Runnable() {
                
                @Override
                public void run() {
                    for(int j=0;j<10;j++) {
                        list.add(Thread.currentThread().getName()+"...."+new Random().nextInt(1000));
                    }
                }
            });
        }
        //4关闭线程池
        es.shutdown();
        while(!es.isTerminated()) {}
        //5打印结果
        System.out.println("元素个数:"+list.size());
        for (String string : list) {
            System.out.println(string);
        }
    }
}

25.5、CopyOnWriteArraySet

线程安全的Set,底层使用CopyOnWriteArrayList实现。

唯一不同在于,使用addIfAbsent()添加元素,会遍历数组

如存在元素,则不添加(扔掉副本)。

/**
 * 演示CopyOnWriteArraySet
 * 重复依据:equals方法
 *
 */
public class Demo3 {
    public static void main(String[] args) {
        //1创建集合
        CopyOnWriteArraySet<String> set=new CopyOnWriteArraySet<>();
        //2添加元素
        set.add("pingguo");
        set.add("huawei");
        set.add("xiaomi");
        set.add("lianxiang");
        set.add("pingguo");
        //3打印
        System.out.println("元素个数:"+set.size());
        System.out.println(set.toString());
    }
}

26、Queue接口(队列)

Collection的子接口,表示队列FIFO (First In First Out)先进先出常用方法:

抛出异常:

  boolean add(E e)//顺序添加一个元素(到达上限后,再添加则会抛出异常)。

  E remove()//获得第一个元素并移除(如果队列没有元素时,则抛异常)。

  E element()//获得第一个元素但不移除(如果队列没有元素时,则抛异常)

返回特殊值:推荐使用

  boolean offer(E e) //顺序添加一个元素(到达上限后,再添加则会返回false)。

  E poll() //获得第一个元素并移除(如果队列没有元素时,则返回null)

  E peek() //获得第一个元素但不移除(如果队列没有元素时,则返回null)

/**
 * Queue接口的使用
 *
 */
public class Demo4 {
    public static void main(String[] args) {
        //1创建队列
        Queue<String> queue=new LinkedList<>();
        //2入队
        queue.offer("苹果");
        queue.offer("橘子");
        queue.offer("葡萄");
        queue.offer("西瓜");
        queue.offer("榴莲");
        //3出队
        System.out.println(queue.peek());
        System.out.println("----------------");
        System.out.println("元素个数:"+queue.size());
        int size=queue.size();
        for(int i=0;i<size;i++) {
            System.out.println(queue.poll());
        }
        System.out.println("出队完毕:"+queue.size());
    }
}

27、ConcurrentLinkedQueue

线程安全、可高效读写的队列,高并发下性能最好的队列

无锁、CAS比较交换算法,修改的方法包含三个核心参数(V,E,N)

V:要更新的变量、E:预期值、N:新值。

只有当V==E时,V=N;否则表示已被更新过,则取消当前操作。

 

/**
 * ConcurrentLinkedQueue的使用
 */
public class Demo5 {
    public static void main(String[] args) throws Exception {
        //1创建安全队列
        ConcurrentLinkedQueue<Integer> queue=new ConcurrentLinkedQueue<>();
        //2入队操作
        Thread t1=new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=1;i<=5;i++) {
                    queue.offer(i);
                }
            }
        });
        Thread t2=new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=6;i<=10;i++) {
                    queue.offer(i);
                }
            }
        });

        //3启动线程
        t1.start();
        t2.start();
        
        t1.join();
        t2.join();
        System.out.println("-------------出队-------------");
        
        //4出队操作
        int size=queue.size();
        for(int i=0;i<size;i++) {
            System.out.println(queue.poll());
        }
        
    }
}

28、BlockingQueue接口(阻塞队列)

Queue的子接口,阻塞的队列,增加了两个线程状态为无限期等待的方法。

方法:

  void put(E e) //将指定元素插入此队列中,如果没有可用空间,则等待。

  E take() //获取并移除此队列头部元素,如果没有可用元素,则等待。

ArrayBlockingQueue:

  数组结构实现,有界队列。(手工固定上限)

LinkedBlockingQueue:

  链表结构实现,有界队列。(默认上限Integer.MAX VALUE)

/**
 * 阻塞队列的使用
 * 案例1:创建一个有界队列,添加数据
 * 案例2:使用阻塞队列实现生产者和消费者
 *
 */
public class Demo6 {
    public static void main(String[] args) throws Exception{
        //创建一个有界队列,添加数据
        ArrayBlockingQueue<String> queue=new ArrayBlockingQueue<>(5);
        //添加元素
        queue.put("aaa");
        queue.put("bbb");
        queue.put("ccc");
        queue.put("ddd");
        queue.put("eee");
        //删除元素
        queue.take();
        System.out.println("已经添加了5个元素");
        queue.put("xyz");
        System.out.println("已经添加了6个元素");
        System.out.println(queue.toString());
    }
}
/**
 * 案例2:使用阻塞队列实现生产者和消费者
 *
 */
public class Demo7 {
    public static void main(String[] args) {
        //1创建队列
        ArrayBlockingQueue<Integer> queue=new ArrayBlockingQueue<>(6);
        //2创建两个线程
        Thread t1=new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=0;i<30;i++) {
                    try {
                        queue.put(i);
                        System.out.println(Thread.currentThread().getName()+"生产了第"+i+"号面包");
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    
                }
            }
        }, "晨晨");
        
        Thread t2=new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=0;i<30;i++) {
                    try {
                        Integer num=queue.take();
                        System.out.println(Thread.currentThread().getName()+"消费了第"+i+"号面包");
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    
                }
            }
        }, "冰冰");
        
        //启动线程
        t1.start();
        t2.start();
    }
}

29、ConcurrentHashMap

初始容量默认为16段(Segment),使用分段锁设计。

不对整个Map加锁,而是为每个Segment加锁。

当多个对象存入同一个Segment时,才需要互斥。

最理想状态为16个对象分别存入16个Segment,并行数量16

使用方式与HashMap无异。

/**
 * ConcurrentHashMap的使用
 *
 */
public class Demo8 {
    public static void main(String[] args) {
        //1创建集合
        ConcurrentHashMap<String, String> hashMap=new ConcurrentHashMap<String, String>();
        //2使用多线程添加数据
        for(int i=0;i<5;i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for(int k=0;k<10;k++) {
                        hashMap.put(Thread.currentThread().getName()+"--"+k, k+"");
                        System.out.println(hashMap);
                    }
                }
            }).start();
        }
    }
}

30、总结

ExecutorService线程池接口、Executors工厂。

Callable线程任务、Future异步返回值。

Lock、ReentrantLock重入锁、ReentrantReadWriteLock读写锁。

CopyOnWriteArrayList线程安全的ArrayList。

CopyOnWriteArraySet线程安全的Set。

ConcurrentLinkedQueue线程安全的Queue。

ArrayBlockingQueue线程安全的阻塞Queue。(生产者、消费者)

ConcurrentHashMap线程安全的HashMap。

 

posted @ 2021-09-09 22:48  菜鸟的道路  阅读(37)  评论(0)    收藏  举报