【Java】高级篇2:多线程

发布于:2024-03-25 ⋅ 阅读:(66) ⋅ 点赞:(0)

一、相关概念

注意:

1、不同进程之间不共享内存

2、进程之间的数据交换和通信成本很高

线程调度:

单核CPU与多核CPU:

并行与并发:

二、创建和启动线程

1、概述

2、方式

2.1 方式一:继承Thread类

2.2 方式二:实现Runnable接口

class CountNumber implements Runnable{
    for(int i=0;i<10;i++){
        print(i);
    }
}

class Test{
    public static void main(String args[]){
        CountNumber c = new CountNumber();
        Thread t = new Thread(c);
        t.start();
    }
}

2.3 方式三:Callable(JDK5.0后新增)

2.4 线程池

代码示例:

三、Thread类常用结构

1、构造器

2、常用方法

四、多线程的生命周期

JDK1.5之前:

JDK1.5之后:

五、线程安全问题

引入

public class Test3 {
    public static void main(String[] args) {
        saleTikect s =new saleTikect();
        Thread t1=new Thread(s);
        Thread t2=new Thread(s);
        Thread t3=new Thread(s);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}
//实现Runnable接口

class saleTikect implements Runnable{
    int tikect=100;

    @Override
    public void run(){
        while (true) {
            if (tikect>0) {
              System.out.println(Thread.currentThread().getName()+"售票,票号:"+tikect); 
              tikect--; 
            }else{
                break;
            }
        }
    }
}

出现问题:

解决方式:线程的同步机制

方式1:同步代码块

格式:

synchornized(同步监视器){

    //需要被同步的代码

}

代码:

public class Test3RunnableSafe {
    public static void main(String[] args) {
        saleTikect s =new saleTikect();
        Thread t1=new Thread(s);
        Thread t2=new Thread(s);
        Thread t3=new Thread(s);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}
class saleTikect implements Runnable{
    static int tikect=100;
    Object obj=new Object();

    @Override
    public void run(){
        while (true) {
            try {
                Thread.sleep(10);
            } catch (Exception e) {
                // TODO: handle exception
                e.printStackTrace();
            }

            synchronized(obj){
                //obj唯一

                if (tikect>0) {
                    try {
                        Thread.sleep(5);
                    } catch (Exception e) {
                        // TODO: handle exception
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"售票,票号:"+tikect); 
                    tikect--; 
                    
                }else{
                    break;
                }
            }

        }
    }
}

说明:

>需要被同步的代码,即为操作共享数据的代码

>共享数据,即为多个线程需要操作的数据

>需要被同步的代码,在被synchornized包裹以后,

就使得一个线程在操作这些代码的过程中,其他进必须等待

>同步监视器,俗称锁。哪个线程获取了锁,哪个线程就能执行需要被同步到代码

>同步监视器,可以使用任何一个类的对象充当。

>多个线程必须共用同一个同步监视器

注意:在实现Runnable接口方式中,同步监视器可以考虑用this

在jichengThread类方式中,同步监视器慎用this,可以可以考虑使用:当前类.class

方式2:同步方法

说明:

>如果操作共享数据的代码完整的声明在一个方法中,将此方法声明为同步方法即可。

>非静态同步方法,默认同步监视器是this;静态同步方法,默认同步监视器是当前类本身。

代码:

public class Test3 {
    public static void main(String[] args) {
        saleTikect s = new saleTikect();
        Thread t1 = new Thread(s);
        Thread t2 = new Thread(s);
        Thread t3 = new Thread(s);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

class saleTikect implements Runnable {
    int tikect = 100;
    boolean isFlag=true;

    @Override
    public void run() {
        while (isFlag) {
            show();
        }
    }

    /**
     * 关键代码完整声明在show()中
     */
    public synchronized void show() {
        if (tikect > 0) {
            try {
                Thread.sleep(10);
            } catch (Exception e) {
                // TODO: handle exception
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "售票,票号:" + tikect);
            tikect--;
        }else{
            isFlag=false;
        }
    }
}

正确:

解决继承类出现的问题。

问题代码:

public class Test3two {
    public static void main(String[] args) {
        Window window1 = new Window();
        Window window2 = new Window();
        Window window3 = new Window();

        window1.setName("窗口1");
        window2.setName("窗口2");
        window3.setName("窗口3");

        window1.start();
        window2.start();
        window3.start();
    }
}

class Window extends Thread{
    static int tikect=100;
    @Override
    public void run(){
        while (true) {
            if (tikect>0) {
              System.out.println(Thread.currentThread().getName()+"售票,票号:"+tikect); 
              tikect--; 
            }else{
                break;
            }
        }
    }
}

出现重票:

解决代码:

public class Test3two {
    public static void main(String[] args) {
        Window window1 = new Window();
        Window window2 = new Window();
        Window window3 = new Window();

        window1.setName("窗口1");
        window2.setName("窗口2");
        window3.setName("窗口3");

        window1.start();
        window2.start();
        window3.start();
    }
}

class Window extends Thread{
    static int tikect=100;
    static boolean isFlag=true;
    @Override
    public void run(){
        while (isFlag) {
            show();
        }
    }

    /*public synchronized void show(){//此时同步监视器this:window1,window2,window3。
    */
        public static synchronized void show(){
            //isFlag也要加上static
            //此时同步监视器:当前类,即为window1.class,这是唯一的
        if (tikect>0) {
            System.out.println(Thread.currentThread().getName()+"售票,票号:"+tikect); 
            tikect--; 
          }else{
              isFlag=false;
          }
    }
}

运行结果:

六、懒汉式的线程安全问题、死锁、Lock的使用

1、懒汉式线程安全问题

案例代码:

public class Test4 {
    static Bank b1 = null;
    static Bank b2 = null;
    public static void main(String[] args) {
        Thread t1 = new Thread(){
            @Override
            public void run(){
                b1=Bank.getInstance();
            }
        };
        
        Thread t2 = new Thread(){
            @Override
            public void run(){
                b2=Bank.getInstance();
            }
        };

        t1.start();
        t2.start();

        try {
            t1.join();
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }
        try {
            t1.join();
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }
        System.out.println(b1);
        System.out.println(b2);
        System.out.println(b1==b2);
    }
}

class Bank{
    
    private Bank(){}
    private static Bank instance=null;

    public static Bank getInstance(){
        if(instance==null){

            try {
                Thread.sleep(100);
            } catch (Exception e) {
                // TODO: handle exception
                e.printStackTrace();
            }
            instance=new Bank();
        }
        return instance;
    }
}

运行结果:

理想结果两个地址应该是一样的,但是出现了线程安全问题。

方式一:

public class Test4 {
    static Bank b1 = null;
    static Bank b2 = null;
    public static void main(String[] args) {
        Thread t1 = new Thread(){
            @Override
            public void run(){
                b1=Bank.getInstance();
            }
        };
        
        Thread t2 = new Thread(){
            @Override
            public void run(){
                b2=Bank.getInstance();
            }
        };

        t1.start();
        t2.start();

        try {
            t1.join();
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }
        try {
            t1.join();
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }
        System.out.println(b1);
        System.out.println(b2);
        System.out.println(b1==b2);
    }
}

class Bank{
    
    private Bank(){}
    private static Bank instance=null;

    //方式一:同步监视器
    public synchronized static Bank getInstance(){
        //同步监视器为当前类,是唯一的
        if(instance==null){

            try {
                Thread.sleep(100);
            } catch (Exception e) {
                // TODO: handle exception
                e.printStackTrace();
            }
            instance=new Bank();
        }
        return instance;
    }
}

运行结果:

方式二:

//方式二:同步监视器(同步代码块)
    public static Bank getInstance(){
        synchronized(Bank.class){
            if(instance==null){

                try {
                    Thread.sleep(100);
                } catch (Exception e) {
                    // TODO: handle exception
                    e.printStackTrace();
                }
                instance=new Bank();
            }
        }
        return instance;
    }

方式三:

    //方式三:多套一层判断
    //当两个线程同时进入第一层判断instance为null时,
    //某一线程进入关键代码创建instance(synchronized只允许一个线程执行
    //另一线程进入等待,当某一线程完成创建,释放同步监视器
    //另一线程在第二层判断时instance!=null,return instance
    //此时两个线程得到的instance是相同的
    //若后续线程进入,在第一层判断时instance不为null,直接返回相同地址
//方式三相较于前两种效率更高,但是存在指令重排问题
//(还没有初始化完成就拿着对象出去了,创建对象的准备工作是很多的,虽然这里代码只有一行)
//可加关键字volatite,将instance声明为volatite
    
    private static volatile Bank instance=null;
    public static Bank getInstance(){
        if(instance==null){
            synchronized(Bank.class){
                if(instance==null){
    
                    try {
                        Thread.sleep(100);
                    } catch (Exception e) {
                        // TODO: handle exception
                        e.printStackTrace();
                    }
                    instance=new Bank();
                }
            }
        }
        return instance;
    }

2、死锁问题

举例:

public class DeadLock {
    public static void main(String[] args) {
        StringBuilder s1 = new StringBuilder();
        StringBuilder s2 = new StringBuilder();

        new Thread(){
            @Override
            public void run(){
                synchronized(s1){
                    
                    s1.append("A");
                    s2.append("1");
    
                    try {
                        Thread.sleep(100);
                    } catch (Exception e) {
                        e.printStackTrace();
                        // TODO: handle exception
                    }
    
                    synchronized(s2){
                        s1.append("B");
                        s2.append("2");
    
                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }.start();

        new Thread(){
            @Override
            public void run(){
                synchronized(s2){
                    
                    s1.append("C");
                    s2.append("3");
    
                    try {
                        Thread.sleep(100);
                    } catch (Exception e) {
                        e.printStackTrace();
                        // TODO: handle exception
                    }
    
                    synchronized(s1){
                        s1.append("D");
                        s2.append("4");
    
                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }.start();

    }
}

出现死锁:

3、Lock

针对购票问题:

代码:

import java.util.concurrent.locks.ReentrantLock;

public class LockTest {
    public static void main(String[] args) {
        Window window1 = new Window();
        Window window2 = new Window();
        Window window3 = new Window();

        window1.setName("窗口1");
        window2.setName("窗口2");
        window3.setName("窗口3");

        window1.start();
        window2.start();
        window3.start();
    }
    
}

class Window extends Thread{
    static int tikect=100;

    //创建Lock的实例,确保多个线程共用同一个Lock实例,需要考虑将Lock声明为static final
    private static final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run(){
        while (true) {

            //2.执行lock()方法,锁定对共享资源的调用
            lock.lock();

            try{

                if (tikect>0) {
    
                    try {
                        Thread.sleep(100);
                    } catch (Exception e) {
                        e.printStackTrace();
                        // TODO: handle exception
                    }
    
                    System.out.println(Thread.currentThread().getName()+"售票,票号:"+tikect); 
                    tikect--; 
                }else{
                    break;
                }

            }finally{

                //3.释放对同步共享数据的锁定
                lock.unlock();

            }


        }
    }

}

运行结果:

 

七、线程的通信(生产者消费者问题)

等待唤醒机制:

注意点:

案例:生产者&消费者

/*
 * 1、多线程:生产者、消费者
 * 2、共享数据:产品
 * 3、需要处理线程安全问题:使用同步机制
 * 4、存在线程通信
 */
public class PCTest {
    public static void main(String[] args) {
        Clerk clerk = new Clerk();
        Producer producer = new Producer(clerk);
        Consumer consumer = new Consumer(clerk);

        producer.setName("生产者1");
        consumer.setName("消费者1");

        producer.start();
        consumer.start();
    }
    
}

class Producer extends Thread{

    private Clerk clerk;

    public Producer(Clerk clerk){
        this.clerk=clerk;
    }

    @Override
    public void run(){

        while (true) {
            System.out.println("生产产品中......");

            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
                // TODO: handle exception
            }

            clerk.addProduct();
        }

    }
}

class Consumer extends Thread{

    private Clerk clerk;

    public Consumer(Clerk clerk){
        this.clerk=clerk;
    }

    @Override
    public void run(){
        while (true) {
            System.out.println("消费产品......");

            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
                // TODO: handle exception
            }

            clerk.minusProduct();
        }

    }
}

class Clerk{
    private int productNumber=0;

    public synchronized void addProduct(){
        if(productNumber>=20){
            
            //等待
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
                // TODO: handle exception
            }

        }
        
        productNumber++;
        System.out.println(Thread.currentThread().getName()+"生产了第"+productNumber+"个产品");
    }

    public synchronized void minusProduct(){

        if(productNumber<=0){
            
            //等待
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
                // TODO: handle exception
            }
    
        }
        
        System.out.println(Thread.currentThread().getName()+"消费了第"+productNumber+"个产品");
        productNumber--;

    }
}

wait()和sleep()的区别:

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到