概述:
程序(program):可以看作是一辆小汽车
进程(process):可以看作是小汽车内部在运行的发动机,进程是动态的
线程(thread):可以看作是发动机内部的一个小齿轮
抽象出概念:
程序:计算机程序是为完成特定任务,用某种语言编写的一组指令的集合.
进程:一个正在运行的程序,一个动态的过程,有自身产生,存在和消亡的过程
线程:进程细化为线程,是程序内部的一条执行路径
一,创建线程
1,继承Thread类
并且重写run方法,run方法不是抽象方法,Thread类不是抽象类
一个类继承了Thread类之后,他就是一个独立的线程
调用线程的strat方法让线程启动,会执行重写的run方法中的代码
native:本地的,java没有权利操作cpu,可能是c语言构建
线程的优先级是概率问题,90%主方法的优先级比普通线程高
2,实现Runnable接口
一个问题能用实现接口解决不用继承解决
想要让线程启动,必须调用Thread类中的start方法
创建Tread类调用statr方法
package demo2;
public class Ch1 implements Runnable{
@Override
public void run() {
System.out.println(2);
}
}
class C{
public static void main(String[] args) {
System.out.println(1);
Ch1 ch1=new Ch1();
Thread t=new Thread(ch1);
t.start();
System.out.println(3);
}
}
package demo2;
public class Ch2 {
public static void main(String[] args) {
System.out.println(1);
//箭头函数
new Thread(() -> System.out.println(2)).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(3);
}
}
3,实现callable接口
callable-FutureTask -RunnableFuture-runnable-thread
package demo2;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class Ch3 implements Callable<String> {
@Override
public String call(){
System.out.println(2);
return "call方法的返回值";
}
}
class T{
public static void main(String[] args) {
System.out.println(1);
FutureTask<String> futureTask=new FutureTask<>(new Ch3());
new Thread(futureTask).start();
System.out.println(3);
}
}
二,守护线程
两种类型的线程:
1,用户线程
2,守护程序线程
守护线程为用户线程提供服务,仅在用户线程运行时才需要
主方法就是用户线程,自己创建的就是守护线程
守护线程对于后台支持的任务非常有用:
垃圾回收,大多数jvm线程都是守护线程
例如:qq,主窗口就是用户线程,聊天窗口就是守护线程,用户线程关闭,守护线程就关闭
ch5
任何线程继承创建它的线程,守护进程状态,由于主线程是用户线程
在main方法内启动的任何线程默认的都是守护线程
package demo2;
public class Ch4 extends Thread {
@Override
public void run() {
super.run();
System.out.println(1);
}
public static void main(String[] args) {
Ch4 ch4=new Ch4();
//设置ch4为守护线程
ch4.setDaemon(true);
ch4.start();
}
}
线程的生命周期:
从摇篮到坟墓
NEW :线程未被start调用执行(新建了线程)
RUNNABLE :线程正在jvm中被执行,等待来自操作系统的调度(可运行,准备就绪)
BLOCKED:阻塞,因为某些原因,不能立即执行,需要挂起等待(有线程正在运行)
WAITING:无限期等待,Object类,如果没唤醒就无限等待
TIMED_WAITING :线程等待指定的时间,有限期等待
TERMINATED:终止线程的状态,线程已经执行完毕.
等待和阻塞:阻塞因为外部原因,等待一般是主动调用方法,发起主动的等待,等待还可以传入参数确定等待时间,
* 1.CPU多核缓存结构
* 物理内存:硬盘内存。(固态硬盘,尽量不要选择混合硬盘)
* CPU缓存为了提高程序运行的性能,现在CPU在很多方面对程序进行优化。
* CPU处理速度最快,内存次之,硬盘速度最低。
* 在CPU处理内存数据时,如果内存运行速度太慢,就会拖累CPU的速度
* 为了解决这样的问题,CPU设计了多级缓存策略。
* CPU分为三级缓存:每个CPU都有L1,L2缓存,但是L3缓存是多核公用的。
* CPU查找数据时,CPU->l1->l2->l3->内存->硬盘
* 从CPU到内存,60-80纳秒(一纳秒等于十亿分之一秒)
* 从CPU到L3,15纳秒
* 从CPU到L2,3纳秒
* 从CPU到L1,1纳秒
* 寄存器,0.3纳秒
* 进一步优化,CPU每次读取一个数据,读取的是与它相邻的64个字节的数据。
* 【缓存行】。
* 英特尔提出了一个协议MESI协议
* 1、修改态,此缓存被动过,内容与主内存中不同,为此缓存专有
* 2、专有态,此缓存与主内存一致,但是其他CPU中没有
* 3、共享态,此缓存与主内存一致,其他的缓存也有
* 4、无效态,此缓存无效,需要从主内存中重新读取
* 【指令重排】
* 四条指令,四个人在四张纸上写下【恭喜发财】。
*
* java内存模型-JMM
* 尽量做到硬件和操作系统之间达到一致的访问效果。
public class Ch02 {
private static int x = 0,y = 0;
private static int a = 0,b = 0;
private static int count = 0;
private volatile static int NUM = 1;
public static void main(String[] args) throws InterruptedException {
long start = System.currentTimeMillis();
for (;;) {
Thread t1 = new Thread(() -> {
a = 1;
x = b;
});
Thread t2 = new Thread(() -> {
b = 1;
y = a;
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("一共执行了:" + count++ + "次");
if(x == 0 && y ==0){
long end = System.currentTimeMillis();
System.out.println("耗时:" +(end - start) + "毫秒,(" + x + "," + y + ")");
break;
}
a = 0;b = 0;x = 0;y = 0;
}
}
我们发现测试结果中大部分感觉是正确的,(0,1)或(1,0),一个是线程1先执行,一个是线程2先执行。
按道理来说,绝对不会出现(0,0),如果出现(0,0)代表存在指令重排,乱序执行。
使用volatile关键字来保证一个变量在一次读写操作时,避免指令重排。
我们在读写操作之前加入一条指令,当CPU碰到这条指令后必须等到前面的执行执行完成才能继续执行下一条指令。
【内存屏障】
现在大多数现代计算机为了提高性能而采取乱序执行,这可能会导致程序运行不符合我们预期,内存屏障就是一类同步屏障指令,是CPU或者编译器在对内存随机访问的操作中的一个同步点,只有在此点之前的所有读写操作都执行后才可以执行此点之后的操作。
public class Ch03 {
private volatile static boolean isOver = false;
private static int number = 0;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while(!isOver){
}
System.out.println(number);
}
});
thread.start();
Thread.sleep(1000);
number = 50;
// 已经改了,应该能退出循环了
isOver = true;
}
}
* 可见性
* thread线程一直在高速读取缓存中的isOver,不能感知主线程已经把isOVer改成了true
* 这就是线程的可见性的问题。
* 怎么解决?
* volatile能够强制改变变量的读写直接在内存中操作。
public class Ch04 {
private volatile static int count = 0;
public synchronized static void add() {
count ++;
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
add();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
add();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("最后的结果是:" + count);
}
}
* 线程争抢
* 解决线程争抢的问题最好的办法就是【加锁】
* synchronized同步锁,线程同步
*
* 当一个方法加上了synchronized修饰,这个方法就叫做同步方法。
* 线程安全的实现方法
* (1)数据不可变。
* 一切不可变的对象一定是线程安全的。
* 对象的方法的实现方法的调用者,不需要再进行任何的线程安全的保障措施。
* 比如final关键字修饰的基本数据类型,字符串。
* 只要一个不可变的对象被正确的创建出来,那外部的可见状态永远都不会改变。
* (2)互斥同步。加锁。【悲观锁】
* (3)非阻塞同步。【无锁编程】,自旋。我们会用cas来实现这种非阻塞同步。
* (4)无同步方案。多个线程需要共享数据,但是这些数据又可以在单独的线程中计算,得出结果
* 我们可以把共享数据的可见范围限制在一个线程之内,这样就无需同步。把共享的数据拿过来,
* 我用我的,你用你的,从而保证线程安全。ThreadLocal
public class Ch05 {
private final static ThreadLocal<Integer> number = new ThreadLocal<>();
private static int count = 0;
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
// t1内部自己用一个count
number.set(count);
for (int i = 0; i < 10; i++) {
number.set(count ++);
System.out.println("t1-----" + number.get());
}
}
});
Thread t2= new Thread(new Runnable() {
@Override
public void run() {
// t2内部自己用一个count
number.set(count);
for (int i = 0; i < 10; i++) {
number.set(count ++);
System.out.println("t2-----" + number.get());
}
}
});
t1.start();
t2.start();
}
}
车站买票例子:
package com.jsoft.afternoon;
public class Ticket implements Runnable{
private static final Object lock = new Object();
private static Integer count = 100;
String name;
public Ticket(String name) {
this.name = name;
}
@Override
public void run() {
while(Ticket.count > 0){
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (Ticket.lock){
System.out.println(name + "出票一张,还剩:" + Ticket.count-- + "张!");
}
}
}
public static void main(String[] args) {
Thread one = new Thread(new Ticket("一号窗口"));
Thread two = new Thread(new Ticket("二号窗口"));
one.start();
two.start();
}
}