Java进阶学习(三)之多线程编程

一、进程与线程

1. 进程:

进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。

2. 线程

线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。

3. 主线程

Java程序至少会有一个线程,这就是主线程,程序启动后是由JVM创建主线程,程序结束时由JVM停止主线程。
负责管理子线程,即子线程的启动、挂起、停止等等操作。

4. 线程优先级

Java虚拟机允许应用程序同时执行多个执行线程。每个线程都有优先权。 具有较高优先级的线程优先于优先级较低的线程执行。
每个线程可能也可能不会被标记为守护程序。
当在某个线程中运行的代码创建一个新的Thread对象时,新线程的优先级最初设置为等于创建线程的优先级,
并且当且仅当创建线程是守护进程时才是守护线程。

二、线程的创建

1. 创建多线程程序的第一种方式:创建Thread类的子类

java.lang.thread,必须实现thread类

实现步骤:

  • 创建一个Thread类的子类
  • 在Thread类的子类中重写Thread中的run方法,设置线程任务(开启线程要做什么?)
  • 调用Thread中的start方法,开启线程,执行run方法。
    void start()使该线程(main线程)和另一个线程(创建的新线程,执行其Run方法)。
    多次启动一个线程是非法的。特别是当前的线程已经结束执行后,不能重新启动。
public class CreateThreadDemo {
    
    
    public static void main(String[] args) {
    
    
        MyThread myThread = new MyThread();
        myThread.start();

        for(int i = 0; i < 20; i++){
    
    
            System.out.println("main"+i);
        }
    }
}

public class MyThread extends Thread {
    
    
    @Override
    public void run() {
    
    
        for(int i = 0; i < 20; i++){
    
    
            System.out.println("run:"+i);
        }
    }
}

2. 获取线程的名称:

1).使用Thread类中的方法getName()
String getName()返回该线程的名称。
2).可以先获取到当前正在执行的线程,使用线程的方法getName()获取线程的名称
static Thread currentThread() 返回对当前正在执行的线程对象的引用。

/*
    线程的名称:
        主线程:main
        新线程:Thread-0,Thread-1,Thread-2
 */
public class DemoGetThreadName {
    
    
    public static void main(String[] args) {
    
    
        MyThreads myThreads = new MyThreads();
        myThreads.start();

        new MyThreads().start();

        new MyThreads().start();

        //链式编程
        System.out.println(Thread.currentThread().getName());
    }
}

//定义一个Thread类的子类
public class MyThreads extends Thread {
    
    
    @Override
    public void run() {
    
    
        //获取线程名称
        //String name = getName();
        //System.out.println(name);

//        Thread t = currentThread();
//        System.out.println(t);
//        String name = t.getName();
//        System.out.println(name);

        //链式编程
        System.out.println(Thread.currentThread().getName());
    }
}

3. 设置线程名称:(了解)

1).使用Thread类中的方法setName(名字)
void setName(String name)
改变线程名称,使之与参数name相同。
2).创建一个带参数的构造方法,参数传递线程名称;调用父类的带参构造方法,把线程名称传递给父类,让父类(Thread)给子线程起一个名字
Thread(String name)分配新的Thread对象。

public class MyThread extends Thread {
    
    
    public MyThread(){
    
    

    }

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

    @Override
    public void run() {
    
    
        //获取线程的名称
        System.out.println(Thread.currentThread().getName());
    }
}

4.进程睡眠

public static void sleep(longmills):使当前正在执行的线程以指定毫秒数暂停(暂时停止执行),毫秒数结束后,线程继续执行。
public class Demo01Sleep {
    
    
    //模拟秒表
    public static void main(String[] args) {
    
    
        for(int i = 0;i <= 60; i++){
    
    
            System.out.println(i);

            //使用Thread类的Sleep方法让程序睡眠一秒钟
            try {
    
    
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
    }
}

5. 创建多线程的第二种方法:实现Runnable接口

java.lang.Runnable
Runnable接口应由任何类实现,其实例将由线程执行。 该类必须定义一个无参数的方法,称为run 。

	java.lang.Thread类的构造方法
      Thread(Runnable target)分配新的Thread对象
      Thread(Runnable target,String name)分配新的Thread对象
**实现步骤:**
    1).创建一个Runnable接口的实现类
    2).在实现类中重写Runnable接口的run方法,设置线程任务
    3).创建一个Runnable接口的实现类对象
    4).创建Thread类对象,构造方法中传递Runnable接口的实现类对象
    5).调用Thread类中的start方法,开启新的线程执行方法
    
**使用Runnable接口创建多线程程序的好处:**

    1).避免了单继承局限性
        一个类只能继承一个类(一个人只有一个亲爹),类继承了thread类就不能继承其它的类
        实现了Runnable接口,还可以继承其它类,实现其它的接口
    2).增强了程序的扩展性,降低了程序的耦合性(解耦)
        实现Runnable接口的方式,把设置线程任务和开启线程分(解耦)
        实现类中,重写了run方法:用来设置线程任务
        创建Thread类对象,调用start方法:用来开启新的线程
public class Demo01_Runnable {
    
    
    public static void main(String[] args) {
    
    
        RunnableImpl rmp = new RunnableImpl();
        //Thread thread = new Thread(rmp);
        //thread.start();

        Thread t = new Thread(new RunnableImpl2());
        t.start();

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

6. 第三种方法创建线程:匿名内部类方式创建线程

匿名:没有名字
内部类:写在其它类内部中的类

**匿名内部类作用**:简化代码
    把子类继承父类、重写父类的方法和创建子类对象合一步完成
匿名内部类的最终产物:子类/实现类对象,而这个类没有名字

**格式:**
    new 父类/接口(){
        重复父类或接口中的方法
    };
public class Demo01_InnerClassThread {
    
    
    public static void main(String[] args) {
    
    
        //线程的父类使Thread
        //new Thread(){}.start;
        new Thread(){
    
    
            //重写run方法,设置线程任务
            @Override
            public void run() {
    
    
                for(int i=0;i<15;i++){
    
    
                    System.out.println(Thread.currentThread().getName()+"-->"+i);
                }
            }
        }.start();

        //线程的接口Runnable
        //RunnableImpl run = new RunnableImpl();
        Runnable run = new Runnable(){
    
    
            //重写run方法,设置线程任务
            @Override
            public void run() {
    
    
                for(int i=0;i<15;i++){
    
    
                    System.out.println(Thread.currentThread().getName()+"-->"+i+"黑马");
                }
            }
        };
        new Thread(run).start();

        //简化接口的方式
        new Thread(new Runnable() {
    
    
            //重写run方法,设置线程任务
            @Override
            public void run() {
    
    
                for(int i=0;i<15;i++){
    
    
                    System.out.println(Thread.currentThread().getName()+"-->"+i+"程序员");
                }
            }
        }).start();
    }
}

三、线程的状态

在线程的生命周期中,线程会有几种状态:

1. 新建状态

新建状态(New)是通过new等方式创建线程对象,它仅仅是一个空的线程对象。

2.就绪状态

当主线程调用新建线程的start()方法后,它就进入就绪状态(Runnable)。此时的线程尚未真正开始执行run()方法,它必须等待CPU的调度。

3.运行状态

CPU调度就绪状态的线程,线程进入运行状态(Running),处于运行状态的线程独占CPU,执行run()方法。

4.阻塞状态

因为某种原因运行状态的线程会进入不可运行状态,即阻塞状态(Blocked),处于阻塞状态的线程JVM系统不能执行该线程,即使CPU空闲,也不能执行该线程。

几个原因会导致线程进入阻塞状态:

当前线程调用sleep()方法,进入休眠状态。 被其他线程调用了join()方法,等待其他线程结束。
发出I/O请求,等待I/O操作完成期间。 当前线程调用wait()方法。
处于阻塞状态可以重新回到就绪状态,如:休眠结束、其他线程加入、I/O操作完成和调用notify或notifyAll唤醒wait线程。

5.死亡状态

线程退出run()方法后,就会进入死亡状态(Dead),线程进入死亡状态有可能是正常实现完成run()方法进入,也可能是由于发生异常而进入的。

四、线程安全问题

1. 解决线程安全的第一种方法:使用同步代码块

格式:
synchronized(锁对象){
    可能出现线程安全问题的代码(访问了共享数据的代码)
}

注意:
    1).通过代码块中的锁对象,可以使用任意的对象
    2).但是必须保证多个线程使用的锁对象是同一个
    3).锁对象的作用:
        把同步代码块锁住,只让一个线程在同步代码块中执行

2. 解决线程安全的第二种方法:使用同步方法

**使用步骤:**
    1).把访问量共享数据的代码放到一个方法中去
    2).在方法上添加Synchronized修饰符

**格式:**

	修饰符 synchronized 返回值类型 方法名(参数列表){
    	可能出现线程安全问题的代码(访问了共享数据的代码)
    }

3. 解决线程安全的第三种方法:使用lock锁

java.util.concurrent.locks.Lock接口
Lock实现提供比使用synchronized方法和语句可以获得的更广泛的锁定操作。
它们允许更灵活的结构化,可能具有完全不同的属性,并且可以支持多个相关联的对象Condition 。

**Lock中的方法:**
    
    1).void lock() 获得锁。
    2).void unlock()释放锁。

java.util.concurrent.locks.ReentrantLock implement Lock接口

**使用步骤:**
    
    1).在成员位置创建一个ReentrantLock对象
    2).在可能会出现线程安全问题的代码前调用Lock接口中的方法lock获取锁
    3).在可能会出现线程安全问题的代码前调用Lock接口中的方法unlock释放锁
public class RunnableImpl implements Runnable {
    
    
    //设置线程任务
    private static int ticket = 100;

    Lock l = new ReentrantLock();

    @Override
    public void run() {
    
    
        System.out.println("this:"+this);
        while (true) {
    
    
            l.lock();
            //线程进,进入获取锁对象
            if (ticket > 0) {
    
    
                //提高安全问题出现的概率,让程序睡眠
                try {
    
    
                    Thread.sleep(10);
                    //票存在,则可以出售
                    System.out.println(Thread.currentThread().getName() + "正在出售第-->" + ticket-- + "张票");
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }finally {
    
    
                    //线程出,释放获取锁对象
                    //无论程序是否执行异常都将释放锁对象
                    l.unlock();
                }
            }
        }
    }


    /*@Override
    public void run() {
        System.out.println("this:"+this);
        while (true) {
            l.lock();
            //线程进,进入获取锁对象
            if (ticket > 0) {
                //提高安全问题出现的概率,让程序睡眠
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //票存在,则可以出售
                System.out.println(Thread.currentThread().getName() + "正在出售第-->" + ticket-- + "张票");
            }
            //线程出,释放获取锁对象
            l.unlock();
        }
    }
*/
    /*
        静态的同步方法锁对象是谁?
            --不是this,this是对象创建后产生的,静态方法优先于对象
            静态方法的锁对象是本类的Class属性-->class文件对象(反射)
     */
    public static /*synchronized*/ void payTicketStatic(){
    
    
        synchronized (RunnableImpl.class) {
    
    
            //线程进,进入获取锁对象
            if (ticket > 0) {
    
    
                //提高安全问题出现的概率,让程序睡眠
                try {
    
    
                    Thread.sleep(10);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                //票存在,则可以出售
                System.out.println(Thread.currentThread().getName() + "正在出售第-->" + ticket-- + "张票");
            }
            //线程出,释放获取锁对象
        }
    }

    /*
        定义一个同步方法
        同步方法也会把方法内部代码锁住
        只让一个线程执行
        同部方法的对象是谁?
            就是new RunnableImpl()
            也是就是this
     */
    public synchronized void payTicket(){
    
    
        //线程进,进入获取锁对象
        if (ticket > 0) {
    
    
            //提高安全问题出现的概率,让程序睡眠
            try {
    
    
                Thread.sleep(10);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            //票存在,则可以出售
            System.out.println(Thread.currentThread().getName() + "正在出售第-->" + ticket--+"张票");
        }
        //线程出,释放获取锁对象
    }
}

五、 线程通信

/*
    进入到TimeWaiting(计时等待)有2种方式
        1.使用sleep(long m)方法,在毫秒值结束后线程睡醒进入到Runnable/Blocked状态
        2.使用wait(long m)方法,wait方法如果在毫秒值结束之后,还没有被notify唤醒,就会自动醒来

    唤醒的方法:
     void notify():唤醒在此对象监视器上等待的单个线程
     void notifyAll():唤醒在此对象监视器上等待的所有线程

 */
public class Demo02WaitAndNotify {
    
    
    public static void main(String[] args) {
    
    
        Object obj = new Object();
        new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                //一直等着买包子
                while(true) {
    
    
                    System.out.println("顾客1:老板我要吃包子");
                    synchronized (obj) {
    
    
                        try {
    
    
                            obj.wait(5000);
                        } catch (InterruptedException e) {
    
    
                            e.printStackTrace();
                        }
                    }
                    System.out.println("包子做好了,顾客1正在吃包子");
                    System.out.println("-----------------------------------------------------");
                }
            }
        }).start();

        new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                //一直等着买包子
                while(true) {
    
    
                    System.out.println("顾客2:老板我要吃包子");
                    synchronized (obj) {
    
    
                        try {
    
    
                            obj.wait(5000);
                        } catch (InterruptedException e) {
    
    
                            e.printStackTrace();
                        }
                    }
                    System.out.println("包子做好了,顾客2正在吃包子");
                    System.out.println("-----------------------------------------------------");
                }
            }
        }).start();

        new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                //一直在做包子
                while(true) {
    
    
                    System.out.println("老板正在做包子,等待5秒钟...");
                    try {
    
    
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                    synchronized (obj) {
    
    
                        //obj.notify();//随机唤醒一个线程
                        obj.notifyAll();//唤醒所有的线程
                    }
                }
            }
        }).start();
    }
}

六、线程池

/*
    JDK1.5之后出现的
    java.util.concurrent.Executors:线程池的工厂类
    Executors类中的静态方法:
        static ExecutorService newFixedThreadPool(int nThreads)  创建一个可重用固定线程的线程池
        参数:
            int nThreads创建线程池中线程的数量
        返回值:
            ExcecutorService接口,返回的是ExcecutorService接口的实现类对象,我们可以使用ExcecutorService接口接收(面向接口编程)

    java.util.concurrent.ExecutorService:线程池接口
        用来从线程池中获取线程,调用start方法;执行线程任务
        submit(Runnable task)提交一个Runnable任务用于执行
        关闭/销毁线程池:
        void shutDown()

    线程池的使用步骤:
        1.使用线程池的工,厂类Executors里面提供的静态方法newFixedThreadPool(int nThreads);生产指定数目线程的线程池
        2.创建一个类,实现Runnable接口,重写Run方法,设置线程任务
        3.调用ExecutorService中的方法submit,传递线程任务(实现类),开启线程,执行run方法;
        4.调用ExecutorService中的方法shutDown()销毁线程池(不建议执行);
 */
public class Demo01ThreadPool {
    
    
    public static void main(String[] args) {
    
    
        // 1.使用线程池的工,厂类Executors里面提供的静态方法newFixedThreadPool(int nThreads);生产指定数目线程的线程池
        ExecutorService es = Executors.newFixedThreadPool(2);
        //3.调用ExecutorService中的方法submit,传递线程任务(实现类),开启线程,执行run方法;
        es.submit(new RunnableImpl());//pool-1-thread-2创建一个新的线程执行
        //线程池会一直开启,使用完了线程,会自动把线程归还给线程池,线程可以继续使用
        es.submit(new RunnableImpl());//pool-1-thread-2创建一个新的线程执行
        es.submit(new RunnableImpl());//pool-1-thread-1创建一个新的线程执行

        es.shutdown();
        es.submit(new RunnableImpl());//error
    }
}
public class RunnableImpl implements Runnable {
    
    
    @Override
    public void run() {
    
    
        // 2.创建一个类,实现Runnable接口,重写Run方法,设置线程任务
        System.out.println(Thread.currentThread().getName()+"创建一个新的线程执行");
    }
}

猜你喜欢

转载自blog.csdn.net/qq_35843514/article/details/104466822