java7-多线程-Part1-线程创建-线程常用方法-线程池-ThreadLocal
1,创建线程
- 在java中,线程也是一个对象,执行完毕Runnable接口里的run方法,线程就结束了。
- 当一个进程里所有的线程都执行结束了,一个进程也就执行结束了。
- 线程相当于是cpu,会从入口开始执行代码。一段代码可以被多个线程同时执行,可以通过
Thread.currentThread()
获取执行当前代码的线程。 - 例子
public class test {
public static void main(String[] args) throws InterruptedException {
//同步执行-非多线程
long start = System.currentTimeMillis();
for(int i=0; i<4; i++){
System.out.println(getSum(1, 1));
}
System.out.println("总耗时" + (System.currentTimeMillis()-start));
//异步执行-多线程
//todo step2-起多线程,异步执行(python中是起Pool进程池,然后statmap)
//todo step3-传递数据:通过类的初始化向run函数传递数据(python可以通过statmap+zip向函数中传递数据)
//todo step4-如果收集多个线程的数据?(python中可以使用Manager().Queue()收集数据)
start = System.currentTimeMillis();
for(int i=0; i<4; i++){
Thread thread = new Thread(new GetSum(1, 1));
thread.start(); //启动线程,调用start方法,而不是run方法。主线程不会阻塞,所以总耗时很短(没有等所有线程执行结束,直接跳过了)。
}
System.out.println("总耗时" + (System.currentTimeMillis()-start));
}
static int getSum(int a, int b) throws InterruptedException {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName());
return a+b;
}
// todo step1:准备好实现了Runnable的类,重写run方法(python中是写好函数)
static class GetSum implements Runnable{
int a;
int b;
public GetSum(int a, int b){
this.a=a;
this.b=b;
}
@Override
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
System.out.println(this.a+this.b);
}
}
}
2,守护线程和优先级属性
2-1,守护线程
- 守护线程:daemon thread
- 理解:
- 如果一个进程里没有线程,或者所有的线程都是守护线程,那么进程就结束了。
- 守护线程类似于整个进程的一个默默无闻的小喽喽;它的生死无关重要,它却依赖整个进程而运行;哪天其他线程结束了,没有要执行的了,程序就结束了,理都没理守护线程,就把它中断了。
thread.setDaemon(true)
必须在thread.start()
之前设置,否则会跑出一个IllegalThreadStateException
异常。不能把正在运行的常规线程设置为守护线程。- 在Daemon线程中产生的新线程也是Daemon的。
- 守护线程不能用于去访问固有资源,比如读写操作或者计算逻辑。因为它会在任何时候甚至在一个操作的中间发生中断。
- Java自带的多线程框架,比如ExecutorService,会将守护线程转换为用户线程,所以如果要使用守护线程就不能用Java的线程池。
- 当把线程设置为守护线程时,try/catch语句块中的finally不一定会执行,因为在进程中的所有用户线程都执行完毕后,jvm会立即关闭,也就不存在资源的释放问题,但在某些特殊情况,cpu时间片刚好轮转的时候,还是会执行到finally代码块。
- 存在意义:当主线程结束时,守护线程自动关闭,就免去了还要继续关闭守护线程的麻烦。
- 应用实例:
- GC垃圾回收线程:就是一个经典的守护线程,当我们的程序中不再有任何运行的Thread,程序就不会再产生垃圾,垃圾回收器也就无事可做,所以当垃圾回收线程是JVM上仅剩的线程时,垃圾回收线程会自动离开。它始终在低级别的状态中运行,用于实时监控和管理系统中的可回收资源。
- 应用场景:
- 为其它线程提供服务支持的情况(如GC);
- 或者在任何情况下,程序结束时,这个线程必须正常且立刻关闭,就可以作为守护线程来使用
- 反之,如果一个正在执行某个操作的线程必须要正确地关闭掉否则就会出现不好的后果的话,那么这个线程就不能是守护线程,而是用户线程。通常都是些关键的事务,比方说,数据库录入或者更新,这些操作都是不能中断的。
- 理解:
public class test {
public static void main(String[] args) throws InterruptedException {
/*==============================
同步执行-非多线程
================================*/
long start = System.currentTimeMillis();
for(int i=0; i<4; i++){
System.out.println(getSum(1, 1));
}
System.out.println("总耗时" + (System.currentTimeMillis()-start));
/*==============================
异步执行-多线程
================================*/
//todo step2-起多线程,异步执行(python中是起Pool进程池,然后statmap)
//todo step3-传递数据:通过类的初始化向run函数传递数据(python可以通过statmap+zip向函数中传递数据)
//todo step4-如果收集多个线程的数据?(python中可以使用Manager().Queue()收集数据)
start = System.currentTimeMillis();
for(int i=0; i<4; i++){
Thread thread = new Thread(new GetSum(1, 1));
thread.start(); //启动线程,调用start方法,而不是run方法。主线程不会阻塞,所以总耗时很短(没有等所有线程执行结束,直接跳过了)。
}
System.out.println("总耗时" + (System.currentTimeMillis()-start));
/*==============================
守护线程执行-当进程中的非守护线程都结束后,守护线程立刻结束
================================*/
Thread thread = new Thread(new daemonThreadClass());
thread.setDaemon(true); //注释掉该语句,则会正常打印daemonThreadClass中run方法的那句话
thread.start();
}
// todo 同步执行所用的方法
static int getSum(int a, int b) throws InterruptedException {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName());
return a+b;
}
// todo step1:准备好异步执行所用的类-实现Runnable抽象类,重写run方法(python中是写好函数)
static class GetSum implements Runnable{
int a;
int b;
public GetSum(int a, int b){
this.a=a;
this.b=b;
}
@Override
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
System.out.println(this.a+this.b);
}
}
// todo 守护线程所用的类
static class daemonThreadClass implements Runnable{
@Override
public void run() {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("守护线程运行结束,不过这句话应该不会打印出来~");
}
}
}
2-2,线程设置优先级
- 设置优先级:
thread.setPriority(Thread.MAX_PRIORITY)
- 可以设置线程的优先级,优先级的作用不能保证,这和线程的运行状态以及机器本身的运行状态有关。无论是否为守护线程都可以设置优先级。
3,线程的常用方法:
3-1,interrupt
和stop
方法
- 线程的
interrupt
方法无法真的像这个方法的名字那样让线程中断,只是改变线程isInterrupted
方法的返回值(false->true
)。
//在线程1中可以通过调用线程2的interrupt方法,改变线程2的状态
thread2.interrupt();
//在线程1可以通过调用线程2的isInterrupted方法,查看线程2的运行状态
thread2.isInterrupted(); //返回boolean值
//也可以线程查看自己的运行状态
Thread.currentThread().isInterrupted(); //返回boolean值
//线程1可以改变线程2的isInterrupted的返回值,然后线程2再自己查看isInterrupted的返回值,再决定执行什么动作。
//是线程之间通信的一种机制
//线程sleep时interrupt会抛出InterruptedException异常
- 线程的
stop
方法可以强制让线程结束,但是这会带来很大的隐患,会造成程序状态的错误,比如锁没有释放等。不要在生产代码中调用这个方法。
3-2,yield
方法
- 使当前线程从执行状态(运行状态)变为可执行态(就绪状态),意思也就是使当前线程主动让出cpu(可以和volatile变量配合使用,以控制多线程执行的相对顺序)。
- 作用:提出释放CPU时间片的请求。不会释放锁,线程依然处于RUNNABLE状态,线程不会进入堵塞状态。
yield
方法重新执行时是从头开始执行,这和wait/notify的从阻塞处开始执行并不相同
3-3,join
方法
- 主线程中,子线程调用了join()方法后面的代码,只有等到子线程执行结束了才能执行
- https://www.cnblogs.com/lcplcpjava/p/6896904.html
4,多线程的混乱-同步控制synchronized
- 多线程修改同一个对象的数据时人多手杂,一个线程在改,其他的线程也在改。从内存(memory)或cpu缓存(cache)中读取当前值,通过cpu中的运算器(ALU)修改为新的值,将新的值写回(memory或cache)这三个步骤并非是原子性的,可能会有别的线程的代码乱入,特别是现代计算机中CPU都有的缓存,让问题变得更加不可预测。
- 例子
class ChangeData implements Runnable{
private long delta;
private long loopCount;
private DataHolder dataHolder;
public ChangeData(){}
public ChangeData(long delta, long loopCount, DataHolder dataHolder){
this.delta = delta;
this.loopCount = loopCount;
this.dataHolder = dataHolder;
}
@Override
public void run(){
for(int i=0; i<loopCount; i++){
dataHolder.change(delta);
}
dataHolder.print();
}
}
class DataHolder{
private long number = 0;
public void change(long delta){
number += delta;
}
public void print(){
System.out.println(number);
}
}
public class MultiThreadSimple {
public static void main(String[] args) {
DataHolder dataHolder = new DataHolder();
ChangeData changeData = new ChangeData(1, 10000, dataHolder);
for(int i=0; i<10; i++){
Thread thread = new Thread(changeData); //多个thread操作同一个dataHolder对象
thread.start();
}
dataHolder.print(); //结果本应该是100000,但是因为别的线程乱入(缓存,时间片轮转),导致最后结果错误
}
}
- 同步控制-
synchronized
:synchronized
关键字可用来修饰成员方法/代码块/静态方法,代表这个方法对于同一个对象/同一个类来说,同一个时间只允许一个线程执行;别的线程如果也调用这个实例/类的这个方法,就需要等待已经在执行这个方法的线程执行完毕,才能执行。- 例子
class DataHolder{
private long number = 0;
synchronized public void change(long delta){
//加锁
number += delta;
}
public void print(){
System.out.println(number);
}
}
class DataHolder{
private long number = 0;
public void change(long delta){
//synchronized修饰代码块,该方法的仅有这一部分是强制同步的,其他部分是异步多线程执行的
//这里用的this,但是也可以用Class类的对象(这样锁的是所有同一类的对象),或者是其他类的对象(比如 new Object(),不过这样好像没有意义)
//只要不是null,synchronized可以锁任一对象
synchronized (this){
number += delta;
}
}
public void print(){
System.out.println(number);
}
}
5,同步控制-wait-notify/notifyAll
- 是来自Object类的方法
- 应用场景:当多个线程互动,需要等待和被唤醒时,可以考虑使用该语法。
wait
- synchronized的对象在某一线程(沉睡线程)中执行wait方法
- wait方法必须在进入相应对象的synchronized块中才能调用
- 执行wait方法后,自动失去对象的monitor,也就是说别的线程可以进入这个对象的synchronized块
- 被唤醒的线程,就是wait方法执行完成,开始向下执行
- 如果wait不是synchronized块中的最后一行,那么第一件事就是“排队”获取之前失去的monitor
- 排队加引号是因为synchronized是非公平的,不是谁先排队谁就能先获得monitor
- 唤醒之后的synchronized代码块中的代码同一时间还是只有一个线程执行
notify/notifyAll
- 同一synchronized的对象在某一线程(唤醒线程)中执行notify/notifyAll方法
- notify/notifyAll 方法必须进入相应对象的synchronized块才能调用
- notifyAll是同时唤醒所有同一对象持有的沉睡线程,notify是随机挑一个进行唤醒
- 如果执行notify的时候,线程还没有进入wait状态,那么notify是没有效果的
- 如果notify在wait之前执行,就是所谓的lost notification问题,可能造成线程一直沉睡,无法进行
- 例子
public class ThreadWaitNotify {
public static void main(String[] args) throws InterruptedException {
Object locker = new Object();
int WorkingSec = 2;
int threadCount = 5;
for(int i=0; i<threadCount; i++){
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"线程开始工作...");
try{
synchronized (locker){
Thread.sleep(WorkingSec*1000);
System.out.println(Thread.currentThread().getName()+"线程进入等待");
//todo wait方法必须在进入相应对象的synchronized块中才能调用
//todo 执行wait方法后,自动失去对象的monitor,也就是说别的线程可以进入这个对象的synchronized块
locker.wait();
//todo 被唤醒的线程,就是wait方法执行完成,开始向下执行
//todo 如果wait不是synchronized块中的最后一行,那么第一件事就是“排队”获取之前失去的monitor
//todo 排队加引号是因为synchronized是非公平的,不是谁先排队谁就能先获得monitor
//todo 唤醒之后的synchronized代码块中的代码同一时间还是只有一个线程执行
System.out.println(Thread.currentThread().getName()+"线程继续执行!");
Thread.sleep(WorkingSec*1000);
System.out.println(Thread.currentThread().getName()+"线程执行结束~");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "员工"+i).start();
}
// todo 如果执行notify的时候,线程还没有进入wait状态,那么notify是没有效果的
// todo 如果notify在wait之前执行,就是所谓的lost notification问题,可能造成线程一直沉睡,无法进行
System.out.println("唤醒线程沉睡中...");
Thread.sleep((WorkingSec+1)*1000); //这里沉睡时间的变化就可能导致一些线程在notifyAll后wait而永远沉睡下去
System.out.println("唤醒线程觉醒了!");
synchronized (locker){
// todo notify/notifyAll 方法必须进入相应对象的synchronized块才能调用
//System.out.println("唤醒线程开始唤醒所有沉睡线程!");
//locker.notifyAll();
for(int i=0; i<threadCount; i++){
System.out.println("========开始逐个唤醒========");
locker.notify();
}
}
}
}
6,多线程经典模型-生产者消费者
附录1-java.util.concurrent包
- 类图
- 其实从类图我们能发现concurrent包(除去java.util.concurrent.atomic 和 java.util.concurrent.locks)中的内容并没有特别多,大概分为四类:BlockingQueue阻塞队列体系、Executor线程组执行框架、Future线程返回值体系、其他各种单独的并发工具等。
- Executor线程组执行框架继承体系
- BlockingQueue阻塞队列体系继承体系
- https://www.cnblogs.com/tjudzj/p/4454490.html
- 在新增的Concurrent包中,BlockingQueue很好的解决了多线程中,如何高效安全“传输”数据的问题。
- BlockingQueue的两个常见阻塞场景:
- 当队列中没有数据的情况下,消费者端的所有线程都会被自动阻塞(挂起),直到有数据放入队列。
- 当队列中填满数据的情况下,生产者端的所有线程都会被自动阻塞(挂起),直到队列中有空的位置,线程被自动唤醒。
- 这也是我们在多线程环境下,为什么需要BlockingQueue的原因。作为BlockingQueue的使用者,我们再也不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这一切BlockingQueue都给你一手包办了。
- 其实BlockingQueue阻塞队列体系的继承(实现)层次比较简单,我们只要需要先学习一下BlockingQueue中的方法:
public interface BlockingQueue<E> extends Queue<E> { void put(E e) throws InterruptedException;--插入一个元素,满了就等待 E take() throws InterruptedException;--弹出队首元素,空了就等待 boolean add(E e);--往队列中插入一个对象,队列满了会抛异常,满了不会等待 boolean offer(E e);--同上,区别是队列满了会返回false boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException;--规定时间内插入元素 E poll(long timeout, TimeUnit unit) throws InterruptedException;--规定时间内弹出队首,空了不会等待 int remainingCapacity();--队列剩余大小 boolean remove(Object o);--删除一个equals o的对象 public boolean contains(Object o);--是否包含o int drainTo(Collection<? super E> c);--把队列迁移到另外一个collection结构中 int drainTo(Collection<? super E> c, int maxElements);--迁移,有个最大迁移数量 }
- 下面去看一下具体的几个实现类。
- ArrayBlockingQueue--声明时就确定大小的队列,fifo方式。(方法基本和接口一致,没有特别要说明的内容)
- LinkedBlockingQueue--链表实现的queue-remove效率会高一些。作为开发者,我们需要注意的是,如果构造一个LinkedBlockingQueue对象,而没有指定其容量大小,LinkedBlockingQueue会默认一个类似无限大小的容量(Integer.MAX_VALUE),这样的话,如果生产者的速度一旦大于消费者的速度,也许还没有等到队列满阻塞产生,系统内存就有可能已被消耗殆尽了。
- PriorityBlockingQueue--优先级队列,PriorityBlockingQueue并不会阻塞数据生产者,而只会在没有可消费的数据时,阻塞数据的消费者。因此使用的时候要特别注意,生产者生产数据的速度绝对不能快于消费者消费数据的速度,否则时间一长,会最终耗尽所有的可用堆内存空间。在实现时,内部控制线程同步的锁采用的是公平锁。
- SynchronousQueue--阻塞队列,必须拿走一个才能放进来一个,也就是最多只有一个~
- DelayQuque--就是放进去的内容,延迟时间到了后才可以获得,DelayQueue是一个没有大小限制的队列,因此往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞。
- LinkedBlockDeque--双端队列 :offerFirst/offerLast,pollFirst/pollLast
- LinkedTransferQueue--类似LinkedUnBlockedQueue,其实就是transfer方法有人再等待队列内容就直接给他这个元素,没人在等就放在队列里面。也就是效率会更高。
附录2-Java线程状态完全解析教程
- https://blog.csdn.net/x541211190/article/details/109425645
- Java线程有6种状态,分别是NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED
- 初始(NEW):新创建了一个线程对象,但还没有调用start()方法时的状态。
- 运行(RUNNABLE):Java线程中将操作系统中的就绪(ready)和运行中(running)两种状态笼统的称为“可运行”RUNNABLE状态。线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。所以调用了start()方法并不意味这线程会立即执行。
- 阻塞(BLOCKED):表示线程阻塞于锁。仅在Synchronized代码块中,且没有获得锁的状态。等待的是其他线程释放排他锁。
- 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。无期限等待。等待被唤醒。
- 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。有时间期限的等待。等待一段设置好的时间。
- 终止(TERMINATED):表示该线程已经执行完毕。线程正常的执行完毕,会进入到这种状态。或者是run()方法中出现了异常,线程会意外终止。
附录3-创建线程的两种方式
- 根据Oracle官方文档介绍,Java提供了两种线程的创建方法,第一种是继承Thread类;第二种是实现Runable接口,并将Runnable实例传递给Thread类。
- 继承Thread类,重写Run方法的方式:
- 优点:方便传参,可以在子类添加成员变量,通过方法设置参数或构造函数传参。
- 缺点:
- 因为Java不支持多继承,所以继承了Thread类以后,就无法继承其他类。
- 每次都要新建一个类,不支持通过线程池操作,创建和销毁线程对资源的开销比较大。
- 从代码结构上讲,为了启动一个线程任务,都要创建一个类,耦合性太高。
- 无法获取线程任务的返回结果。
//定义
public class MyThread extends Thread {
public void run(){
//做操作
}
}
//调用
new MyThread().start();
- 实现Runnable接口run方法,并把Runnable对象传递给Thread类的方式
- 优点:此方式可以继承其他类。也可以使用线程池管理,节约资源。创建线程代码的耦合性较低。推荐使用此种方式创建线程。
- 缺点:不方便传参,只能使用主线程中用final修饰的变量。其次是无法获取线程任务的返回结果。
//定义
public class MyRunnable implements Runnable{
@Override
public void run() {
//做操作
}
}
//调用
new Thread(new MyRunnable(), "name").start();
//匿名类
new Thread(new Runnable() {
@Override
public void run() {
//做操作
}
}, "name").start();
new Thread("name"){
}.start();
//lambda表达式
new Thread(()->{}, "name").start();
附录4-有返回值的创建线程
- 实现Callable方式可以获取到线程任务的返回值方式
- 此种方式创建线程底层源码也是使用实现Runnable接口的方式实现的,所以不是一种新的创建线程的方式,只是在实现Runnable接口方式创建线程的基础上,同时实现了Future接口,实现有返回值的创建线程。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* @project JavaSE
* @description: 创建有返回值的线程
* @author: 大脑补丁
* @create: 2020-05-08 21:19
*/
//
public class TestCallable implements Callable<String> {
@Override
public String call() throws Exception {
//自定义一个线程返回值,本例定义为String类型返回值
return "hello world";
}
//测试示例:
public static void main(String[] args) {
//创建异步任务,参数为本例对象
FutureTask<String> futureTask = new FutureTask<>(new TestCallable());
// 启动线程
new Thread(futureTask).start();
try {
//获取线程的返回值
String result = futureTask.get();
System.out.println(result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
————————————————
版权声明:本文为CSDN博主「大脑补丁」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/x541211190/article/details/73739606
附录5-线程的启动为什么不能直接调用run方法,而是调用start方法?
- 原因:
- 通过调用run()方法,是在主线程中执行任务,所以本质上并没有创建出新的线程。
- 通过调用start()方法,是在主线程中创建一个子线程去执行任务,这才是创建新线程去执行。通过start()方法启动线程后,并不一定立即执行,而是由线程调度器决定何时运行,可能立刻就会运行,也可能稍后才会运行,也可能一直不运行(饥饿状态)。
- 两种方式都能成功运行并执行,只是直接调用run()方法,并没有使用新线程去运行任务,程序还是串行执行的,所以这种方式是不符合预期的。
附录6-如果线程连续调用两次start()方法,会怎样?
- https://blog.csdn.net/x541211190/article/details/73739606
- 运行结果:抛出异常
- 原因分析:通过查看start()方法源码可以发现,之所以抛出java.lang.IllegalThreadStateException,是因为threadStatus不为0;threadStatus为0,表示线程刚初始化完成,还没有启动。若threadStatus不为0,说明线程已经被启动过了。所以第二次调用start()方法时,线程的状态threadStatus已改变,此时会抛出异常。
- 例子
public class ThreadClass extends Thread {
@Override
public void run() {
System.out.println("运行Thread线程");
}
public static void main(String[] args) {
ThreadClass threadClass = new ThreadClass();
threadClass.start();
threadClass.start();
}
}
附录7-java多线程对变量自增
- lambda表达式可以操作对象的实例属性,可以操作类的静态属性,不能操作main方法中定义的局部变量
- 写在{}中的匿名类可以操作类的静态属性,不能操作对象的实例属性,不能操作main方法中定义的局部变量
- 写在()中的匿名类可以操作类的静态属性,不能操作对象的实例属性,不能操作main方法中定义的局部变量
- lambda表达式1-写在Thread的()中-线程中操作对象的实例属性
public class test12 {
int num = 0; //要操作的变量
public static void main(String[] args) {
test12 data = new test12();
for(int i=0; i<100; i++){
new Thread(()->{
for(int j=0; j<100; j++){
data.num++; //线程中操作对象的实例属性
}
}).start();
}
System.out.println(data.num);
}
}
- lambda表达式2-写在Thread的()中-线程中操作类的静态属性
public class test12 {
static int num = 0; //要操作的变量
public static void main(String[] args) {
for(int i=0; i<100; i++){
new Thread(()->{
for(int j=0; j<100; j++){
num++; //线程中操作类的静态属性
}
}).start();
}
System.out.println(num);
}
}
- lambda表达式3-写在Thread的()中-错误写法-线程中操作main方法中的非final局部变量-从lambda表达式引用的本地变量必须是最终变量或实际上的最终变量
public class test12 {
public static void main(String[] args) {
int num = 0; //要操作的变量
for(int i=0; i<100; i++){
new Thread(()->{
for(int j=0; j<100; j++){
num++; //报错:从lambda表达式引用的本地变量必须是最终变量或实际上的最终变量
}
}).start();
}
System.out.println(num);
}
}
- 匿名类1-写在Thread(){}中-线程中操作类的静态属性
public class test12 {
static int num = 0;
public static void main(String[] args) {
for(int i=0; i<100; i++){
new Thread(){
public void run(){
for(int j=0; j<100; j++){
num++;
}
}
}.start();
}
System.out.println(num);
}
}
- 匿名类2-写在Thread(){}中-错误写法-线程中操作对象实例属性-java: 无法从静态上下文中引用非静态 变量 num
public class test12 {
int num = 0;
public static void main(String[] args) {
test12 myData = new test12();
for(int i=0; i<100; i++){
new Thread(){
public void run(){
for(int j=0; j<100; j++){
test12.num++;
}
}
}.start();
}
System.out.println(test12.num);
}
}
- 匿名类3-写在Thread(){}中-错误写法-线程中操作main函数中定义的局部变量-java: 从内部类引用的本地变量必须是最终变量或实际上的最终变量
public class test12 {
int num = 0;
public static void main(String[] args) {
test12 myData = new test12();
for(int i=0; i<100; i++){
new Thread(){
public void run(){
for(int j=0; j<100; j++){
test12.num++;
}
}
}.start();
}
System.out.println(test12.num);
}
}
- 匿名类4-写在Thread()中-线程中操作类的静态属性
public class test12 {
static int num = 0;
public static void main(String[] args) {
for(int i=0; i<100; i++){
new Thread(new Runnable() {
@Override
public void run() {
for(int j=0; j<100; j++){
num++;
}
}
}).start();
}
System.out.println(num);
}
}
- 匿名类5-写在Thread()中-线程中操作对象实例属性-java: 无法从静态上下文中引用非静态 变量 num
public class test12 {
int num;
public static void main(String[] args) {
test12 myData = new test12();
myData.num = 0;
for(int i=0; i<100; i++){
new Thread(new Runnable() {
@Override
public void run() {
for(int j=0; j<100; j++){
test12.num++;
}
}
}).start();
}
System.out.println(test12.num);
}
}
- 匿名类6-写在Thread()中-错误写法-线程中操作main函数中定义的局部变量-java: 从内部类引用的本地变量必须是最终变量或实际上的最终变量
public class test12 {
public static void main(String[] args) {
int num = 0;
for(int i=0; i<100; i++){
new Thread(new Runnable() {
@Override
public void run() {
for(int j=0; j<100; j++){
num++;
}
}
}).start();
}
System.out.println(num);
}
}
附录8-归纳
- 开启多线程,线程操作,线程池
- 可见性(volatile),有序性
- 原子性(内置锁、显示锁、CAS与原子类、内置/高效容器类、AQS抽象同步器)
- 高并发设计模式(单例模式、生产者-消费者、master-worker、fork-join、Future、异步回调)
行动是治愈恐惧的良药,而犹豫拖延将不断滋养恐惧。