Java 多线程总结

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 多线程总结相关的知识,希望对你有一定的参考价值。

1.简介

在现实的生活中,有很多的事情是可以同时进行的,比如一边听歌,一边撸着代码。在java中,为了模拟现实中的这种情况因此引入了线程的机制。

进程:在操作系统中的多任务执行时以进程为单位的,系统分配进程有限的cup时间片去执行某进程,下一个cup时间片又去执行其他的进程,

由于cpu转换较快,使得进程好像是在同时执行。


线程:线程是存在于进程中的执行流程,一个进行可以包括多个线程,这些线程共享进程的内存,每个线程可以得到一小段的执行时间,因此一个

进程就能具有多个并发执行的线程。一个进程至少要有一个线程。

2.线程的实现

线程的实现主要有两种方式:

1.继承Thread类,重写run方法,在其中书写线程任务逻辑

2.定义线程要执行的任务,即定义一个类实现Runnable接口,然后创建线程的同时将任务指定

public class CreatThreadDemo01 {
    public static void main(String[] args) {
        Thread thread1 = new Dream();
        Thread thread2 = new Reality();
        /**
         * 启动线程要调用start方法,不能直接调用run方法
         * 
         * start方法会将当前线程纳入到线程调度中,使其具有并发运行能力。
         * start方法很快会执行完毕,当start方法执行完毕后,当前线程的run方法会很快的被执行
         * 起来(只要获取到了cpu的时间)。但不能理解为调用start方法时run方法就执行了!
         * 
         * 线程有几个不可控因素:
         * 1:cpu分配时间片给哪个线程我们说了不算
         * 2:时间长短也不可控
         * 3:线程调度会尽可能均匀的将时间片分配给多个线程。
         * 
         */
        thread1.start();
        thread2.start();
        
    }
}
/**
 * 第一种创建线程的方式存在两个不足:
 * 1:由于java是单继承的,这就导致我们若继承了Thread类就无法再继承其他类,这在写项目时会遇到很大问题。
 * 2:由于我们定义线程的同时重写run方法来定义线程要执行的任务,这就导致线程与任务有一个强耦合关系,
 *      线程的重要性变得非常局限。
 */
class Dream extends Thread{

    public void run() {
        for(int i = 0; i < 1000; i++){
            System.out.println("理想很丰满!");
        }
    }

}

class Reality extends Thread{
    
    public void run(){
        for(int i = 0; i < 1000; i++){
            System.out.println("现实很骨感。。。。");
        }
    }
}
public class CreatThreadDemo02 {
    public static void main(String[] args) {
        Runnable r1 = new MyThread1();
        Runnable r2 = new MyThread2();
        Thread t1 = new Thread(r1);
        Thread t2 = new Thread(r2);
        t1.start();
        t2.start();
    }
}


class MyThread1 implements  Runnable {

    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("干什么");
        }
    }
    
}

class MyThread2 implements Runnable{
    
    public void run(){
        for (int i = 0; i < 10000; i++) {
            System.out.println("去吃饭了");
        }
    }
    
}

使用匿名内部类创建线程

public class CreatThreadDemo03 {
    public static void main(String[] args) {
        //使用匿名内部类创建线程
        //方式1
        Thread  t1 = new Thread(){
            public void run(){
                for(int i = 0; i < 1000; i++){
                    System.out.println("天气很热");
                }
            }
        };
        
        t1.start();
        //方式2
        Runnable r = new Runnable() {
        
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    System.out.println("热就开风扇啊");
                }
                
            }
        };
        
        Thread t2 = new Thread(r);
        t2.start();
        
    }
}

3.线程的一些操作方法使用

1.获取运行当前方法的线程

public class UseThreadDemo {

    public static void main(String[] args) {
        Thread mt = Thread.currentThread();
        System.out.println("运行main方法的线程为:"+mt);
        
        Thread t = new Thread(){
            public void run(){
                Thread myT = Thread.currentThread();
                System.out.println("自定义的线程为:"+myT);
                doSome();
            }
        };
        t.start();
    }
    
    public static void doSome(){
        Thread t = Thread.currentThread();
        //指定线程运行
        if(!t.toString().equals("Thread[Thread-0,5,main]")){
            return;
        }
        
        System.out.println("运行doSome方法的线程为:"+t);
    }
    
    
}

2.获取线程的信息操作

public class ThreadInfoDemo {
    public static void main(String[] args) {
        Thread t = Thread.currentThread();
        //获取id
        System.out.println("线程id:"+t.getId());
        //获取名字
        System.out.println("线程名字:"+t.getName());
        //获取优先级
        System.out.println("线程的优先级为:"+t.getPriority());
        //是否是守护线程
        System.out.println("是否是守护线程:"+t.isDaemon());
        //是否被中断
        System.out.println("是否被中断:"+t.isInterrupted());
        
    }
}

3.线程的优先级

每个线程都具有各自的优先级,代表在程序中该线程的重要性,当多个线程处于就绪状态时,优先级高的程序等到cpu时间片的几率就高,

也就是运行的几率会大些。

线程的优先级分10个等级,1-最低,5-默认,10-最高。

MIN_PRIORITY: 1 对应最低优先级

MAX_PRIORITY:  10 对应最高优先级

NORM_PRIORITY: 5 默认优先级

public class ThreadPriorityDemo {
    public static void main(String[] args) {
        Thread max = new Thread(){
            public void run(){
                for (int i = 0; i <1000; i++) {
                    System.out.println("我是优先级最高的max");
                }
            }
        };
        
        Thread min = new Thread(){
            public void run(){
                for (int i = 0; i <1000; i++) {
                    System.out.println("我是优先级最低的min");
                }
            }
        };
        
        Thread norm = new Thread(){
            public void run(){
                for (int i = 0; i <1000; i++) {
                    System.out.println("我是优先级为默认的norm");
                }
            }
        };
        max.setPriority(10);
        min.setPriority(Thread.MIN_PRIORITY);
        //启动
        min.start();
        norm.start();
        max.start();
    
    }
    
}

4.线程的休眠

Thread类提供了静态方法 static void sleep(long ms)用于指定该线程的休眠时间,以毫秒计算。

当超时之后,线程会自动回到Runnable状态,等待再次分配时间片运行。(不代表超时之后立即运行,只能保证进入就绪的状态)

public class ThreadSleepDemo {
    public static void main(String[] args) {
        
        Thread t = new Thread(){
            public void run(){
                while(true){
                    SimpleDateFormat sdf =  new SimpleDateFormat("HH:mm:ss");
                    System.out.println(sdf.format(new Date()));
                    //休眠一秒
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    
                }
                
            }
        };
        
        t.start();
    }
}

5.守护线程使用

Java中有两类线程,User Thread-用户线程,也叫前台线程、Daemon Thread-守护线程,也称后台线程。

当进程中的所有User Thread结束了,则进程也就结束了,无论进程中的其他Daemon Thread是否正在运行,都会被牵制中断。

public class DaemonThreadDemo {
    public static void main(String[] args) {
        
        Thread t1 = new Thread(){
            public void run(){
                for (int i = 0; i < 10; i++) {
                    System.out.println("我是User Thread!");
                }
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println("User Thraed 运行完了");
            }
            
        };
        
        
        Thread t2 = new Thread(){
            public void run(){
                while(true){
                    System.out.println("我是Daemon Thread");
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }
        };
        
        t2.setDaemon(true);
        
        t1.start();
        t2.start();
        System.out.println("main 方法运行完成");
        //while(true);  // 看看加上之后的效果
        
    }
}

6.线程的加入

Thread中join()方法能够允许当前线程在另一个线程上等待,直到该线程结束工作。

通常用来协调两个线程工作时使用。比如Thread A里面插入了线程B,则线程A会等待线程B执行完成之后再继续执行下去。

public class ThreadJoinDemo {
    public static void main(String[] args) {
        
        Thread threadA = new Thread(){
            public void run(){
                System.out.println("thread A 开始计数:");
                for (int i = 0; i < 100; i++) {
                    System.out.println("Thread A:"+i);
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
                System.out.println("Thread A 计数完毕!");
            }
            
        };
        
        Thread threadB = new Thread(){
            public void run(){
                System.out.println("我要等Thread A 计数完成我再开始,好气哦!");
                try {
                    //等待threadA执行完毕在向下执行
                    threadA.join();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                for (int i = 101; i < 200; i++) {
                    System.out.println("Thread B:"+i);
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
                System.out.println("Thread B 计数完毕!");
            }
        };
        
        threadA.start();
        threadB.start();
        
    }
}

7.线程的礼让

Thread 提供了yield()方法,给正在运行的线程提醒,告知它可以将资源礼让给其他线程,但这只是一种暗示,不能保证当前线程一定

会将资源让出来。可以用来模拟线程之间的切换。

4.线程的同步

在多线程的任务中,如果多个线程操作同一个资源时,由于线程切换的不确定性,可能会导致逻辑出现混乱,严重

时可能导致系统崩溃,业务逻辑出现错误等情况。

java中使用synchronized 关键字来确保线程使用资源时能够同步进行。该关键字有两种用法:

1.修饰方法:这样的话,该方法就称为“同步方法”,多个线程就不能同时进入到方法内部去执行,可以避免由于线程切换不确定,导致的逻辑错误。

2.synchronized块:可以将某段代码片段括起来,多个线程不能同时执行里面的代码。

 

public class SyncDemo {
    
    public static void main(String[] args) {
        final Table t= new Table();
        Thread t1 = new Thread(){
            public void run(){
                while(true){
                    int bean =t.getBeans();
                    Thread.yield();//模拟线程切换
                    System.out.println(getName()+" "+bean);
                }
            }
        };
        Thread t2 = new Thread(){
            public void run(){
                while(true){
                    int bean=t.getBeans();
                    Thread.yield();//模拟线程切换
                    System.out.println(getName()+" "+bean);
                }
                
            }
        };
        t1.start();
        t2.start();
    }
    
}
class Table{
    private int beans =20;
    /**
     * synchronized 关键字
     * 该关键字有两个用法:
     * 1:修饰方法,这样的话,该方法就称为“同步方法”
     *       多个线程就不能同时进入到方法内部去执行,可以避免由于线程切换不确定,导致的逻辑错误。
     * 2:synchronized块,可以将某段代码片段括起来,多个线程不能同时执行里面的代码。
     * @return
     */
    public  synchronized int getBeans(){
        if(beans==0){
            throw new RuntimeException("没有豆子了");    
        }
        Thread.yield();//模拟线程切换
        return beans--;
    }
}
/**
 * 使用synchronized 块的意义
 * 有效的缩小同步范围,可以在保证安全的前提下提高并发效率。
 *
 */
public class SyncDemo02 {
    public static void main(String[] args) {
        final Shop shop  = new Shop();
        Thread t1 = new Thread(){
            public void run(){
                shop.buy();
            }
        };
        
        Thread t2 = new Thread(){
            public void run(){
                shop.buy();
            }
        };
        
        t1.start();
        t2.start();
    }
}

class Shop{
    public void buy(){
        try {
            Thread t = Thread.currentThread();
            System.out.println(t+"正在挑衣服~");
            Thread.sleep(5000);
            /**
             * synchronized 块
             * 若想使用同步块达到同步效果,必须保证多个线程看到的“同步监视器”(上锁的对象)
             * 是同一个才有效果!
             * 通常使用this就可以了。
             * 
             * synchronized 若修饰的是方法,也是有上锁的对象,该对象就是当前方法所属的对象,也就是this。
             */
            synchronized (this) {
                System.out.println(t+"正在试衣服");
                Thread.sleep(5000);
            }
            System.out.println(t+"结账离开了");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
/**
 * synchronized 也可以达到代码间的互斥
 * 两个同步块括起来不同的两段代码,但是只要上锁的对象相同,那么这两段
 * 代码间就存在互斥效果。
 * @author lenovo
 *
 */
public class SyncDemo04 {
    public static void main(String[] args) {
        final Foo foo = new Foo();
        Thread t1= new Thread(){
            public void run(){
                foo.methoA();
            }
        };
        Thread t2= new Thread(){
            public void run(){
                foo.methoB();
            }
        };
        t1.start();
        t2.start();
    }
}
class Foo{
    public synchronized void methoA(){
        Thread t =     Thread.currentThread();    
        System.out.println(t+"正在调用A方法");
        try {
            t.sleep(5000);
        } catch (Exception e) {
        
        }    
        System.out.println(t+"将方法A执行完毕");    
            
        
    }
    public synchronized void methoB(){
        Thread t =     Thread.currentThread();    
        System.out.println(t+"正在调用B方法");
        try {
            t.sleep(5000);
        } catch (Exception e) {
        
        }    
        System.out.println(t+"将方法B执行完毕");    
            
        
    }
}

wait()和notify()方法使用

/**
 * Object 类中定义了两个方法wait(),notify()
 * 他们也可以实现协调线程之间同步工作的方法。
 * 
 * 当一个线程调用了某个对象的wait方法时,这个线程就进入阻塞状态,直到这个对象的notify方法
 * 被调用,这个线程才会解除wait阻塞,继续向下执行代码。
 * 
 * 若多个线程在同一对象上调用wait方法进行阻塞状态后,那么当该对象的notify方法被调用时,会随机解除一个线程
 * 的wait阻塞,这个不可控。
 * 若希望一次性将所有线程的wait阻塞解除,可以调用notifyAll方法。
 * @author lenovo
 */
public class WaitAndNotifyDemo {
    /**
     * 局部内部类(方法内的)无调用方法外的局部变量,必须为final
     * 下面isfinish的被调用,因此写在main方法外,成为成员变量,类的属性、
     */
    private static boolean isFinish=false;
    private static Object obj = new Object();
    public static void main(String[] args) {
        final Thread download = new Thread(){
            public void run(){
                System.out.println("down:开始下载图片");
                for(int i=0;i<=100;i++){
                    System.out.println("down:开始下载:"+i+"%");
                    try {
                        Thread.sleep(50);
                    } catch (Exception e) {
                        
                    }
                }
                System.out.println("down:图片下载完成");
                isFinish=true;
                //通知show线程开始工作
                synchronized(this){
                    this.notifyAll();
                }
                
                System.out.println("down:开始下载附件:");
                for(int i=0;i<=100;i++){
                    System.out.println("down:"+i+"%");
                    try {
                        Thread.sleep(50);
                    } catch (Exception e) {
                    
                    }
                }
                System.out.println("down:附件下载完毕!");
            }
        };
        
        Thread show = new Thread(){
            public void run(){
                System.out.println("show:开始显示图片");
                try {
                    synchronized(download){
                        download.wait();
                    }
                    
                    //download.join();
                } catch (Exception e) {
                
                }
                if(!isFinish){
                    throw new RuntimeException("图片未下载完成");
                }
                System.out.println("show:图片显示完成!");
            }
        };
    download.start();
    
    
    show.start();
    }
}

5.线程池的创建

线程池: 当我们的逻辑中出现了会频繁创建线程的情况时,就要考虑使用线程池来管理线程。 这可以解决创建过多线程导致的系统威胁。 线程池主要解决两个问题: 1:控制线程数量 2:重用线程

public class ThreadPoolDemo {
    public static void main(String[] args) {
        //创建固定大小的线程池
        ExecutorService pool = Executors.newFixedThreadPool(2);
        //指派任务
        for (int i = 0; i < 5; i++) {
            Runnable r = new Runnable() {
                public void run() {
                    Thread t = Thread.currentThread();
                    System.out.println("线程"+t+"在执行");
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    System.out.println("线程"+t+"执行完成");
                }
            };
            pool.execute(r);
            
        }
        
        System.out.println("线程池停止");
        pool.shutdown();
    } 
}

以上是关于Java 多线程总结的主要内容,如果未能解决你的问题,请参考以下文章

经验总结:Java高级工程师面试题-字节跳动,成功跳槽阿里!

学习java第19天个人总结

号称史上最全Java多线程与并发面试题总结—基础篇

java多线程总结

Java多线程-线程池的使用与线程总结(狂神说含代码)

第十周java学习总结