目录
ThreadLocal和InheritableThreadLocal的区别
线程和进程的概念
说到线程就必须说进程,进程是系统分配应用的基本单位,进程可以理解为一个可以独立运行的程序单位,就好比开一个QQ就是一个进程(同时可以打开多个QQ,允许多个进程?),那么一个进程可以包含多个线程,比如打开一个qq聊天窗口就是一个线程(如果是聊天合并窗口,也只有一个线程)。
为什么要使用多线程?
使用单线程,在执行耗时较长的操作的时候,就会存在等待的情况。在多核CPU的条件下,为了提高CPU的利用率,可以使用多个线程去执行其他操作,减少耗时。
多线程有什么缺点?
1.使用多线程很消耗系统资源,因为多线程需要开辟内存,而且线程切换也是需要时间的。(多线程处理导致生产内存使用到80%告警)
2.线程的终止会对程序有影响。
3.多个线程之间存在共享数据,容易出现线程死锁的情况。
线程怎么创建?
//实现runnable接口 、继承 Thread,重写run方法、实现Callable
//1.runnable接口
public class MyThread implements Runnable{
@Override
public void run() {
System.out.println("this is run");
}
public static void main(String[] args) {
new Thread(new MyThread()).start();
}
}
//2. 继承 Thread 缺点不能再继承了。
public class MyThread extends Thread{
@Override
public void run() {
System.out.println("this is run");
}
public static void main(String[] args) {
new Thread(new MyThread()).start();
}
}
//3.Callable 有返回值
public class ExcutorsCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "test";
}
public static void main(String[] args) {
MyThread task =new MyThread();
FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
Thread thread = new Thread(futureTask);
thread.start();
}
}
线程的生命周期
包含5个阶段,包括:新建、就绪、运行、阻塞、销毁。
新建:就是刚使用new方法,new出来的线程;
就绪:就是调用的线程的start()方法后,这时候线程处于等待CPU分配资源阶段,谁先抢的CPU资源,谁开始执行;
运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run方法定义了线程的操作和功能;
阻塞:在运行状态的时候,可能因为某些原因导致运行状态的线程变成了阻塞状态,比如sleep()、wait()之后线程就处于了阻塞状态,这个时候需要其他机制将处于阻塞状态的线程唤醒,比如调用notify或者notifyAll()方法。唤醒的线程不会立刻执行run方法,它们要再次等待CPU分配资源进入运行状态;
销毁:如果线程正常执行完毕后或线程被提前强制性的终止或出现异常导致结束,那么线程就要被销毁,释放资源;
线程什么时候结束?
1.run方法正常执行完毕
2.run方法抛出异常
3. return+interrupt
4.睡眠中interrupt 一般不会这么写
5.stop 不推荐了 JDK作废方法
run方法抛出异常:
package com.demo.thread;
public class MyThread extends Thread {
public MyThread(String name) {
this.setName(name);
}
@Override
public void run() {
if (true) {
throw new RuntimeException("模拟业务异常");
}
System.out.println("run end");
}
public static void main(String[] args) {
MyThread myThread = new MyThread("t1");
myThread.start();
}
}
//可以看到后面 “run end” 不会执行了。
return+interrupt
package com.demo.thread;
public class MyThread extends Thread {
public MyThread(String name) {
this.setName(name);
}
@Override
public void run() {
while (true) {
if(this.isInterrupted()){
return;
}
System.out.println("run end");
}
}
public static void main(String[] args) {
MyThread myThread = new MyThread("t1");
myThread.start();
myThread.interrupt();
}
}
//“run end” 就不会执行了
interrupt不是真正的停止,是打个停止标记,是线程中断
interrupted 查看当前线程是否中断,清除状态,第二次调用就不是中断了。
isInterrupted 查看当前线程是否中断,不清除状态,第二次调用还是中断。
如何实现实例变量共享和不共享
不共享:
package com.demo.thread;
public class TestVarShareThread extends Thread {
private int count = 3;
public TestVarShareThread(String name) {
this.setName(name);
}
@Override
public void run() {
super.run();
while (count > 0) {
System.out.println(this.getName()+":"+count);
count--;
}
}
public static void main(String[] args) {
TestVarShareThread t1 = new TestVarShareThread("t1");
TestVarShareThread t2 = new TestVarShareThread("t2");
TestVarShareThread t3 = new TestVarShareThread("t3");//这样创建不共享
t1.start();
t2.start();
t3.start();
}
}
t2:3
t2:2
t2:1
t3:3
t3:2
t3:1
t1:3
t1:2
t1:1
共享:(线程不安全)
public class TestVarShareThread extends Thread {
private int count = 3;
@Override
public void run() {
super.run();
System.out.println(this.currentThread().getName() + ":" + count);
count--;
}
public static void main(String[] args) {
TestVarShareThread t = new TestVarShareThread();
Thread t1 = new Thread(t, "t1");
Thread t2 = new Thread(t, "t2");
Thread t3 = new Thread(t, "t3");
t1.start();
t2.start();
t3.start();
}
t1:3
t2:3
t3:1
改进:加锁
package com.demo.thread;
public class TestVarShareThread extends Thread {
private int count = 3;
@Override
synchronized public void run() {
super.run();
System.out.println(this.currentThread().getName() + ":" + count);
count--;
}
public static void main(String[] args) {
TestVarShareThread t = new TestVarShareThread();
Thread t1 = new Thread(t, "t1");
Thread t2 = new Thread(t, "t2");
Thread t3 = new Thread(t, "t3");
t1.start();
t2.start();
t3.start();
}
}
说说sleep suspend wait的区别
suspend是过时方法,容易引起死锁,没用过。
sleep是Thread的方法,当前线程睡眠,睡觉时间到,继续运行。提前运行用interrrupt
wait是Object类的方法,使线程处于不可运行状态,唤醒用notify
基本上wait/notify 和sleep/interrupt类似,只是前者需要获取对象锁。
getId() 线程唯一标识
什么是线程不安全?
多个线程对同一个对象中的实例变量进行操作时,会出现值被改或者值不同步的情况。
线程不安全的例子
package com.demo.thread;
public class ThreadTest {
public static void main(String[] args) {
TA t = new TA();
int i = 0;
while (i < 10) {
new Thread(t).start();
i++;
}
}
}
class TA implements Runnable {
private int cnt =10;
@Override
public void run() {
System.out.println(cnt);
cnt = cnt - 1;
}
}
10
9
8
8
6
5
4
3
2
1
改一下就不会出问题了:
package com.demo.thread;
public class ThreadTest {
public static void main(String[] args) {
int i = 0;
while (i < 10) {
TA t = new TA();
new Thread(t).start();
i++;
}
}
}
class TA implements Runnable {
private int cnt =10;
@Override
public void run() {
System.out.println(cnt);
cnt = cnt - 1;
}
}
为什么调用start方法会执行run方法
因为start里面调用了native方法,native方法不是java实现的,用其他语音实现调用了run方法。
start能重复调吗?
不能。Exception in thread "main" java.lang.IllegalThreadStateException ,而run可以
直接调用run方法是调用start方法有什么区别
直接调用run,就跟普通方法调用没区别,要等run方法执行完了才能继续往下执行,而start是开启一个线程去执行,不用等待,程序会继续往下走,run方法内容由线程去独立执行。
怎么理解join
t.join()方法的作用:使主线程t.join()后的内容进入等待池并等待t线程执行完毕后才会被唤醒。不影响同一时刻处在运行状态的其他线程。
//开启3个线程
t1.start();
t2.start();
t1.join();
sysout("this is main");
t3.start();
t1 和t2会正常执行,main方法运行到t1.join的时候,main方法进入等待状态,因此不会打印“this is main”,同样的t3.start()也不会开始,等t1运行结束了,才会继续往下执行,打印“this is main”和开始t3。注意:这里t2运行不受影响,该执行还是执行;而“this is main”和t3也不会受t2影响,只会等t1结束。
package com.demo.thread;
public class JoinTest {
public static void main(String[] args) throws InterruptedException {
MyThread t1 = new MyThread("t1");
MyThread t2 = new MyThread("t2");
MyThread t3 = new MyThread("t3");
System.out.println(1);
t1.start();//睡3秒
System.out.println(2);
t2.start();//睡3秒
t1.join();
System.out.println(3);
t3.start();//睡3秒
}
}
package com.demo.thread;
public class MyThread extends Thread {
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(this.name+" start");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.name+" end");
}
}
线程之间如何通信?
传统的方式,就是定义一个对象,里面属性是一个List,然后两个线程都引用了这个list,一个线程addlist,另一个线程根据list大小来判断通信。缺点while占用CPU资源
通过 wait/notify
volatile真的有用吗?
作用:解决了内存不可见性,保证每次取出来的值是最新的。(可是,这有啥用,至今没用上这关键字。)
我的尝试:多线程取数递增,加上volatile只能保证每次取最新的数字,但是不能保证两个线程不会取到同一个值。所以volatile不解决脏读。铁FW????
private volatile int cnt = 0;
cnt++
ExecutorService service = Executors.newFixedThreadPool(3);
int i = 0;
while (i < 50) {
service.execute(myThread);
i++;
}
最后会出现两个线程取到同一个数值的情况,也就是脏读。
ThreadLocal是这样使用的
private static ThreadLocal<List<String>> listInstance= new ThreadLocal<List<String>>() {
@Override
protected List<String> initialValue() {
return new ArrayList<>();
}
};
listInstance.get().add("xxxx");
这样取出来的数据就是相互独立的了。
ThreadLocal也叫线程本地变量,就是每个线程复制一个变量副本,所以线程之间互不干扰。
AtomicInteger的使用
ThreadLocal是线程之间变量独立,那么如果想共享一个变量,比如做计数,怎么做呢?
private int cnt;
cnt++;
这样会出现两个线程取到同一个数值的情况,脏读,或者说不是线程安全的。
正确的做法是:
private AtomicInteger batchNum = new AtomicInteger(0);
batchNum.incrementAndGet();// batchNum.get()
这个是线程安全的,保证不会出现脏读。
总结
1.多线程共享变量计数,用AtomicInteger。
2.每个线程独立使用变量,使用ThreadLocal。
3.没事别乱用volatile。
ThreadLocal和InheritableThreadLocal的区别
import com.demo.MyBean;
/**
* 永久性测试类,可覆盖
*/
public class Test {
private static ThreadLocal<MyBean> threadLocal = new ThreadLocal<>();
private static InheritableThreadLocal<MyBean> threadLocal2 = new InheritableThreadLocal<MyBean>();
public static void main(String[] args) {
MyBean myBean = new MyBean();
myBean.setName("22");
threadLocal.set(myBean);
threadLocal2.set(myBean);
System.out.println(threadLocal.get());
System.out.println(threadLocal2.get());
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(threadLocal.get());
System.out.println(threadLocal2.get());
}
}).start();
}
}
MyBean{id=0, name='22'}
MyBean{id=0, name='22'}
null
MyBean{id=0, name='22'}
InheritableThreadLocal 是ThreadLocal的子类,它的作用是父线程能给子线程传递值。用在父子线程共享变量的情况。