并发与并行
类比在KTV唱歌,并发就是几个人轮流用一个麦克风,并行就是每人都有一个麦克风。
实现多线程的方式
继承Thread类
重写父类Thread的run方法,然后在其他地方实例化该类,并调用该对象的start方法,即启动此线程。
public class TestThread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("我在看代码。。。" + i);
}
}
public static void main(String[] args) {
TestThread1 testThread1 = new TestThread1();
testThread1.start();
for (int i = 0; i < 20; i++) {
System.out.println("我在学习。。。" + i);
}
}
}
实现Runnable接口
实现Runnable的run方法,在其他地方,实例化该对象,并实例化Thread类,传入该对象,然后调用Thread对象的start方法。
public class TestThread3 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("我在看代码。。。" + i);
}
}
public static void main(String[] args) {
TestThread3 testThread3 = new TestThread3();
Thread thread = new Thread(testThread3);
thread.start();
for (int i = 0; i < 20; i++) {
System.out.println("我在学习。。。" + i);
}
}
}
另外,使用这种方式,也可以直接传入Lambda表达式,因为Thread的一个构造方法传入的是Runnable接口对象,且Runnable接口只有一个抽象方法。
public class TestThread4{
public static void main(String[] args) {
new Thread(() -> System.out.println("我在看代码。。。")).start();
}
}
实现Callable接口
已很少使用
线程状态
线程休眠sleep
(抱着锁睡),线程从运行到阻塞,让出cpu使用权。
线程礼让yield
让出cpu使用权,但可能由于cpu调度算法,马上又获取到cpu使用权。
线程强制执行join
直接执行该线程(插队),其他线程阻塞,直到该线程运行完毕。
守护线程
JVM不用带单守护线程执行完毕,如后台记录操作日志,监控内存,垃圾回收等待;
设置线程对象的daemon为true,则当其他线程运行完毕,此线程也会结束(会伴有一定的延迟)。
线程同步
多个线程操作同一个资源。
synchronized
每一个对象都有一个锁(sleep不会释放锁),当一个线程获得对象的排他锁(使用关键字synchronized),独占资源,其他线程必须等待。使用后释放锁即可。
可以使用同步方法与同步块两种方式。
死锁
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,从而导致线程都停止运行。
某一个同步块同时拥有“两个以上对象的锁“时,就可能会发送“死锁”问题。
Lock锁
显示定义同步锁。
// 可重入锁 ReentrantLock lock = new ReentrantLock(); // 加锁 lock.lock(); // 解锁 lock.unlock();
线程池
ThreadPoolExecutor
构造函数的7个参数
- int corePoolSize:表示常驻核心线程数;int maximumPoolSize:表示线程能够容纳同时执行的最大线程数;long keepAliveTime:表示线程池中的线程空闲事件,当空闲时间达到keepAliveTime值时,线程会被销毁,知道只剩下corePoolSize个线程为止;TimeUnit unit:表示事件单位,一般为秒;BlockingQueue
- ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常;ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常;ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程);ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务 ;
线程池不允许使用Exectors,而是通过ThreadPoolExecutor的方式创建,这样的处理方式能更加明确线程池的运行规则,规避资源耗尽的风险。
ThreadLocal
ThreadLocal是用来隔离不同线程间的相同变量。
public class TestThreadLocal {
static ThreadLocal threadLocal = new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
// 设置两个线程,分别设置自己的ThreadLoacal,观察是否互相影响
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
if (i == 0) threadLocal.set(100);
else {
System.out.println(Thread.currentThread().getName() + " " + threadLocal.get());
threadLocal.set(100 + i);
}
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
if (i == 0) threadLocal.set(200);
else {
System.out.println(Thread.currentThread().getName() + " " + threadLocal.get());
threadLocal.set(200 + i);
}
}
});
t1.start();
t2.start();
}
}
输出结果如下,可以看到线程1和线程2的输出变量是相互隔离的。
可以根据实际变量的情况来命名threadLocal的名字。
三个重要方法- set():如果没有set操作的ThreadLocal,容易引起脏数据问题;get():始终没有get操作的ThreadLocal对象是没有意义的;remove():如果没有remove操作,容易引起内存泄漏;
线程池中使用ThreadLocal会产生这个问题,如果某个线程不显示调用remove()方法,且下一个线程不调用set()设置初始值,则可能get()到重用的线程信息。
内存泄漏


