多线程并发执行可以提高程序的效率,同时完成多项工作。
多线程的应用场景:迅雷下载多个资源,服务器处理多个请求,qq多人视频。
并行需要多核cpu,并发不需要。

java程序的运行原理:
java命令会启动jvm虚拟机,等于启动了一个进程(应用程序)。该进程会启动一个主线程,主线程去调用入口类的main方法。
jvm至少启动了主线程和垃圾回收线程,所以是多线程的。

多线程程序实现的两种方式:
1.继承Thread类

public class ThreadTest2 extends Thread {
     private int threadCnt = 10;    @Override
     public void run() {
         while (true) {
             if (threadCnt > 0) {
                 System.out.println(Thread.currentThread().getName() + " 剩余个数 " + threadCnt);
                 threadCnt--;
                 try {
                     Thread.sleep(30);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             } else {
                 break;
             }
         }
     }    public static void main(String[] args) {
         new ThreadTest2().start();
         new ThreadTest2().start();
     }
 }
 2.实现runnable方法
 public class RunnalbleTest2 implements Runnable {
     private int threadCnt = 10;    @Override
     public void run() {
         while (true) {
             if (threadCnt > 0) {
                 System.out.println(Thread.currentThread().getName() + " 剩余个数 " + threadCnt);
                 threadCnt--;
                 try {
                     Thread.sleep(30);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             } else {
                 break;
             }
         }
     }    public static void main(String[] args) {
         RunnalbleTest2 runnalbleTest2 = new RunnalbleTest2();
         new Thread(runnalbleTest2).start();
         new Thread(runnalbleTest2).start();
         new Thread(runnalbleTest2).start();
         new Thread(runnalbleTest2).start();
     }
 }实现runnable的原理(查看源码可知):
 1.Thread类的构造函数传递了runnable接口的引用,将实现的类传入了构造方法当参数。
 2.构造方法调用init方法,用实现的类给成员变量target赋值。
 3.run方法中调用了实现类的(target的)run方法。
 4.thread的start方法调用了run方法。两种创建线程的方式的区别
 1.从代码的角度看,继承thread是调用start方法时直接找子类的run方法,实现runnable是start方法先调用thread的run方法,再通过父类引用指向子类的原理调用作为参数的子类的run方法。
 2.从使用的角度看,继承thread代码简单,但有父类就不能用了。实现runnable代码复杂,但有父类也没关系。匿名内部类实现多线程的两种方式(不需要创建类):
 public class Demo {
     public static void main(String[] args) {
         // 继承Thread类实现多线程
         new Thread("线程名字") {
             public void run() {
                 for (int i = 0; i < 100; i++) {
                     System.out.println(Thread.currentThread().getName() + "--" + i);
                 }
             }
         }.start();
         // 实现Runnable接口实现多线程
         new Thread(new Runnable() {            public void run() {
                 for (int i = 0; i < 100; i++) {
                     Thread.sleep(100)
                     System.out.println(Thread.currentThread().getName() + "--" + i);
                 }
             }
         }) {
         }.start();
     }
 }spark解决数据倾斜可以用加随机前后缀的方式,可是如此一来key就失去了意义,怎么办???7月23日 02:53
 spark是实时的,为什么shuffle的时候还会产生磁盘文件,而不是直接将单条或少数几条数据传送到下一个rdd?7月23日10:07
  1、实时处理也是需要在内存中计算数据的,内存不足的时候就内存溢出了。 所以实时处理 和 使用内存不冲突的
  2、修改key的情况是要根据业务设计的。  添加后缀是把原来一个分区的数据分到几个子分区处理,添加前缀会全部打散,这是一个解决问题的思路,具体问题还要具体分析7月23日 10:10
  3、内存不足的时候,spark中的数据是会刷到磁盘上面的,RDD的弹性主要说的就是这个特点匿名内部类多线程获取和设置线程名字:
 this.getName()
 this.setName()
 非匿名内部类:
 t.setName()
 t.start()获取当前线程对象:
 Thread.currentThread()休眠线程:
 Thread.sleep(毫秒值)守护线程:setDaemon() 设置一个线程为守护线程,该线程不会单独执行,当其他的非守护线程都执行完毕后,自动退出。
 t.setDaemon(true)
 t.start()加入线程:
 t.join()让t线程插队先执行
 t.join(100)让t线程插队先执行100ms再交替执行礼让线程:
 Thread.yield() 让出cpu(很多时候达不到效果)设置线程的优先级:
 t.setPriority()设置优先级,默认是5,最小是1,最大是10同步代码块:
 什么情况下需要同步:当多线程并发,有多段代码同时执行时,我们希望某一段代码执行的过程中cpu不要切换到其他的线程,这时就需要同步。
 如果两段代码是同步的,那么同一时间只能执行其中的一段。
 使用synchronized关键字加上一个锁对象来定义一段代码,就叫同步代码块。
 多个同步代码块如果使用相同的锁对象,那他们就是同步的。
 锁对象可以是任意对象,但是被锁的代码需要保证是同一把锁,因此不能使用匿名对象。同步方法:
 使用synchronized关键字修饰一个方法,该方法中的所有代码都是同步的。
 public synchronized void xxx
 非静态同步方法的锁对象是this
 静态同步方法的锁对象是该类的字节码对象懒汉式的单例多线程访问时有线程安全隐患,饿汉(开发时使用该方式)的没有。饿汉式是空间换时间。
单例模式的应用举例:RunTime类RunTime.getRunTime()返回当前程序的运行时对象
Timer类:计时器,在指定的时间做指定的事(原理就是开启一个线程)。
 Timer.schedule()public class MyTimerTask extends TimerTask {
  
     @Override
     public void run() {
         System.out.println("Timer task started at:"+new Date());
         System.out.println("Timer task finished at:"+new Date());
     }
     }TimerTask timerTask = new MyTimerTask();
 Timer timer = new Timer();
 timer.schedule(timerTask, 0, 10*1000);(run方法中的异常只能处理,因为父类不抛异常子类就不能抛异常)
两个线程之间的通信:wait()等待,notify()唤醒
 在同步中,锁设置为什么,就用什么调用wait和notify。假如多个方法使用同一个锁(同步代码块),那就随机唤醒他们中的一个
 wait方法和notify方法定义在了Object类里面,因为他是所有类的父类sleep和wait方法的区别:sleep必须传入参数,时间到了自动醒来。wait传不传参数都行,传入参数就是多长时间之后开始等待,不传参数就是立即等待;
 sleep方法在同步函数或代码块中不释放锁,wait释放锁jdk1.5的新特性互斥锁:
 同步:使用ReentrantLock的lock()和unlock()方法进行同步
 通信:使用ReentrantLock的newCondition()方法可以获取condition对象。
 等待用condition的await(),唤醒用signal()方法。
 不同的线程使用不同的condition,就能区分唤醒哪个线程了。线程组:(默认情况下都属于主线程组main)
 t.getThreadGroup().getName()获取某线程的线程组对象的名字Runnable方式指定线程的线程组和线程名字:
 ThreadGroup th = new ThreadGroup("线程组名");
 MyRunnable mr = new MyRunnable();
 Thread t = new Thread(th,mr,"线程名字");

线程的五种状态(生命周期):新建,就绪(有执行资格,没执行权),执行(有,有),阻塞(无,无),死亡。阻塞必须先变成就绪才能变成执行。

线程池:
 创建线程成本高,因为涉及到与操作系统进行交互。jdk5开始java内置线程池,之前需要手动实现。
 Executors工厂类生产线程池,方法有newFixedThreadPool(int),newSingleThreadExecutor(),这些方法返回ExecutorService对象,表示线程池,可以执行Runable对象或Callable对象代表的线程。
 他提供了submit(Runnable task)和submit(Callable<T> task)
使用线程池的步骤:创建线程池对象,创建runnable实例,提交runnable实例,关闭线程池shutdown()