Java多线程总结

1.多线程开发又叫JUC开发(java.util.concurrent),至少5年以上才能涉及到,面试阿里常问

2.线程和进程有什么区别?

1)进程是资源(CUP,内存)分配的最小单位,线程是程序执行的最小单位。

2)一个程序至少有一个进程,一个进程至少有一个线程。

3.多线程的特点

三高:高并发,高可用,高性能。

4.线程核心概念

1)线程就是独立的执行路径。

2)在程序执行时,即使没有自己创建线程,后台也会存在多个线程,如GC线程,主线程。

3)main()称之为主线程,为系统的入口点,用于执行整个程序。

4)在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的。

5)对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制。

6)线程会带来额外的开销,如CUP调度时间,并发控制开销。

7)每个线程在自己的工作内存交互,加载和存储主内存控制不当会造成数据不一致。

5.Java中哪个方法必须在方法内部处理异常并且不能抛出?

多线程实现方法中,继承Thread的Run()方法是不能进行方法内部处理异常并且不能抛出的。

5.多线程的实现方式

1)继承Thread

public class MyThread extends Thread{
    
    @Override
    public void run() {
        for (int i = 1; i <= 50; i++) {
            System.out.println(Thread.currentThread().getName() + "---" + i);
        }
    }

}
public class Test {

    public static void main(String[] args) {
        MyThread m1 = new MyThread();
        MyThread m2 = new MyThread();
        m1.start();
        m2.start();
    }

}    

2)实现Runnable接口(推荐使用)

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

}
public class Test {
public static void main(String[] args) { MyThread m = new MyThread(); Thread t1 = new Thread(m); Thread t2 = new Thread(m); t1.start(); t2.start(); }
}

3)实现Callable接口

<1>

/**
 * 实现Callable接口.相较于实现Runnable接口的方式,方法可以有返回值,并且可以抛出异常
 * 执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。  FutureTask 是  Future 接口的实现类
 * @author 奇
 *
 */
public class ThreadDemo implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 0; i < 50; i++) {
            sum += i; 
        }
        return sum;
    }

}
public class TestCallable {
public static void main(String[] args) { ThreadDemo td = new ThreadDemo(); //1.执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。 FutureTask<Integer> result = new FutureTask<Integer>(td); Thread t = new Thread(result); t.start(); //2.接收线程运算后的结果 try { //FutureTask 可用于 闭锁 类似于CountDownLatch的作用,在所有的线程没有执行完成之后这里是不会执行的 Integer sum = result.get(); System.out.println(sum); System.out.println("------------------------------------"); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } }
}

<2>(面试中推荐说这个)

/**
 * 实现Callable接口,他有泛型
 * 实现Callable的call()方法
 * call()方法与run()方法的区别:有返回值,可以对异常类型进行抛出(throws)
 * run方法没有返回值,遇到异常必须try-catch处理
 * @author 奇
 *
 */
public class Test04 implements Callable<Integer> {
    
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 0; i < 50; i++) {
            sum += i; 
        }
        return sum;
    }
    
    /**
     * 1.创建服务(线程池,里面放一个线程)
     * 2.提交线程
     * 3.获得结果对象
     * 4.通过结果对象的get方法来获得结果
     * 5.关闭服务
     * @param args
     * @throws InterruptedException
     * @throws ExecutionException
     */
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        //与线程池有关
        Test04 t = new Test04();
        //创建一个线程池,也就是创建一个服务,线程池里面放了1个线程
        ExecutorService ser = Executors.newScheduledThreadPool(1);
        //提交执行
        //提交一个返回值的任务用于执行,返回一个表示任务的计算结果
        //Future表示异步计算结果,只能用get方法get()来拿到这个结果
        Future<Integer> res = ser.submit(t);
        //获得结果
        Integer r1 = res.get();
        System.out.println(r1);
        //关闭服务操作
        ser.shutdownNow();
    }

}

6.线程中常用的方法

1)获取线程名称:Thread.currentThread().getName();

2)设置线程名称:

<1>Thread.currentThread().setName("要设置的名字");

<2>实现Runnable()接口的时候new Thread(new StrartThread() , "要设置的名字").start();

7.使用start()方法会立即启动线程吗?

不会,他会通知CPU,CPU进行调度,说的专业点就叫时间片。

8.多线程——静态代理设计模式

1)我们实现的线程的Thread类等其实就是静态代理模式

2)代理模式分为:<1>静态代理<2>动态代理

3)所谓静态代理模式就是别人写好的我直接拿过类用就可以了

4)所谓动态代理模式就是动态生成类,然后在去找这个类就可以了

5)演示:

/**
 * 静态代理模式
 * 1.真实角色
 * 2.代理角色
 * 3.无论是真实角色和代理角色都应该实现同一个接口才能代理行为
 * @author 奇
 *
 */
public class StaticProxy {
    
    public static void main(String[] args) {
        new HunQingGongSi(new You()).happyMarry();
        //这里和new一个线程操作类似
        //比如
        //new Thread("线程1").start();     //也就说明了线程其实就是静态代理模式
    } 
    
}

/**
 * 结婚接口
 */
interface Marry{
    
    void happyMarry();
    
}

/**
 * 真实角色
 */
class You implements Marry{

    @Override
    public void happyMarry() {
        System.out.println("你和小小结婚了!");
    }
    
}

/**
 * 代理角色--婚庆公司
 */

class HunQingGongSi implements Marry{

    //他要代理谁结婚
    private Marry target; //创建谁来结婚的引用
    
    public HunQingGongSi(Marry target) {
        this.target = target;
    }

    //结婚前婚庆公司做的事儿
    private void before(){
        System.out.println("布置场景!");
    }
    
    //结婚后婚庆公司做的事儿
    private void after(){
        System.out.println("拆除场景!");
    }
    
    @Override
    public void happyMarry() {
        before();
        this.target.happyMarry();
        after();
    }

}

9.多线程的状态

1)有五种:新生,就绪,运行,阻塞,死亡。

2)新生:线程一旦被创建就进入了新生状态。

3)就绪:当调用start()方法后线程进入就绪状态。

4)运行:运行。

5)阻塞:线程进入阻塞状态后不能进入运行状态,一定是就绪状态,然后CPU进行再次调度才可以进入运行状态(面试中常问!!!)。

6)死亡:如果该线程进入死亡状态将不能再次开启该线程。

7)图示:

10.多线程的方法

1)sleep():使当前线程停止一段时间,该线程将处于阻塞状态。但是不会释放当前线程的锁(面试中常问!!!)。

2)join():阻塞线程等到另外一个线程执行完以后再继续执行。

3)yield():多线程礼让。让当前线程暂停(不是终止,也不是阻塞线程),而是将当前线程转入就绪状态。调用了yiele()方法之后,如果没有其他等待的执行线程,此时当前线程就会马上恢复执行。

4)setDaemon():守护线程,可以将指定的线程设置成后台线程。创建线程结束时,后台线程也随之消亡(不是死循环状态的,如果是死循环,是消亡不了的)。只能在线程启动之前把她设置为后台线程。

5)stop()/destroy():线程的停止,不推荐使用,因为不考虑人们的感受。

6)循环中判断之后用break:也是线程的停止,推荐使用,因为它考虑人们的感受。

7)图示:

11.对线程的状态做深度观察

1)进入就绪状态会有几种情况:

<1>线程调用start()。

<2>sleep()/join()/IO等方法执行完毕后。

<3>使用yield()。

<4>JVM内部算法的自动切换。

2)进入阻塞状态

<1>sleep()

<2>join()

<3>IO()

<4>wait()

<5>锁

12.线程有几种

1)线程分为用户线程和守护线程。虚拟机一定要确保用户线程执行完毕。虚拟机不用等守护线程执行完毕。用在如后台日志操作,监控内存(GC线程)等。

2)守护线程:

/**
 * 守护线程
 *线程分为用户线程和守护线程
 *虚拟机一定要确保用户线程执行完毕
 *虚拟机不用等守护线程执行完毕
 *用在,如后台日志操作,监控内存等
 * @author java
 *
 */
public class DeamonTest {

    public static void main(String[] args) {
        You you = new You();
        God god = new God();
        god.setDaemon(true);
        
        new Thread(you, "帅帅").start();
        new Thread(god, "上帝").start();
        
    }

}

class You extends Thread {

    @Override
    public void run() {
        for(int i = 1; i <= 365*100; i++) {
            System.out.println(Thread.currentThread().getName() + "快乐的活着......");
        }
        System.out.println("ooooooooo");
    }

}

class God extends Thread{

    @Override
    public void run() {
        for(int i = 1; i <= 365 * 100000; i++) {
            System.out.println(Thread.currentThread().getName() + "上帝一直在你身边守护着你。。。。。。");
        }
        System.out.println("hahahahahaha");
    }

}

13.线程的同步

1)有两种方法:<1>锁<2>队列(属于JUC的高级开发)。

2)如果synchronized锁的是普通方法,那么它锁的是this(当前对象)。

3)如果synchronized锁的是静态方法,那么它锁的是.class文件。

4)如果使用synchronized锁的位置太大的话,就会影响性能。比如说方法中有查询属性和修改属性,那么查询属性是不会出现并发问题,一般增删改会出现并发问题,那么如果整个方法都使用synchronized锁上了,那么查询也会受到影响。

5)synchronized锁:

public class Web12306 implements Runnable{

    private int tickerNums = 10;
    boolean flag = true;

    @Override
    public void run() {
        while(flag) {
            test();
        }
    }
     
    public synchronized void test() {
        if(tickerNums <= 0) {
            flag = false;
            return;   //退出该方法
        }
        try {
            Thread.sleep(110);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "--->" + tickerNums--);
    }
    
    public static void main(String[] args) {
        Web12306 web = new Web12306();
        System.out.println(Thread.currentThread().getName());
        new Thread(web, "码农").start();
        new Thread(web, "码黄").start();
        new Thread(web, "码蓝").start();
    }

}

6)同步方法和同步代码块的区别:同步方法比同步代码块的范围大,一般同步的范围越大,性能就越差。一般需要加锁进行同步的时候,肯定是范围越小越好,这样性能更好。

7)lock和synchronized的同步区别与选择

<1>lock是一个接口,而synchronized是java的一个关键字,synchronized是内置的语言实现;(具体实现上的区别在《Java虚拟机》中有讲解底层的CAS不同,以前有读过现在又遗忘了)。

<2>synchronized在发生异常时候会自动释放占有的锁,因此不会出现死锁;而lock发生异常时候,不会主动释放占有的锁,必须手动unlock来释放锁,可能引起死锁的发生。(所以最好将同步代码块用try catch包起来,finally中写入unlock,避免死锁的发生)。

<3>lock等待锁过程中可以用interrupt来终端等待,而synchronized只能等待锁的释放,不能响应中断。

<4>lock可以通过trylock来知道有没有获取锁,而synchronized不能。

<5>Lock可以提高多个线程进行读操作的效率。(可以通过readwritelock实现读写分离)。

原文地址:https://www.cnblogs.com/chuanqi1995/p/10127130.html