文章目录
线程创建
java无法真正开启线程,只能通过private native void start0(); 调用本地方法.让本地c++方法开启线程,因为java运行在虚拟机上无法直接操作硬件.
继承Thread类
继承Thread类,重写run方法,创建对象,调用start方法开启线程执行run方法.
start()方法开启的子线程不一定立即执行,由CPU决定调度那个线程
实现Runnable接口
外部类
public class TalkSend implements Runnable{
//xxx
@Override
public void run(){
// xxx
}
}
public class TalkReceive implements Runnable{
//xxx
@Override
public void run(){
// xxx
}
}
//开启两个线程
new Thread(new TalkSend(5555,"localhost",8888)).start;
new Thread(new TalkReceive(9999,"学生")).start;
内部类
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
}).start();
真实开发中,类不用实现接口,直接new线程将对象传入
//资源类不需要实现Runnable接口,重写run方法. 完成解耦
class Ticket{
//正常编写属性和方法
private int num =50;
public void sale(){
//.....
}
}
public static void main(String[] args){
Ticket tk = new Ticket();
//使用lambda表达式,启动线程
new Thread(()-> {tk.sale();},"线程A").start();
new Thread(()-> {tk.sale();},"线程B").start();
new Thread(()-> {tk.sale();},"线程C").start();
}
实现Callable接口
有返回值(和指定的泛型相同),可以抛出异常
- 实现callable接口(要指定泛型),
- 重写call方法.
- 因为Thread不能直接 接收Callable接口,所有要通过FutureTask适配类. FutureTask即可接收Callable接口,也可接收Runnable接口
- FutureTask的 get 方法获取Callable接口call方法的返回值.
可能会因为call方法没执行完而阻塞
注意: 开启了两个线程但因为结果会被缓存,所以call()方法只执行了一次.
Thread方法
//构造器
new Thread(Runnable接口实现类对象);
new Thread(Runnable接口实现类对象,线程名);
//非静态方法
start(); //创建线程
setPriority(int) //更改线程优先级
getPriority() //获取线程优先级
a.join() //等待a线程运行结束,再继续执行当前线程
isAlive() //检测线程是否处于活跃状态
interrupt() //中断线程,不推荐使用
getName() //获取线程名
//静态方法
Thread.currentThread() //获取当前的线程
Thread.sleep(200) //线程休眠200毫秒,不会释放锁
Thread.yield() //线程让出CPU,再共同竞争
线程状态Thread.State
线程优先级priority
优先级用数字表示1-10,数字越大优先级越高,main方法默认为5
注意: 先设置优先级再启动
守护线程daemon
- 虚拟机不需要等待守护线程执行完毕
- 虚拟机必须等待用户线程执行完毕
Thread thread=new Thread(god);
thread.setDaemon(true); //默认是false表示是用户线程,正常的线程都是用户线程.
停止线程
- 不推荐使用jdk提供的停止线程方法: stop destroy
- 推荐让线程自己停下来,使用一个标志位来停止线程,并对外提供一个修改标识的方法.
线程同步
synchronized
锁的范围
- 非静态方法: 每个对象都有一个锁(对象锁),记录在对象头的Mark Word中.
- 静态方法: 静态方法在类加载时就生成了,存在于.class字节码文件中,不管 new多少个对象都只有一个锁 (类锁,只锁静态方法,与对象锁互不干扰).
使用方式
- 同步方法: 在方法的访问权限修饰符后,使用synchronized修饰
- 同步代码块: synchronized (obj) { }
- obj为同步监视器,可以是任何对象,但推荐使用共享资源.同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this即对象本身.
注意:当使用String作为监视器时,如果两个字符串相同且后一个不是new 出来的,那么这两个字符串从同一个地址取值,可以看做同一个对象.锁也是共用的,可能导致死锁等问题.
synchronized 优化
- JVM在JDK 1.6中引入了分级锁机制来优化synchronized
- 当一个线程获取锁时,首先对象锁成为一个偏向锁
- 这是为了避免在同一线程重复获取同一把锁时,用户态和内核态频繁切换
- 如果有多个线程竞争锁资源,锁将会升级为轻量级锁
- 这适用于在短时间内持有锁,且分锁交替切换的场景
- 轻量级锁还结合了自旋锁来避免线程用户态与内核态的频繁切换
- 如果锁竞争太激烈(自旋锁失败),同步锁会升级为重量级锁
- 优化synchronized同步锁的关键:减少锁竞争
- 应该尽量使synchronized同步锁处于轻量级锁或偏向锁,这样才能提高synchronized同步锁的性能
- 常用手段
- 减少锁粒度:降低锁竞争
- 减少锁的持有时间,提高synchronized同步锁在自旋时获取锁资源的成功率,避免升级为重量级锁
- 在锁竞争激烈时,可以考虑禁用偏向锁和禁用自旋锁
LOCK
lock接口的实现类
- ReentrantLock 可重入锁
- 空参构造默认创建非公平锁(可插队),
- 也可传入一个Boolean值指定创建,true代表创建公平锁(先来后到)
- ReentrantReadWriteLock.ReadLock:读锁
- ReentrantReadWriteLock.WriteLock:写锁
private final ReentrantLock lock =new ReentrantLock();
//建议将unlock()写到finally语句块中
lock.lock(); //加锁
try{
//需要保证线程安全的代码
}finally{
lock.unlock(); //解锁
}
lock.tryLock() //尝试获取锁
Lock与synchronized的区别
- Lock是类由juc包提供,synchronized是关键字jvm实现
- Lock是显式锁(手动开启和关闭锁,别忘记关闭锁)synchronized是隐式锁,出了作用域自动释放
- Lock只有代码块锁,synchronized有代码块锁和方法锁
- Lock可以手动尝试获取锁,synchronized没有锁时只能等待
- Lock可以判断是否获取到了锁,synchronized不能判断
- Lock的定制性更高可设置为公平锁,synchronized则不行
Volatile
Volatile(关键字) 是 Java 虚拟机提供轻量级的同步机制,修饰在变量上,常用于单例模式
- 保证内存可见性 :使用Volatile修饰变量,使工作内存的变量副本可以感知到主内存变量的变化
- 不保证原子性 :使用atomic原子类或加锁来保证原子性
- 禁止指令重排(有序性): 使用内存屏障保证操作的执行顺序,常用在单例模式中.
指令重排
源代码–>编译器优化的重排–> 指令并行也可能会重排–> 内存系统也会重排—> 执行
处理器在进行指令重排的时候,会考虑单线程数据之间的依赖性!
但多线程时,原本无依赖的操作进行重排后可能导致出错
Volatile与synchronized的区别
- volatile本质是在告诉JVM当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
- volatile仅能使用在变量级别;synchronized则可以使用在方法和代码块级别的;
- volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性;
- volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
- volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。
线程协作通信
synchronized使用等待wait与唤醒notify
下面是Object类的同步监视器方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常llegalMonitorStateException
wait() //线程会一直等待,直到其他线程通知.会释放锁
wait(long) //等待指定的毫秒数
notify() //唤醒任意一个处于等待的线程
notifyAll() //唤醒同一个对象上所有调用wait()方法的线程
注意:
wait() 如果在 if 中执行,当被唤醒时不会重新判断if条件,而直接向下执行代码, 可能导致虚假唤醒.
wait() 如果在 while 中执行,被唤醒后会重新判断循环条件.如果不满足则仍然等待.
lock使用Condition接口中的方法
可以创建多个Condition同步监视器,实现精准唤醒
ReentrantLock lock =new ReentrantLock();
Condition condition = lock.newCondition();
condition.await(); //等待
condition.signalAll(); //唤醒全部线程
condition.signal(); //唤醒condition监视等待的线程,实现精确唤醒
生产者消费者模型
流程
判断是否等待->业务->唤醒,通知
判断等待要用while,而不能使用if,否则可能产生虚假唤醒.
管程法
生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据
信号灯法
通过标志位,决定生产者执行还是消费者执行