1.每个进程都拥有自己的一整套变量,线程共享数据。
2.共享变量使得线程之间通信更有效、容易。
3.关于Runnable函数式接口的问题参考我的博客:https://www.cnblogs.com/cckong/p/14264821.html
4.直接调用run方法只会在一个线程执行,是同步的,start方法是异步的。
我们知道Thread是实现Runnable接口的,但是调用start方法,是让线程进入了可执行态。如果调用run会当成普通方法调用。
5.线程状态(6个:新建、可运行、阻塞、等待、计时等待、终止)
这里是根据java Thread.state 官方表示的状态
在操作系统我们也有线程3种或者5种状态的说法
三种状态:就绪、运行、等待
五种状态:新建、就绪、运行、等待、退出
6.想要获取当前状态 使用getState方法
7.当你new Thread时进入新建状态了
8.可运行状态取决于CPU处理器是否运行你,有可能在运行也有可能不在。
9.阻塞或等待状态原因:需要的资源的锁没有释放
等待另一个线程通知出现了一个条件
方法有超时参数,线程会进入计时等待阶段。
10.终止状态:run方法自然退出,线程自然终止
没有捕获的异常使线程意外终止
11.中断线程:interrupt方法请求终止一个线程,(阻塞的线程无法请求终止),最好捕获InterruptedException
12.守护线程:setDaemon(true)转换为守护线程。
13.异常
检查型异常:其他所有
非检查型异常:派生于Error类 、RuntimeException类的异常。
14.run方法不能抛出任何检查型异常,但非检查型可能导致线程终止,或死亡。
15.针对上条,可以用处理未捕获异常的处理器,实现UncaughtExceptionHandler接口的类。
16.让线程调用setUncaughtExceptionHandler静态方法安装处理器。
17.线程优先级 可以用setPriority方法1-10(不推荐声明优先级)
18.竞态条件:两个或以上线程对于共享数据的修改会让对象破坏,需要要同步存取。
例子:修改数字三步:读出数据,修改数据,写回数据。
线程A读出、修改了,这时候线程B杀了进来,完成了修改三部曲,A继续第三步,这时候数字就已经不对了
19.Reentrant类:重入锁
从JDK5引入。下面来看一个实例
private ReentrantLock mylock=new ReentrantLock(); public void transfer(int a) { mylock.lock(); try { a++; } finally { mylock.unlock(); } }
unlock操作一定要放在finally里面执行,不然其他线程永久阻塞。
20.用条件对象(if/while)来管理获得一个锁但不能做有用工作的线程。
例子:线程A进入修改数字程序,发现数字低于多少值不能修改了。
21.await方法 线程暂停 并放弃锁
signalAll方法重新激活所有等待此条件的线程(通知现在有可能满足条件,线程需自己去检查满足条件与否)
22。只要一个对象的状态发生变化,就signalAll所有等待线程来检查条件。
23.signal()唤醒指定线程。
24.总结锁和条件对象:
锁保护代码片段,一次只有一个线程执行代码
一个锁可以有一个或多个条件对象
锁管理进入保护代码的线程
条件对象管理进入保护代码但还不能运行的线程
24.synchronized关键字:
可以作为synchronized方法 或者 synchronized代码块
从Java1.0开始,每个对象都有一个内部锁。这个锁有一个内部条件
如果一个方法声明时有synchronized关键字 对象的锁将保护整个方法代码
public synchronized void method() { //body }
等价于(intrinsic固有的)
public void method() { this.intrinsicLock.lock(); try{ //body } finally{ this.intrinsicLock.unlock(); } }
wait和notify的应用:
wait将线程添加到等待集里面
notify/notifyAll解除等待线程的阻塞
25.并发关键字使用推荐顺序:
(1)concurrent包里的机制,如阻塞队列
(2)synchronized关键字,减少代码量
(3)Lock/Condition
26.监视器monitor特型:
只包含私有字段的类
类中每个对象都有一个关联的锁
所有方法由这个锁锁定。
锁可以有任意多的相关条件。
27.监视器本质上就是os上的管程,https://www.cnblogs.com/noteless/p/10394054.html,这篇博客讲的非常清楚。
28.volatile关键字(英文翻译:不稳定的)提供免锁机制,编译器和jvm知道该字段有可能被另一个线程更新
29.
private boolean done; public synchronized boolean isDone(){ return done;} public synchronized void setDone(){done=false;}
同时调用两个方法,肯定会有一个加锁,另一个阻塞。
使用volatile关键字修改对其他所有线程可见。
private volatile boolean done; public boolean isDone(){ return done;} public void setDone(){done=false;}
30.假设对共享变量只有赋值的功能,可以使用volatile关键字。
31.stop(终止)不安全,suspend(阻塞)易导致死锁,resume(恢复suspend的线程):都试图控制一个线程的操作,而不是线程互操作。
stop:结束所有未结束的方法,包括run方法,立即释放对象的锁,导致对象处于不一致状态。(转账,钱取出来了,还没放进别人账户,就是释放了,不安全)
因为调用stop的线程不清楚 调用线程的状态!
suspend如果线程在拥有一个锁,然后被阻塞了,锁是不能释放的,会导致别的线程无法访问,导致死锁。
安全挂起线程:suspendRequested变量
32.并发的修改一个数据结构,很容易破环这个数据结构(链表的指针无效、混乱等)
33.可以并发的时候加锁来保证数据结构的安全,但不如选择线程安全的实现。
34.阻塞队列:
35.concurrentHashMap计数方法*如果映射太大 会超出int)
36..concurrentHashMap默认支持16个书写器,通过构造器可以更多。
37.
38.对于并发散列映射
search搜索:为每个键或者值应用一个函数,直到生成一个非null的结果
reduce规约:组合所有键或值
foreach:所有键或值应用一个函数。
39.线程池::包含很多准备运行的线程。
40.Callble和Runnable类似,封装一个call方法(函数式接口),但是有返回值,返回调用的类型。Callable<Integer> 就会返回Integer
public interface Callable<V> { V call() throws Exception; }
41.Future保存异步计算的结果,接口下的方法:
V get()调用后阻塞,直到计算完成
V get(long tiomeout,TimeUnit unit)调用后阻塞,在时间限制内计算完成返回,否则返回异常
void cancel(boolean mayInterrupt)取消计算,已经开始计算让它中断,没开始则取消
boolean isCancelled()是否取消
boolean isDone()是否完成
42.执行器(Executors)类中有很多静态工厂类方法来构造线程池
43.实现线程的三个方法
继承Thread类 实现Runnable接口 实现Callable接口
44.使用Thread类写一个线程
我们先观察一下 线程调用run方法和start方法的区别
package com.Thread; /** * @Description: 使用继承thread类 实现线程。重写run方法,在主函数用start调用 * @Author: cckong * @Date: 2021/2/2 */ public class Test01 extends Thread{ @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println("run"+i); } } public static void main(String[] args) { Test01 test01=new Test01(); test01.run(); for (int i = 0; i < 20; i++) { System.out.println("main"+i); } } }
我们可以看到run方法是执行完在执行其他的线程。
我们再看一下start方法
package com.Thread; /** * @Description: 使用继承thread类 实现线程。重写run方法,在主函数用start调用 * @Author: cckong * @Date: 2021/2/2 */ public class Test01 extends Thread{ @Override public void run() { for (int i = 0; i < 17; i++) { System.out.print(" start"+i); } } public static void main(String[] args) { Test01 test01=new Test01(); test01.start(); System.out.println(" "); for (int i = 0; i < 17; i++) { System.out.print(" main"+i); } } }
可以看出是交替执行的。(由CPU调度安排)
45.实现Runnable接口生成一个线程
package com.Thread; /** * @Description: 实现Runnable接口 实现线程.先重写run方法,然后使用一个thread代理调用start方法 * @Author: cckong * @Date: 2021/2/2 */ public class Test02 implements Runnable{ @Override public void run() { for (int i = 0; i < 17; i++) { System.out.print(" start"+i); } } public static void main(String[] args) { Test02 test02=new Test02(); new Thread(test02).start(); for (int i = 0; i < 17; i++) { System.out.print(" main"+i); } } }
我们可以看见也需要重写run方法。唯一的不同是 需要new一个Thread来代理调用
46.二者区别
47.经典抢火车票问题
/** * @Description: 经典抢火车票例子 * @Author: cckong * @Date: 2021/2/2 */ public class Test03 implements Runnable{ private int ticnum=10; @Override public void run() { while (true) { if(ticnum<=0) break; try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "抢到了" + ticnum); ticnum--; } } public static void main(String[] args) { Test03 test03=new Test03(); new Thread(test03,"黄牛").start(); new Thread(test03,"小明").start(); new Thread(test03,"小红").start(); new Thread(test03,"老师").start(); } }
出现了很多并发的问题需要解决。
我们现在ticnum上加了 volatile关键字 其作用是 变量的更改对外可见
但还是无法保证安全性
48.经典龟兔赛跑问题
/** * @Description: 龟兔赛跑案例 * @Author: cckong * @Date: 2021/2/2 */ public class Test04 implements Runnable{ private static String winner;//赢家的名字 @Override public void run() { String user=Thread.currentThread().getName(); int step=0;//记录当前步数 for (int i = 0; i < 101; i++) { //这里是对于兔子树下睡觉的形象化 当线程用户是兔子时 十步歇一会 if (user.equals("兔子") && step % 10 == 0) { try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } step++;//步数+1 System.out.println(user+"已经跑了"+step+"步"); if(win(step)) break;//当已经有了赢家了或者自己赢了 就停止循环 } } //判断是否结束游戏了(已经存在赢家 自己赢了) public boolean win(int step) { if(winner!=null) return true; if(step>=100){ winner=Thread.currentThread().getName(); System.out.println(winner+"赢了"); return true; } return false; } public static void main(String[] args) { Test04 test04=new Test04(); new Thread(test04,"兔子").start(); new Thread(test04,"乌龟").start(); // // System.out.println(winner+"赢了"); } }
49.实现callbale接口 建立线程
/** * @Description: callable接口 * @Author: cckong * @Date: 2021/2/3 */ public class Test05 implements Callable<Boolean> { @Override public Boolean call() throws Exception { for (int i = 0; i < 17; i++) { System.out.print(" start"+i); } return true;//可以有返回值 } public static void main(String[] args) { Test05 test05=new Test05();//创建一个线程 ExecutorService executorService= Executors.newFixedThreadPool(1);//创建执行环境 并指定线程数 Future<Boolean> r1=executorService.submit(test05);//提交线程
System.out.println(" "); for (int i = 0; i < 17; i++) { System.out.print(" main"+i); }
boolean rs1=false;//默认false try { rs1 = r1.get();//获得返回值 } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } System.out.println(rs1); executorService.shutdownNow();//关闭服务 } }
50.模拟倒计时(Thread.sleep())
51.线程礼让yield
礼让是讲资源让出来 然后由cpu重新调度。不一定礼让就会换线程
正在运行的线程由运行状态变为就绪状态。
52.线程强制执行join(插队)
主线程和thread刚开始是并发执行的。
在i=200时 thread进行插队 只执行thread。
thread执行之后在执行主线程。
53.Thread.getState()获取当前状态
54.setPriority()设置优先级
当你设置最高优先级的话 也不一定调度 要看cpu的调度情况
要求在1-10之间
55.守护线程 daemon
需要用到线程函数 setDaemon设置为true 默认是false是用户线程。
main是用户线程 gc垃圾回收算法是守护线程
在这个程序里 上帝作为守护线程先输出 当用户线程 你启动了之后 开始运行你
当你运行结束。此时jvm里面只有一个守护线程上帝。
上帝还要运行一段时间 因为jvm关闭需要时间。
56.list是线程不安全的集合
可以使用CopyOnWriteArrayList是JUC包下的安全集合
57.死锁的条件
58.线程池