Integer sum = futureTask.get(); 会等待其对应的线程执行完 ,即阻塞 再获得结果。
所以我在测试时,出现一个小插曲
@Slf4j
public class ThreeWays {
//1.自定义MyThread进行继承Thread
static void test001(){
Thread thread = new MyThread();
thread.setName("t1");
thread.start();
}
//2.实现Runnable接口
static void test002(){
Thread thread=new Thread(()-> {
for (int i = 0; i <1000 ; i++) {
log.info("{}",i);
}
});
thread.setName("t2");
thread.start();
}
//3.实现
static Integer test003() throws ExecutionException, InterruptedException {
FutureTask futureTask=new FutureTask<>(new Callable() {
@Override
public Integer call() throws Exception {
int num=0;
for (int i = 0; i < 1000000; i++) {
num+=i;
log.info("{}",i);
}
return num;
}
});
Thread thread=new Thread(futureTask);
thread.start();
Integer sum = futureTask.get();
log.info("sum为:{}",sum);
return sum;
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
// test001();
// test002();
test003();
log.info("123");
}
}
当使用Runable作为参数时 ,即 new Thread( runnable )
把runnable 赋给 target
再运行时,去判断,究竟是否调用 targert.run()
方法1 是把线程和任务合并在了一起,方法2 是把线程和任务分开了
用 Runnable 更容易与线程池等高级 API 配合
用 Runnable 让任务类脱离了 Thread 继承体系,更灵活
①ps -ef 查看所有进程的信息,一般会跟grep 连用。
e代表 every , f代表格式化输出
ps -fT -p
②kill 杀死进程
kill -9 是强制杀死进程的意思。
③top 按大写 H 切换是否显示线程
top -H -p
Java
jps 命令查看所有 Java 进程
jstack
jconsole 来查看某个 Java 进程中线程的运行情况(图形界面)
在目录下新建一个 Dockerfile
FROM openjdk:8-jdk-slim-buster
COPY . /usr/src/myapp
WORKDIR /usr/src/myapp
RUN javac Hello.java
CMD ["java","Hello"]
新建一个 Hello.java, 我们在主线程开启两个线程,分别为t1,t2
import java.util.Random;
public class Hello {
public static void main(String[] args) {
Thread t1=new Thread(()->{
while(true){
try {
Thread.sleep(new Random().nextInt(100)+900);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
t1.setName("t1");
t1.start();
Thread t2=new Thread(()->{
while (true){
try {
Thread.sleep(new Random().nextInt(100)+900);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(2);
}
});
t2.setName("t2");
t2.start();
System.out.println("main");
}
}
执行 docker build -t 自定义镜像的名字 .
docker run 自定义镜像的名字
注意第一个命令有 句点
现在我们去用命令看一下
执行类加载,把字节码加载到java虚拟机中,放到方法区
其实就是栈帧,每当调用一个方法,就加到栈上面。每个线程都有一个程序计数器和对应的栈。
这句话基本上是正确的,但需要进一步解释和澄清:
- 栈帧:每当线程调用一个方法时,虚拟机为该方法创建一个新的栈帧,并将其压入当前线程的Java虚拟机栈顶。栈帧中包含了局部变量表、操作数栈、动态链接信息(指向常量池的方法引用)、方法返回地址和其他额外的数据区域,如用于异常处理的信息等。
- 程序计数器:每个线程确实都有自己的程序计数器(PC寄存器),它记录了当前线程执行指令的位置,即下一条要执行的字节码指令地址。当线程在不同的方法之间切换时,程序计数器会保存并恢复相应方法的入口地址或返回地址。
- 线程与栈的关系:每个线程拥有独立的Java虚拟机栈,这个栈就是用来存储该线程所调用的所有方法的栈帧。因此,每个线程的栈都是私有的,这样可以保证不同线程间的本地变量相互独立,不会互相干扰。
总结一下,对于每个线程来说,其执行过程中的每一步都可以对应到某个栈帧,而程序计数器则指示着当前线程正在执行哪一段代码。当方法调用发生时,新的栈帧会被添加到相应的线程栈上,而当方法结束时,对应的栈帧将被弹出。
run()和调用一个对象的方法没区别,就是去调用,不会开启一个线程。
而 start会去开启一个线程去调用run()
① 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)
②其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException
③睡眠结束后的线程未必会立刻得到执行
④建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读
让出当前线程的cpu使用权。但这不意味着不会再执行该线程,因为 此时 线程处于就绪状态,意味着调度器可以去随机选择线程。
1. 调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程
2. 具体的实现依赖于操作系统的任务调度器
yield跟sleep对比: ①所更改的线程状态不同。②yiled不需要指定等待时间。
线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它
如果 cpu 比较忙,那么优先级高的线程会获得更多的时间片,但 cpu 闲时,优先级几乎没作用。
线程名.setPriority()
下面是线程优先级的范围。 最小,默认,最大。
join就是等待指定线程结束。
join()
join( final long millis ) 指定等待线程结束,如果等待时间超过mills ,那么不再等待。
看下面这个案例很有意思!
t1线程等待2秒,此时t2线程等待1秒,t1线程执行完后,t2线程肯定是执行完的,t2.join()会立即执行完,无需等待。
static int r = 0;
public static void main(String[] args) throws InterruptedException {
test1();
}
private static void test1() throws InterruptedException {
log.debug("开始");
Thread t1 = new Thread(() -> {
log.debug("开始");
sleep(1);
log.debug("结束");
r = 10;
});
t1.start();
log.debug("结果为:{}", r);
log.debug("结束");
}
对于正常执行的线程,使用 interrupt() 打断后, isInterrupted() 的结果为true, 即被打断。
对于sleep,wait,join 的线程 ,使用 interrupt() 打断后,会清空标记状态,抛出中断异常, isInterrupted() 的结果为false,即标记状态仍未变,为false.
比较有趣的一点:
对于正常运行的线程(没有sleep),当被打断后,并不会像我们想的那样直接被打断,而是 正常执行,只不过是它的标记状态isInterrupted() 变为false
场景:一个监控线程,不断检测主机状态,但我们可以随时去停掉这个监控线程,怎样优雅的停。
如下方代码,实现监控:
@Slf4j
public class Test {
private static Thread monitor;
public static void main(String[] args) {
monitor=new Thread(()->{
Thread curr = Thread.currentThread();
while (true){
if(curr.isInterrupted()){
log.info("料理后事");
break;
}
try {
Thread.sleep(100);
log.info("执行监控");
} catch (InterruptedException e) {
//正在睡眠时time_waited 被打断,此时会抛出异常,清楚中断标记
//因此在这里要重新打断一下
curr.interrupt();
}
}
});
//中断监控,此时如果未睡眠,结果为true
monitor.interrupt();
}
}
当打断标记为真,park()就会失效
默认情况下,Java 进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其它非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。
下面这段代码,如果不设置守护进程,就会不断运行。
@Slf4j
public class Tea {
public static void main(String[] args) {
Thread t1=new Thread(()->{
while (true){
log.info("我守护你!");
}
});
t1.setDaemon(true);
t1.start();
}
}
从操作系统层面来讲:
从java虚拟机来讲
先看Thread.state 枚举类
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
*
* - {@link Object#wait() Object.wait} with no timeout
* - {@link #join() Thread.join} with no timeout
* - {@link LockSupport#park() LockSupport.park}
*
*
* A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called {@code Object.wait()}
* on an object is waiting for another thread to call
* {@code Object.notify()} or {@code Object.notifyAll()} on
* that object. A thread that has called {@code Thread.join()}
* is waiting for a specified thread to terminate.
*/
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
*
* - {@link #sleep Thread.sleep}
* - {@link Object#wait(long) Object.wait} with timeout
* - {@link #join(long) Thread.join} with timeout
* - {@link LockSupport#parkNanos LockSupport.parkNanos}
* - {@link LockSupport#parkUntil LockSupport.parkUntil}
*
*/
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
}
sleep属于timed_waiting 有时间的等待
join属于waiting
join(时间) 属于timed_waiting
关于synchronized 锁对象的一些理解。
在Java中,`synchronized`关键字可以用来实现锁机制,它可以锁定的是对象或者类的**方法**或**代码块**。
1. **同步方法**:当`synchronized`修饰一个方法时,它锁住的是调用该方法的对象实例(即对象锁)。这意味着在同一时间,只能有一个线程访问该对象的这个同步方法。例如:
```java
public class MyClass {
public synchronized void myMethod() {
// 这个方法被synchronized修饰,锁住的是当前MyClass对象实例
// ...
}
}
```
在上述例子中,任何时刻只有一个线程能够执行`myMethod`方法,对于不同的`MyClass`实例则是互不影响的。
2. **同步代码块**:更细粒度的控制可以通过指定特定对象来锁定代码块:
```java
public class MyClass {
private final Object lock = new Object();
public void myMethod() {
synchronized (lock) {
// 这个代码块被synchronized修饰,锁住的是变量lock引用的对象
// ...
}
}
}
```
在这种情况下,锁住的是`lock`对象,所有通过该对象进行同步的代码块将共享同一把锁。
另外,还可以使用`synchronized`锁定类的**静态方法**,这时获取的是类锁(也称为类级锁或全局锁),意味着无论多少个对象实例,同一时间内只有一个线程可以访问此类的任何一个静态同步方法:
```java
public class MyClass {
public static synchronized void myStaticMethod() {
// 静态同步方法,锁住的是MyClass类本身
// ...
}
}
```
总结来说,`synchronized`既可以作用于对象实例的方法上获得对象锁,也可以作用于类的静态方法上获得类锁。
例如对于 i++ 而言(i 为静态变量),实际会产生如下的 JVM 字节码指令:
临界区 Critical Section
一个程序运行多个线程本身是没有问题的
问题出在多个线程访问共享资源
多个线程读共享资源其实也没有问题
在多个线程对共享资源读写操作时发生指令交错(不按顺序执行),就会出现问题
一段代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界区
竞态条件 Race Condition
多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件
如下图,未产生指令交错,就可以正常执行。
但下面这张图,就会 出现问题
synchronized 实际是用对象锁保证了临界区内代码的原子性,临界区内的代码对外是不可分割的,不会被线程切换所打断。
思考一下 下面的问题:
当在方法上加synchronized时,是锁对象,还是锁类,显而易见。
情况5 一个是锁的类,一个是锁的对象,因此是不会冲突的。
public class Tea {
public static synchronized void a() {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.info("1");
}
public synchronized void b() {
log.info("2");
}
public static void main(String[] args) {
Tea n1 = new Tea();
new Thread(() -> {
n1.a();
}).start();
new Thread(() -> {
n1.b();
}).start();
}
}
private final 让子类不能重写
String Integer 等是线程安全的,因为不可变。
没有竞争,访问时间错开。
因为此时Object变为了重量级锁,因此,此时虽然Thread0原本持有轻量级锁,但Object中已经变化,Thread0 如果解锁就会失败。
缺点:每次发生锁重入,都会产生一条锁记录,
对于hashcode,当调用对应的hashcode()方法,时,才会把对应的值写到标记头中。
对于开启偏向锁的对象,调用hashcode(),会禁用偏向锁。
对于 轻量锁 会存在锁记录里,因为进行交换了。
对于 重量级锁 hashcode会存在 Monitor里。
jit 会对java字节码进行进一步优化,对于反复调用的方法,就会进行优化。
如下方代码,o是一个局部变量,并不会被共享,因此需要用 o加锁没有什么用,因此就会进行优化。
首先,只有进入 synchronized的块 内 ,才能够调用,wait()和notify()
即 只有获得了synchronized(Object lock) lock这个锁,然后才能去指向lock.wait();
此时,waitset就有用了!
当使用wait()后,会进入monitor的waitset进行等待,并释放掉当前的锁,等到满足条件,lock.notify() ,随机唤醒一个线程,此时被唤醒的线程就去竞争锁,如果获取成功,就继续执行刚才未执行的代码。
看下图片:
使用while循环 其实是一个不错 的选择
3.10 保护性暂停
3.11 生产者消费者
不管同步代码块正常或者异常,synchonized 都会解锁。
直接调用wait时会报错的!必须先进入synchonized中,获得了Object锁,然后才能去执行Object.wait()方法,进入waitSet 。
Object.wait(等待时间) 如果等待时间没被唤醒,那么就不再等待,去执行它下面的代码,但是 此时应该不是直接获得锁,而是去争夺。
sleep与wait的区别,当在synchronized
4)都是timed-waiting 有时限的等待。
保护性暂停
对上面的代码进行优化一下
保护性暂停,就是不满足条件时,进行wait()等待
park unpark
原理:
调用多次unpark效果一样,干粮还是1
new->runable->blocked->waiting->timedwaiting->termited
runnable()
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
method1();
}
public static void method1() {
lock.lock();
try {
log.debug("execute method1");
method2();
} finally {
lock.unlock();
}
}
public static void method2() {
lock.lock();
try {
log.debug("execute method2");
method3();
} finally {
lock.unlock();
}
}
public static void method3() {
lock.lock();
try {
log.debug("execute method3");
} finally {
lock.unlock();
}
}
打断后,就不会去获得锁了。
可打断的意义是让 它停止无休止的等待。
注意:要调用
lock.lockInterruptibly();
才行。
不然,即使去打断了线程,也不会停止该线程在等待获得锁。
@Slf4j
public class Test002 {
static ReentrantLock lock=new ReentrantLock();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
try {
lock.lockInterruptibly();
} catch (InterruptedException e) {
log.info("被中断");
e.printStackTrace();
return;
}
//未被中断,获得锁
log.info("获得锁");
lock.unlock();
}, "t1");
//主线程获得锁
lock.lock();
t1.start();
//打断t1线程
t1.interrupt();
// 主线程释放锁
lock.unlock();
}
tryLock 不带时间,没有获得到锁,立即返回false
tryLock带时间,在对应时间段内没有获得锁,返回false
用trylock解决哲学家就餐问题。
筷子继承了 ReentrantLock,因此可以执行trylock()方法。
class Chopstick extends ReentrantLock {
String name;
public Chopstick(String name) {
this.name = name;
}
@Override
public String toString() {
return "筷子{" + name + '}';
}
}
class Philosopher extends Thread {
Chopstick left;
Chopstick right;
public Philosopher(String name, Chopstick left, Chopstick right) {
super(name);
this.left = left;
this.right = right;
}
@Override
public void run() {
while (true) {
// 尝试获得左手筷子
if (left.tryLock()) {
try {
// 尝试获得右手筷子
if (right.tryLock()) {
try {
eat();
} finally {
right.unlock();
}
}
} finally {
left.unlock();
}
}
}
}
private void eat() {
log.debug("eating...");
Sleeper.sleep(1);
}
}
公平锁:先入先得。(必要性不大,降低并发度)。需要在构造方法时进行指定。
自旋重试,在单核cpu中没啥用,
对于下面这个,由于指定了多例,每个请求都会创建一个新的 TestController
实例,结果总是返回0
@RestController
@RequestMapping("/add")
@Scope("prototype")
public class TestController {
private int a=0;
@GetMapping
public int add(){
return a++;
}
}
当去掉注解 @Scope("prototype")
此时就是单例了,每个请求都用的同一个对象。
十、船新版本