1.线程是独立的执行路径,多线程就是指多条路径
2.线程一旦开启后是由操作系统的调度器安排调度的
3.创建线程:
(1)继承Thread类,重写run方法,.start
(2)实现runnable接口,重写run方法,new Thread,调用start
(3)实现Callable接口,重写call方法
4. Lambda:
如果是一个参数可以省略括号(两个参数以上不可以省略,但都可以省略参数类型),如果方法里只是一条语句,则可以省略花括号(两条以上不可以),如果只有一句return返回值,则可以省略return关键字,例如:
love = a-> System.out.println("I like Labmda1 --->" + a);
interest = (a,c) -> a+c;
5. 五大状态:
(1)新生状态:线程对象一旦创建就进入到了新生状态
(2)就绪状态:当调用start()方法,线程立即进入就绪状态,但是不意味着立即调度执行,runnable
a. 进入就绪状态的四种情况:
a1. start()
a2. 阻塞解除
a3. yield(避免一个线程占用cpu太多,中断),让当前正在执行的线程暂停,转为就绪
让cpu调度器重新调度
a4. jvm(jvm本身将本地线程切换到其他线程)
(3)运行状态:从就绪状态被cpu调用后进入运行状态,runnable
(4)阻塞状态:
a. 进入阻塞状态的四种情况:
a1. sleep(),抱着资源不给别人用,停止运行一段时间,TIME_WAITING
sleep指定当前线程阻塞的毫秒数
sleep存在异常InterruptedException
sleep可以模拟网络延时或者倒计时
a2. wait(),不占用资源 BLOCKED
a3. join,插队,合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞,TIME_WAITING
a4. 有些操作会进入阻塞,比如IO的read,Write BLOCKED
(5)死亡状态:代码执行完毕或者停止了线程,不推荐stop()/destory(),不安全,TERMINATE
线程停止方法:提供一个boolean类型的终止变量,当这个变量为False时,终止线程运行。
//1. 加入标识,标记线程体是否可以运行
private boolean flag = true;
//2. 关联标识,true--运行 false--停止
while (flag){
System.out.println(name+"-->"+(i++));
}
//3. 对外提供方法改变标识
public void terminate(){
this.flag = false;
}
//4. 线程终止
tt.terminate();
6. 优先级 Priority
* 1. NORM_PRIORITY 5 - 默认
* 2. MIN_PRIORITY 1
* 3. MAX_PRIORITY 10
* 不代表绝对的优先级顺序,只是获得调度的概率大小问题
* 概率高的可能会先执行,概率低的不代表不执行
//设置优先级要在启动之前
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(10);
t3.setPriority(Thread.MIN_PRIORITY);
t4.setPriority(1);
t1.start();
t2.start();
t3.start();
t4.start();
7. 线程分为用户线程和守护线程,JVM必须确保用户线程执行完毕才会停止,而不用等待守护线程执行完毕。默认都是用户线程,可以使用 .setDaemon(true); //将用户线程调整为守护线程,这样,jvm在执行完用户线程后就停止运行了,而不管守护线程是否运行完毕。
8.线程同步:形成等待池队列,形成锁机制(synchronized)
(1)同步方法
public d synchronized void method(int args) {}
若将一个大的方法声明为synchronized 将会大大影响效率
(2)同步块:
synchronized (obj){ },obj称之为同步监视器
• obj可以是任何对象,但是推荐使用共享资源作为同步监视器
• 同步方法中无需指定同步监视器,因为同步方法的同步监视器是this即该对象本身,或 class即类的模子
• 同步监视器的执行过程:
• 第一个线程访问,锁定同步监视器,执行其中代码
• 第二个线程访问,发现同步监视器被锁定,无法访问
• 第一个线程访问完毕,解锁同步监视器
• 第二个线程访问,发现同步监视器未锁,锁定并访问
• 尽可能锁定合理的范围(不是指代码,而是指数据的完整性)。synchronized锁定的对象应 该不变化的。
//尽可能锁定合理的范围(不是指代码,而是指数据的完整性)
//double checking
public void test5() {
if (ticketNums <= 0) { //考虑的是没有票的情况
flag = false;
return;
}
synchronized (this) {
if (ticketNums <= 0) { //考虑的是最后一张票的情况
flag = false;
return;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "--->" + ticketNums--);
}
}
9. 死锁:多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能进行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。
某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题。
避免死锁发生: * 不要在同一个代码块中,同时持有多个对象的锁
10. 线程协作(生产者消费者模式)
(1)在生产者消费者问题中,仅有synchronized是不够的
• synchronized可阻止并发更新同一个共享资源,实现了同步
• synchronized不能用来实现不同线程之间的消息传递(通信)
(2)线程通信
解决方式1: 管程法-----借助缓冲区
解决方式2: 信号灯法------借助标志位
(3)Java提供了3个方法解决线程之间的通信问题(均是java.lang.Object类的方法都只能在同步方法或者同步代码块中使用,否则会抛出异常)
11.知识扩展
(1)任务定时调度:通过Timer和Timetask,我们可以实现定时启动某个线程。
Timer timer = new Timer(); //执行安排 timer.schedule(new MyTask(),1000); //执行任务一次 timer.schedule(new MyTask(),1000,200); //1秒后每个200ms执行一次
(2)QUARTZ:
//1.创建Schedule的工厂 SchedulerFactory sf = new StdSchedulerFactory(); //2.从工厂中获取调度器 Scheduler sched = sf.getScheduler(); //3.创建JobDetail JobDetail job = newJob(HelloJob.class).withIdentity("job1", "group1").build(); //时间 Date runTime = evenSecondDateAfterNow(); //下一秒 // 4.触发器 //Trigger trigger = newTrigger().withIdentity("trigger1", "group1").startAt(runTime).build(); //下一秒执行一次 Trigger trigger = newTrigger().withIdentity("trigger1", "group1").startAt(runTime) .withSchedule(simpleSchedule().withIntervalInSeconds(5).withRepeatCount(4)).build(); //下一秒执行,然后每5秒执行一次,共执行4次 // 5. 注册任务和触发条件 sched.scheduleJob(job, trigger); //6. 启动 sched.start(); try { //100秒后停止 Thread.sleep(50L * 1000L); } catch (Exception e) { } sched.shutdown(true); } //HelloJob public class HelloJob implements Job { private static Logger _log = LoggerFactory.getLogger(HelloJob.class); public HelloJob() { } public void execute(JobExecutionContext context) throws JobExecutionException { // Say Hello to the World and display the date/time //_log.info("Hello World! - " + new Date()); System.out.println("-------------Start--------------"); System.out.println("Hello World! - " + new Date()); System.out.println("-------------End--------------"); } }
(3)HappenBefore(指令重排):代码执行顺序与预期不一致 , 如果数据和数据之间没有依赖,有可能会出现指令重排 ;目的:提高性能
(4)volatile:用于保证数据的同步也就是保证线程间变量的可见性,线程对变量进行修改之后,要立刻回写到主内存。
(5)单例模式(DCL模式):懒汉式套路 在多线程环境下,对外存在一个对象
* 1. 构造器私有化 -- 避免外部new构造器
* 2. 提供私有的静态属性---存储对象的地址
* 3. 提供公共的静态方法-- 获取属性
public class DoubleCheckedLocking {
public static void main(String[] args) {
//如果加了同步,对象的地址相同
// Thread t = new Thread(()->{
// System.out.println(DoubleCheckedLocking.getInstance());
// });
// t.start();
// System.out.println(DoubleCheckedLocking.getInstance());
//}
//如果不加锁,对象的地址有可能不相同
Thread t = new Thread(() -> {
System.out.println(DoubleCheckedLocking.getInstance1(500));
});
t.start();
System.out.println(DoubleCheckedLocking.getInstance1(1000));
}
// 2. 提供私有的静态属性---存储对象的地址
//避免重排,等对象创建好了再用,
// 如果没有volitile,有可能其他线程会访问到一个还没有进行初始化的对象
private static volatile DoubleCheckedLocking instance;
// 1. 构造器私有化 -- 避免外部new构造器
private DoubleCheckedLocking() {
}
// 3. 提供公共的静态方法-- 获取属性
public static DoubleCheckedLocking getInstance() {
//再次检测
if (null != instance) {
return instance; //避免不必要的同步,已经存在对象
}
synchronized (DoubleCheckedLocking.class) { //模子,class对象
if (null == instance) {
instance = new DoubleCheckedLocking();
//new 一个对象时,三步:
//1.开辟空间
//2.初始化对象信息
//3.返回对象的地址给引用
//所以在此期间有可能发生指令重排,所以要加volatile
}
return instance;
}
}
public static DoubleCheckedLocking getInstance1(long time) {
if (null == instance) {
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new DoubleCheckedLocking();
//new 一个对象时,三步:
//1.开辟空间
//2.初始化对象信息
//3.返回对象的地址给引用
//所以在此期间有可能发生指令重排,所以要加volatile
}
return instance;
}
}
(6)ThreadLocal: 每个线程自身的存储本地、局部区域,
(1)建议ThreadLocal定义为private static
(2)常用的方法:get/set/initialValue
public class ThreadLocalTest2 {
private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(()->1);
public static void main(String[] args){
for (int i =0;i<5;i++){
new Thread(new MyRun()).start(); //每个线程都会得到1,剩下0.彼此各不影响
}
}
public static class MyRun implements Runnable{
@Override
public void run() {
Integer left = threadLocal.get();
System.out.println(Thread.currentThread().getName()+"得到了-->"+left);
threadLocal.set(left-1);
left = threadLocal.get();
System.out.println(Thread.currentThread().getName()+"还剩下-->"+left);
}
}
}
(3)ThreadLocal 分析上下文环境 : 重点看起点
* 1. 构造器 : 哪里调用,就属于哪里 找线程体
* 2. run方法:本线程自身
public class ThreadLocalTest3 {
private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(()->1);
public static void main(String[] args) {
new Thread(new MyRun()).start(); //main
new Thread(new MyRun()).start();
}
public static class MyRun implements Runnable{
public MyRun(){
threadLocal.set(-100);
System.out.println(Thread.currentThread().getName()+"-->"+threadLocal.get()); //main
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-->"+threadLocal.get()); //另外的线程
}
}
}
运行结果:
main-->-100
main-->-100
Thread-0-->1
Thread-1-->1
(4)InheritableThreadLocal:继承上下文环境的数据 找起点,拷贝一份给子线程,子线程可以进行设置
public class ThreadLocalTest4 {
//private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>(); //mian为2,另一个线程为0
private static ThreadLocal<Integer> threadLocal = new InheritableThreadLocal<>();//mian为2,如果不设置值另一个线程也为2
public static void main(String[] args) {
threadLocal.set(2);
System.out.println(Thread.currentThread().getName()+"-->"+threadLocal.get());
//此线程由main开辟
new Thread(()->{
threadLocal.set(200);
System.out.println(Thread.currentThread().getName()+"-->"+threadLocal.get()); //子线程设置后为200
}).start();
}
}
(5)ReentrantLock(可重入锁) : 锁可以延续使用+每个锁里边有一个计数器
public class LockTest4 {
ReentrantLock lock = new ReentrantLock();
public void a() throws InterruptedException {
lock.lock();
System.out.println(lock.getHoldCount());
doSomeThing();
lock.unlock();
System.out.println(lock.getHoldCount());
}
//不可重入锁
public void doSomeThing() throws InterruptedException {
lock.lock();
System.out.println(lock.getHoldCount());
//........
lock.unlock();
System.out.println(lock.getHoldCount());
}
public static void main(String[] args) throws InterruptedException {
LockTest3 test = new LockTest3();
test.a(); //使用了锁没有释放
Thread.sleep(1000);
System.out.println(test.lock.getHoldCount());
}
}
(6)CAS:Compare and Swap 比较并交换:
(1)乐观锁的实现;
• 有三个值:一个当前内存值V、旧的预期值A、将更新的值B。先获取到内存当中当前的内存值V,再将内存值V和原值A作比较,要是相等就修改为要修改的值B并返 回 true,否则什么都不做,并返回false;
(2)CAS是一组原子操作,不会被外部打断;
(3)属于硬件级别的操作(利用CPU的CAS指令,同时借助JNI来完成的非阻塞算法),效率比加锁操作高。
public class CASTest {
//库存原子型操作
private static AtomicInteger stock = new AtomicInteger(5);
public static void main(String[] args){
for (int i =0;i<5;i++){
new Thread(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Integer left = stock.decrementAndGet(); //相减同时获取:CAS思想
if (left<1){
System.out.println("抢完了");
return;
}
System.out.print(Thread.currentThread().getName()+"抢了1件");
System.out.println("-->还剩"+left+"件");
}).start();
}
}
}