本文共 7197 字,大约阅读时间需要 23 分钟。
多线程:真多线程是指多个CPU同时服务(并行),伪多线程是指一个CPU的多次切换模拟出的效果(并发)
三种方法:
其实:Thread类相当于Runnable接口的静态代理,将Runnable接口的实现类传入,然后生成代理对象,去做一些事情,事情怎么做就在start函数中。
步骤:继承Thread–重写run–调用start方法
该方式过于简单,懒得写过程了 注意:线程执行顺序,无法确定!步骤:实现Runable–new Thread(实现类)–调用new出来的对象的start方法
该方式过于简单,懒得写过程了 注意:推荐使用该方法,可以避免单继承,同时方便同一个对象创建多个线程 由此产生一个问题:线程不安全。 线程不安全是因为多个线程操作同一个资源导致的。线程之间的资源共享
静态变量:由于是类所有的,存在于方法区,所有该类实例都能看到该变量,都可以修改,所以线程不安全 实例变量:如果多线程共享一个实例则等同于静态变量的情况,线程不安全。如果每个线程都有一个类的实例,则实例变量互相隔离,线程安全 局部变量:局部变量是线程安全的
步骤:★★★
参考demo
public class Demo01 implements Callable{ private int num; @Override public Integer call() throws Exception { int x; while(6 != (x=(int)(Math.random()*10))){ num++;// System.out.println(Thread.currentThread().getId()+":running,x="+x); } return num+1; } public static void main(String[] args) throws ExecutionException, InterruptedException { // 创建实例 Demo01 d1 = new Demo01(); Demo01 d2 = new Demo01(); Demo01 d3 = new Demo01(); // 创建线程池 ExecutorService executorService = Executors.newFixedThreadPool(3); Future f1 = executorService.submit(d1); Future f2 = executorService.submit(d2); Future f3 = executorService.submit(d3); // 获取值 Integer r1 = f1.get(); Integer r2 = f2.get(); Integer r3 = f3.get(); //关闭线程池 executorService.shutdown(); System.out.println("r1="+r1+",r2="+r2+",r3="+r3); }}
避免匿名内部类定义过多,属于函数式编程
定义:任何接口,如果只包含一个方法且为抽象方法,那他就是函数式接口(Functional Interface,Java8)
public interface Abc(){ public abstract void go()}
对于函数式接口,可以用lambda创建该接口对象
Abc abc = ()->{ //实现};abc.go();
注意:如果带参数:单参数可以不写参数括号。实现语句如果是一句,可以不写花括号。入参类型可以省略。
注意:不推荐JDK提供的线程停止方法:stop、destory,推荐使用自定义标志位来作为停止的标识
sleep:
让线程暂停,直接回到就绪状态,不会释放锁!!
thread.join的含义是当前线程需要等待previousThread线程终止之后才从thread.join返回。简单来说,就是线程没有执行完之前,会一直阻塞在join方法处。
Thread.State
Java提供给线程调度器来使用,优先调用优先级高的,但是否一定会调用是不确定的。主线程优先级是默认的5
优先级范围:1-10 其中: Thread.MIN_PRIORITY=1 Thread.MAX_PRIORITY=10 Thread.NORM_PRIORITY=5使用方法:getPriority() setPriority(int xx)
注意:优先级超过1-10范围会报错,优先级设置不能超过当前线程所属组的最大线程优先级 如果对于性能要求比较高则可以设置线程优先级防止出现性能倒置。我们创建的线程都是用户线程,除非设置了为守护线程
thread.setDaemon(true)
必须要在start前设置 在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程)
用个比较通俗的比如,任何一个守护线程都是整个JVM中所有非守护线程的保姆:
只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。 Daemon的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是 GC (垃圾回收器),它就是一个很称职的守护者。User和Daemon两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果 User Thread已经全部退出运行了,只剩下Daemon Thread存在了,虚拟机也就退出了。 因为没有了被守护者,Daemon也就没有工作可做了,也就没有继续运行程序的必要了。
队列+锁:
同步方法:在方法上加sychronized关键字,同时只能有一个线程在该方法内执行:pubic synchronized void a(){}
synchronized(obj){}
一般而言,只在需要修改的部分加锁,防止性能浪费
在方法上加锁,锁的是调用该方法的对象,this
同步块锁定的是一个对象,该对象称为同步监视器,推荐使用共享资源对象作为锁,比如要修改的那个对象。
java util concurrent 并发包,专门用来做并发编程的
CopyOnWriteArrayList 线程安全的ArrayList,使用volatile和transient来修饰,限制了内存可见性和指令重排多个线程各子占有一部分资源(锁)又等待去占有其他资源(锁)时,容易出现,通俗点讲就是,想同时占有多个锁
解决方法:获取对方线程可能占有的锁时,释放掉自己的锁JDK5开始,显示定义同步锁对象,使用Lock对象充当,JUC下的包,用于控制多个线程对共享资源进行访问
Lock是一个接口,ReentrantLock是其一个实现类,可重入锁
使用示例:package com.xiaopi3;import java.util.concurrent.locks.ReentrantLock;public class Demo01 { public static void main(String[] args) { Test t = new Test(); new Thread(t).start(); new Thread(t).start(); }}class Test implements Runnable{ private int num=10; /** * static 保证new多个对象时,锁只有一份 * final 保证该锁不会被重新赋值 */ private static final ReentrantLock lock = new ReentrantLock(); @Override public void run() { while(true){ // 一定要在判断前加锁,要不然会出现同时判断成功的错误 lock.lock(); if(num>=0){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getId()+" : "+num--); }else{ lock.unlock(); break; } lock.unlock(); } }}
注意:一般而言,lock.unlock()
在try catch中需要写在finally中,也可以直接try finally中写unlock!(推荐)
Lock和synchronized区别
- 前者显示锁后者隐式锁
- 前者只能锁代码块,后者还可以锁方法
- 前者调度线程消耗少于后者,扩展性高
- 建议:Lock>同步代码块>同步方法
明确一个模型:生产者和消费者模型,两条线程进行通信
解决方式一:管程法 生产者:产出产品到缓冲区,缓冲区满则停产,不满则生产 消费者:从缓冲区消费,缓冲区空则停止,不空则消费 线程通信的方法:wait和notify 示例:package com.xiaopi3;public class Demo { public static void main(String[] args) { MyPool myPool = new MyPool(); new Producer(myPool).start(); new Consumer(myPool).start(); }}class MyPool{ private int[] pool = new int[10]; private int num=0; public synchronized void push(int x){ System.out.println("生产"); if(num==pool.length){ try { System.out.println("仓库满了,等待消费!"); wait();// 等待并释放MyPool的push上的锁 } catch (InterruptedException e) { e.printStackTrace(); } }else{ num++; System.out.println("存入,当前存量为:"+num); notifyAll();// 通知所有在MyPool的push上等待锁的线程 } } public synchronized void pop(){ System.out.println("消费"); if(num==0){ try { System.out.println("仓库空了,等待生产!"); wait(); } catch (InterruptedException e) { e.printStackTrace(); } }else{ num--; System.out.println("取出,当前存量:"+num); notifyAll(); } }}class Producer extends Thread{ private MyPool myPool; public Producer(MyPool o){ this.myPool=o; } @Override public void run() { for (int i = 0; i < 100; i++) { myPool.push(i); } }}class Consumer extends Thread{ private MyPool myPool; public Consumer(MyPool o){ this.myPool=o; } @Override public void run() { for (int i = 0; i < 100; i++) { myPool.pop(); } }}
解决方式二:信号灯法
生产者和消费者通过标志位来判断是否生产或者消费,原理是(借助第三方中介来作为判断人),信号灯法适用于生产者产出1个消费者消费1个的情况。这里讲一下思路:第三方负责生产和消费的具体实现,生产者负责调用第三方生产方法,消费者负责调用第三方消费方法,第三方设置了标志位,生产还是消费取决于标志位。
JDK5提供了线程池相关接口
public class Demo { public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(10); Test test = new Test(); executorService.execute(test); executorService.execute(test); executorService.execute(test); executorService.shutdown(); }}class Test implements Runnable{ @Override public void run() { System.out.println("123"); }}
转载地址:http://aamnz.baihongyu.com/