目录
程序:指令的集合,是静态的
进程:程序执行后的动态行式,是系统分配资源的基本单元
线程:一个进程至少有一个线程,是CPU调度执行的基本单位
多线程:真多线程是指多个CPU同时服务(并行),伪多线程是指一个CPU的多次切换模拟出的效果(并发)
线程创建
三种方法:
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口
其实:Thread类相当于Runnable接口的静态代理,将Runnable接口的实现类传入,然后生成代理对象,去做一些事情,事情怎么做就在start函数中。
继承Thread类
步骤:继承Thread–重写run–调用start方法
该方式过于简单,懒得写过程了
注意:线程执行顺序,无法确定!
实现Runnable接口
步骤:实现Runable–new Thread(实现类)–调用new出来的对象的start方法
该方式过于简单,懒得写过程了
注意:推荐使用该方法,可以避免单继承,同时方便同一个对象创建多个线程
由此产生一个问题:线程不安全。
线程不安全是因为多个线程操作同一个资源导致的。
线程之间的资源共享
静态变量:由于是类所有的,存在于方法区,所有该类实例都能看到该变量,都可以修改,所以线程不安全
实例变量:如果多线程共享一个实例则等同于静态变量的情况,线程不安全。如果每个线程都有一个类的实例,则实例变量互相隔离,线程安全
局部变量:局部变量是线程安全的
实现Callable接口
步骤:★★★
- 实现Callable接口,带上返回值
- 重写call方法,需要抛出异常
- 创建目标对象
- 开启服务
- 提交执行
- 获取结果
- 关闭服务
参考demo
public class Demo01 implements Callable<Integer> {
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<Integer> f1 = executorService.submit(d1);
Future<Integer> f2 = executorService.submit(d2);
Future<Integer> 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);
}
}
Lambda表达式
避免匿名内部类定义过多,属于函数式编程
函数式接口
定义:任何接口,如果只包含一个方法且为抽象方法,那他就是函数式接口(Functional Interface,Java8)
public interface Abc(){
public abstract void go()
}
对于函数式接口,可以用lambda创建该接口对象
Abc abc = ()->{//实现};
abc.go();
注意:如果带参数:单参数可以不写参数括号。实现语句如果是一句,可以不写花括号。入参类型可以省略。
线程状态
注意点:就绪和运行状态是可以互相转化的取决于【CPU资源是否释放和获得】,线程一旦结束不能再被启动!
线程常用方法:
线程停止
注意:不推荐JDK提供的线程停止方法:stop、destory,推荐使用自定义标志位来作为停止的标识
线程休眠
sleep:
- 参数是毫秒数
- 会抛出InterruptedException
- 结束后恢复就绪状态
- 不释放锁!!
线程礼让
让线程暂停,直接回到就绪状态,不会释放锁!!
Join插队
thread.join的含义是当前线程需要等待previousThread线程终止之后才从thread.join返回。简单来说,就是线程没有执行完之前,会一直阻塞在join方法处。
线程状态
Thread.State
- NEW状态:没有启动的线程
- RUNNABLE:运行态
- BLOCKED:阻塞态
- WATING:等待另一个线程执行工作
- TIMED_WATING:等待另一个线程执行工作到指定时间
- TERMINATED:退出的线程
线程优先级
Java提供给线程调度器来使用,优先调用优先级高的,但是否一定会调用是不确定的。主线程优先级是默认的5
优先级范围:1-10
其中:
Thread.MIN_PRIORITY=1
Thread.MAX_PRIORITY=10
Thread.NORM_PRIORITY=5
使用方法:getPriority() setPriority(int xx)
注意:优先级超过1-10范围会报错,优先级设置不能超过当前线程所属组的最大线程优先级
如果对于性能要求比较高则可以设置线程优先级防止出现性能倒置。
守护线程Daemon
我们创建的线程都是用户线程,除非设置了为守护线程
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
同步块
同步块锁定的是一个对象,该对象称为同步监视器,推荐使用共享资源对象作为锁,比如要修改的那个对象。
JUC
java util concurrent 并发包,专门用来做并发编程的
CopyOnWriteArrayList 线程安全的ArrayList,使用volatile和transient来修饰,限制了内存可见性和指令重排
死锁
多个线程各子占有一部分资源(锁)又等待去占有其他资源(锁)时,容易出现,通俗点讲就是,想同时占有多个锁
解决方法:获取对方线程可能占有的锁时,释放掉自己的锁
Lock锁
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提供了线程池相关接口
Executors:线程池工具类
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");
}
}