zoukankan      html  css  js  c++  java
  • Java 多线程

    1. 多线程

    进程:正在运行的程序,是系统进行资源和调度的独立单位,每一进程都有独立的内存空间和系统资源

    多进程的意义:单进程的计算机只能做一件事情,多进程可以执行多个进程 ,提供CPU的使用率,实际是CPU在不同进程之间的高效切换

    线程:在同一个进程内可以执行多个任务,而每一个任务都可以看成是一个线程,是程序的执行单元,执行路径,是程序使用CPU的最基本单位,多线程有多条执行路径

    多线程的意义:提供程序的使用率,程序的执行都是在抢CPU的执行权,线程的执行具有随机性

    Java程序的运行原理:由java命令启动JVM,JVM启动相当于启动了一个进程

    JVM虚拟机的启动时单线程的,垃圾回收线程也要启动,最少需要启动两个 线程

    2. 多线程实现

    2.1 继承Thread类,该子类重写run()方法,创建对象,启动线程

    run()和start()的区别:

    run():仅仅是封装被线程执行的代码,调用时普通方法。

    start():首先启动了线程,然后再由JVM去调用线程的run()方法

        public static void main(String[] args){
            MyThread my1 = new MyThread();
            MyThread my2 = new MyThread();

          my1.setName("xiaojignzi");
          my2.setName("xiaojunzi");

            my1.start();
            //my1.start();    IllegalThreadStateException:非法的线程状态异常,相当于一个线程被调用了两次
            my2.start();
        }

    获取线程对象的名称

    public class MyThread extends Thread {
        @Override
        public void run(){
            for(int x = 0; x < 100; x++){
                System.out.println(getName() + "-----" + x);
            }
        }
    }

    如何获取main方法所在的线程对象的名称

    Thread.currentThread().getName()

    线程的调度

    计算机只有一个CPU时,CPU在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令。

    线程具有两种调度模型

    1. 分时调度模型:所有的线程轮流调用CPU的使用权,平均分配给每个线程占用CPU的时间片

    2. 抢占式调度模型,优先让优先级高的线程使用CPU,如果线程的优先级相同会随机选择一个,优先级高的线程获取CPU时间片相对多一些

    java使用抢占式调度模型:

    int getPriority():获取线程的优先级

    setPriority(int):设置线程的优先级

    默认的优先级为5,可以设置的优先级范围是1-10

    线程的控制:

    线程休眠:public static void sleep(long millis):在指定的毫秒内让当前正在执行的线程休眠(暂停实现)。此操作受到系统计时器和调度程序的精确度和准确性的影响

    线程加入:public final void join():等待本线程终止,程序再继续向后面执行

    线程礼让:public static void yield():暂停当前正在执行的线程对象,并执行其他线程,不能靠它保证每个线程轮流依次指向

    线程守护:public final void setDaemon(boolean on):将该线程标记为守护线程或者用户线程,当正在运行的线程都是守护线程时,java虚拟机退出,该方法必须在启动线程前调用,写在main函数时,main主线程执行完,则退出

    线程结束:public void stop:让线程停止,已经过时的函数。

            public void interrupt:中断线程

    线程的生命周期:

    新建:创建线程对象

    就绪:有执行资格,没有执行权

    运行:有执行资格,有执行权

       阻塞:由于一些操作可以让线程处于该状态,没有执行资格,没有执行权,而另一些操作可以把它激活,激活后处于就绪状态

    死亡:线程对象变成垃圾,等待被回收

    2.2 实现Runnable接口

    1. 自定义类MyRunnable实现Runnable接口

    2. 重写run()方法

    3. 创建MyRunnable类的对象

    4. 创建Thread类的对象,并把3步骤的对象作为构造函数传递

        public static void main(String[] args){
            MyRunnable my = new MyRunnable();
            Thread my1 = new Thread(my);
            Thread my2 = new Thread(my);
            
            my1.setName("wangwang");
            my2.setName("hahah");
            
            my1.start();
            my2.start();
            
        }
    
    public class MyRunnable implements Runnable{
    
        @Override
        public void run() {
            for(int x = 0; x < 100; x++){
                System.out.println(Thread.currentThread().getName() + x);
            }
        }
    
    }

    好处:可以避免由于java单继承带来的局限性

         适合多个相同程序的代码去处理一个资源的情况,把线程通程序的代码,数据有效的分离,较好的体现了面向对象的设计思想

    线程的安全性问题:

    A:是否是多线程环境

    B:是否有共享数据

    C:是否有多条语句操作共享数据

    public class SellTicket implements Runnable {
        
        private int tickets = 100;
    
        @Override
        public void run() {
            // TODO Auto-generated method stub
            while(true){
                if(tickets > 0){
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets-- + "张票");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }
        }
        
    }
    public class SellTicketsDemo {
        public static void main(String[] args){
            SellTicket st = new SellTicket();
            Thread t1 = new Thread(st);
            Thread t2 = new Thread(st);
            Thread t3 = new Thread(st);
            
            t1.start();
            t2.start();
            t3.start();
        }
    }

    上述的操作存在线程的安全性问题:

    解决方法:把多条语句的共享数据包装成一个整体,当有线程在执行时,别的线程不能执行

    同步代码块:synchronized(obj){}

    public class SellTicket implements Runnable {
    
        private int tickets = 100;
        private Object obj = new Object();
    
        @Override
        public void run() {
            // TODO Auto-generated method stub
            while (true) {
                synchronized (obj) {
                    if (tickets > 0) {
                        System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets-- + "张票");
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }

     同步代码块的锁是任意的对象。

    同步方法的格式以及锁的问题:this

    静态方法的锁对象:对象名.class

    线程安全的类:

    StringBuffer,Vector,Hashtable

     

    JDK5后出现的Lock锁的应用:

    Lock(接口):

    void lock():获取锁

    void unlock():释放锁

    ReentrantLock是Lock的实现类

    例火车票卖票:

    public class SellTickesDemo {
        public static void main(String[] args){
            SellTicket st = new SellTicket();
            Thread t1 = new Thread(st);
            Thread t2 = new Thread(st);
            Thread t3 = new Thread(st);
            
            t1.start();
            t2.start();
            t3.start();
            
        }
    }
    public class SellTicket implements Runnable {
        private int tickets = 100;
        private Lock lock = new ReentrantLock();
    
        @Override
        public void run() {
            // TODO Auto-generated method stub
            while(true){
                try{
                    lock.lock();
                    if(tickets > 0){
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets-- + "张票");
                    }
                }finally{
                    lock.unlock();
                }
            }
        }
    }

    死锁问题的概述和使用:

    同步的弊端:效率低,如果出现了同步嵌套,就容易出现死锁的问题

    死锁的问题和代码:

    指两个或者两个以上的线程在执行的过程中,因争夺资源而产生的一种互相等待的现象

    public class MyLock {
        // 创建两把锁对象
        public static final Object objA = new Object();
        public static final Object objB = new Object();
        
    }
    public class DieLock extends Thread { private boolean flag; public DieLock(boolean flag){ this.flag = flag; } @Override public void run(){ if(flag){ synchronized(MyLock.objA){ System.out.println("if objA"); synchronized(MyLock.objB){ System.out.println("if objB"); } } }else{ synchronized(MyLock.objB){ System.out.println("else objB"); synchronized(MyLock.objA){ System.out.println("else obja"); } } } } }
    public class DeadDemo { public static void main(String[] args){ DieLock d1 = new DieLock(true); DieLock d2 = new DieLock(false); d1.start(); d2.start(); } }
    有可能产生死锁现象:此时的输出为:
    else objB  if objA
    
    

    多线程之间的通信问题:同步锁解决。

    等待唤醒机制

    生产者:是否有数据,如果有就等待,没有就生产,生产完通知消费者来消费

    消费者:是否有数据,如果有就消费,没有就等待,通知生产者生产数据

    Object提供了三个方法

    wait():等待

    notify():唤醒单个线程

    notifyAll():唤醒所有线程,所以定义在object中

    这些方法的调用必须通过锁对象来调用

    锁池:假设线程A已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized块),由于这些线程在进入对象的synchronized方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程A拥有,所以这些线程就进入了该对象的锁池中。

    等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁后,进入到了该对象的等待池中

    notify和notifyAll的区别

    如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。

    当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只要一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争

    优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。

    循环里调用 wait 和 notify,不是在 If 语句

    现在你知道wait应该永远在被synchronized的背景下和那个被多线程共享的对象上调用,下一个一定要记住的问题就是,你应该永远在while循环,而不是if语句中调用wait。因为线程是在某些条件下等待的——在我们的例子里,即“如果缓冲区队列是满的话,那么生产者线程应该等待”,你可能直觉就会写一个if语句。但if语句存在一些微妙的小问题,导致即使条件没被满足,你的线程你也有可能被错误地唤醒。所以如果你不在线程被唤醒后再次使用while循环检查唤醒条件是否被满足,你的程序就有可能会出错——例如在缓冲区为满的时候生产者继续生成数据,或者缓冲区为空的时候消费者开始小号数据。所以记住,永远在while循环而不是if语句中使用wait!

    出处:https://blog.csdn.net/djzhao/article/details/79410229

    举例:生产者和消费者:

    public class Student {
        String name;
        int age;
        boolean flag;
    }
    
    public class GetThread implements Runnable {
        private Student s;
    
        public GetThread(Student s) {
            this.s = s;
        }
    
        @Override
        public void run() {
            // TODO Auto-generated method stub
            while (true) {
                synchronized (s) {
                    while (!s.flag) {
                        try {
                            s.wait();    // 等待以后立即释放锁,上面使用while的区别在于执行完这个语句以后仍然会去判断条件是否满足
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                    }
                    System.out.println(s.name + "-----" + s.age);
                    s.flag = false;
                    s.notify();
                }
            }
        }
    }
    
    public class SetThread implements Runnable {
        private Student s;
        private int x = 0;
    
        public SetThread(Student s) {
            // TODO Auto-generated constructor stub
            this.s = s;
        }
    
        @Override
        public void run() {
            // TODO Auto-generated method stub
            while(true){
                synchronized (s) {
                    while(s.flag){
                        try {
                            s.wait();
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                    }
    
                    if (x % 2 == 0) {
                        s.name = "xiaojignz";
                        s.age = 20;
                    } else {
                        s.name = "xiaoxiao";
                        s.age = 100;
                    }
                    x++;
                    s.flag = true;
                    s.notify();
                }
            }
        }
    }
    public static void main(String[] args){ Student s = new Student(); SetThread st = new SetThread(s); GetThread gt = new GetThread(s); Thread t1 = new Thread(st); Thread t2 = new Thread(gt); t1.start(); t2.start(); }

     生产者和消费者改进版

    public class Student {
        private String name;
        private int age;
        private boolean flag;
        
        public synchronized void set(String name, int age){
            while(this.flag){
                try{
                    this.wait();
                }catch(InterruptedException e){
                    e.printStackTrace();
                }
            }
            
            this.name = name;
            this.age = age;
            this.flag = true;
            this.notify();
        }
        
        public synchronized void get(){
            while(!this.flag){
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            System.out.println(this.name + "------" + this.age);
            this.flag = false;
            this.notify();
        }
    }
    
    public class GetThread implements Runnable {
        private Student s;
    
        public GetThread(Student s) {
            this.s = s;
        }
    
        @Override
        public void run() {
            // TODO Auto-generated method stub
            while (true) {
                s.get();
            }
        }
    }
    
    public class SetThread implements Runnable {
        private Student s;
        private int x = 0;
    
        public SetThread(Student s) {
            // TODO Auto-generated constructor stub
            this.s = s;
        }
    
        @Override
        public void run() {
            // TODO Auto-generated method stub
            while (true) {
                if (x % 2 == 0) {
                    s.set("ha", 30);
                } else {
                    s.set("wang", 20);
                }
                x++;
            }
        }
    }
    
    public class StudentDemo {
        public static void main(String[] args){
            Student s = new Student();
            
            SetThread st = new SetThread(s);
            GetThread gt = new GetThread(s);
            
            Thread t1 = new Thread(st);
            Thread t2 = new Thread(gt);
            
            t1.start();
            t2.start();
            
            
        }
    }

    线程组:Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类和管理,Java允许程序直接对线程组进行控制,默认情况下,所有的线程都属于主线程组

    获取当前线程组:public final ThreadGroup getThreadGroup()

    设置线程组:Thread(ThreadGroup group, Runnable target, String name)

    public class ThreadGroupDemo {
        public static void main(String[] args) {
            MyRunnable my = new MyRunnable();
            ThreadGroup tg = new ThreadGroup("新组");
    
            Thread t1 = new Thread(tg, my, "xiaojingzi");
            Thread t2 = new Thread(tg, my, "xiaoxiao");
            
            ThreadGroup t3 = t1.getThreadGroup();
            ThreadGroup t4 = t2.getThreadGroup();
            
            System.out.print(t3.getName() + "-----" + t4.getName() + "-----");
            System.out.println(Thread.currentThread().getThreadGroup().getName());
            
            tg.setDaemon(true);  //没有明显的效果
            t1.setDaemon(true);
            t2.setDaemon(true);
            t1.start();
            t2.start();
        }
    }
    新组-----新组-----main
    xiaojingzi----0
    xiaoxiao----0
    xiaojingzi----1
    xiaoxiao----1
    xiaojingzi----2
    xiaoxiao----2
    xiaojingzi----3
    xiaoxiao----3
    xiaoxiao----4
    xiaoxiao----5
    xiaoxiao----6

    线程池

    程序器启动一个新的线程的成本是很高的,因为它设计到要与操作系统 进行交互,而使用线程池可以很高的提高性能,尤其是当程序中要创建大量的生存周期很短的线程时,更应该考虑使用线程池

    JDK5新增Executors工厂类来产生线程池:

    public static ExecutorService newCachedThreadPool();

    public static ExecutorService newFixedThreadPool(int nThreads)

    public static ExecutorService newSingleThreadExecutor()

    这些方法的返回值都是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程,它提供了如下的方法:

    Future<?> submit(Runnable task)

    <T> Future<T> submit(Callable<T> task)

    public class ExecutorsPoolDemo {
        public static void main(String[] args){
            ExecutorService pool = Executors.newFixedThreadPool(2);
            pool.submit(new MyRunnable());
            pool.submit(new MyRunnable());
            pool.shutdown();        //用完以后等待可以回收继续使用,shutdown结束
        }
    }

    2.3 实现Callable接口(带泛型,依赖于线程池存在)

    public class MyCallable implements Callable<Integer> {
        private int number;
        
        public MyCallable(int number){
            this.number = number;
        }
    
        @Override
        public Integer call() throws Exception {
            // TODO Auto-generated method stub
            int sum = 0;
            for(int x = 0; x <= number; x++){
                sum += x;
            }
            return sum;
        }
    }
    
    public class MyCallabelDemo {
        public static void main(String[] args) throws InterruptedException, ExecutionException{
            ExecutorService pool = Executors.newFixedThreadPool(2);
            Future<Integer> f1 = pool.submit(new MyCallable(10));
            Future<Integer> f2 = pool.submit(new MyCallable(20));
            Integer i1 = f1.get();
            Integer i2 = f2.get();
            System.out.println(i1 + "-----" + i2);
            pool.shutdown();
        }
    }

    匿名内部类方式实现多线程程序

    public class ThreadDemo {
        public static void main(String[] args){
            new Thread(){
                @Override
                public void run(){
                    for(int x = 0; x < 100; x++){
                        System.out.println(Thread.currentThread().getName() + "----" + x);
                    }
                }
            }.start();
            
            new Thread(new Runnable(){
                @Override
                public void run(){
                    for(int x = 0; x < 100; x++){
                        System.out.println(Thread.currentThread().getName() + "----" + x);
                    }
                }
            }){}.start();
            
        }
    }

    定时器:定时器是一个十分应用广泛的线程工具,可以用于调度多个定时任务以后台线程的方式执行,在java中,可以通过Timer和TimerTask类来实现定义调度的功能

    Timer:

    public Timer()

    public void schedule(TimerTask task, long delay):安排在指定延迟后执行指定的任务

    public void shcedule(TimerTask task, Date time):安排在指定的时间执行指定的任务

    public void schedule(TimerTask task, long delay, long period):安排在指定的时间后指定的周期执行任务

    public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period):安排指定的任务在指定的时间开始重复的固定延迟执行

    public void cancel():终止此计数器,丢弃当前所有已经安排的任务

    TimerTask:

    public abstract void run()

    public boolean cancel():终止此计时器,丢弃任何当前计划的任务。

    public class TimerDemo {
        public static void main(String[] args){
            Timer t = new Timer();
            Timer m = new Timer();
    //        t.schedule(new MyTask(t), 3000);
    //        t.schedule(new MyTask(), 3000, 2000);
            m.scheduleAtFixedRate(new MyTask(m), new Date(), 3000);
        }
    }
    
    class MyTask extends TimerTask{
        private Timer t;
        
        
        public MyTask() {
            super();
            // TODO Auto-generated constructor stub
        }
    
        
        public MyTask(Timer t) {
            super();
            this.t = t;
        }
    
    
        @Override
        public void run(){
            System.out.println("GG");
    //        t.cancel();
        }
    }

    3. 单例模式的饿汉式

    public class RuntimeDemo {
        public static void main(String[] args) throws IOException{
            Runtime r = Runtime.getRuntime();
            r.exec("calc");    // 执行dos命令
        }
    }
  • 相关阅读:
    【贪心】POJ1017:Packets
    【贪心】POJ2393:Yogurt factory
    【贪心】POJ3190:Stall Reservations
    【递归】地盘划分
    【递归与递推】青蛙过河
    【搜索】POJ1242:Rescue
    单词方阵(dfs)
    反向因子Inverse Factorial
    P1604 B进制星球
    抵制克苏恩(记忆化搜索)
  • 原文地址:https://www.cnblogs.com/feng-ying/p/9597675.html
Copyright © 2011-2022 走看看