Ch00_JavaThread
# JavaThread
线程创建与执行的基本方法:
应用1:猫狗喝水
完全解耦做法:只创建一个house线程类,通过Thread.currentThread.getName来区分猫狗
使用sleep来实现轮流,同步


1. Thread.sleep(1000),线程在1000毫秒后是否会被唤醒?
2. Thread.sleep(0),有什么用?
UNIX的cpu竞争是用的时间片算法:
- 时间片算法:存在一张就绪进程列表,当一个线程的时间片用完时,会把该线程排到队列尾,如果在一个时间片内结束,CPU会马上进行切换
WIN的cpu竞争策略是抢占式:
- 抢占式:当一个进程获得了CPU使用权,除非自己放弃CPU,否则会一直霸占CPU,该操作系统假设所有的进程都是“人品很好的”,会主动退出CPU。(当然会有算法检测,长时间霸占会强制挂起)
Sleep函数的意思是告诉操作系统,我在未来多少毫秒内不参与CPU的竞争
所以:
不一定会被唤醒,因为参与竞争不代表能马上占用CPU,类似Resume函数,告诉CPU我要参与竞争了。
有用,Thread.sleep(0),的作用就是触发操作系统重新进行一次CPU竞争,所以我们在大循环中经常会加上这句话,这样就给了其他线程比如paint线程获取CPU的能力,这样界面就不会出现假死。(并不是原来不竞争,而是增加大循环被打断的几率)

- 在多线程中即使使用volatile都不能使i++保证原子性,因为i++的操作是可分割的,所以要保证原子性可以用原子类AtomicInteger以及其提供的方法


Callable的实现方法:
public class Thread04 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ThreadTest04 th = new ThreadTest04();
FutureTask<String> fu = new FutureTask<String>(th);//先要把实现callable接口的线程注册到Futuretask里
new Thread(fu).start();//再new线程来执行这个task
System.out.println(fu.get());//获取线程执行的返回值
}
}
class ThreadTest04 implements Callable<String>{
@Override
public String call() throws Exception {
System.out.println("start");
return "success";
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17


使用Callable、Future和FutureTask的好处

java定义线程有两种方式:
- 实现Runnable接口
- 继承Thread类(Thread类也实现了Runnable接口)
在多线程中多使用接口的方式
线程的状态有6种:
- 新建状态NEW:刚刚new出来,并没有启动
- 可运行状态RUNNABLE:调用start方法时的状态,但是不一定竞争的到cpu资源
- 阻塞BLOCKED
- 等待WAITING:调用不带超时的Object的wait()方法、不带超时的Thread的join()方法、LockSupport的park()方法
- 超时等待TIMED_WAITING:调用带有指定正等待时间的Object的wait()方法、Thread的join()方法、Thread的sleep()方法、LockSupport(不可重入锁)的parkNanos()方法、LockSupport的parkUntil()方法
- 终止状态TERMINATED:线程调用终止或者run()方法执行结束后,线程即处于终止状态
Thread类中的实例方法:
- start()
- run()
- getId()
- getName()
- setPriority(int priority) 和 getPriority(): 优先级越高越优先执行
- isDaeMon() 和 setDaeMon(boolean on): 设置和判断是否为守护线程,set必须在线程start之前
- interrupt(): 线程遭到阻塞时抛出的中断信号,这样就可以退出阻塞状态,并不能中断线程
- isInterrupted():检测线程是否已经中断
- join(): 会使调用该线程的线程阻塞,直到该线程执行完毕,当然也可以指定超时时间
wait和sleep都会让出cpu资源


Thread中的静态方法:
- currentThread()方法是返回当前正在执行的线程对象的引用
- sleep():让线程休眠一段时间,并不释放锁
- yield():暂停当前执行的线程对象,并执行其他线程。这个暂停是会放弃CPU资源的,并且放弃CPU的时间不确定,有可能刚放弃,就获得CPU资源了,也有可能放弃好一会儿,才会被CPU执行
- interrupted():测试当前线程是否已经中断,执行后具有将状态标识清除为false的功能。换句话说,如果连续两次调用该方法,那么返回的必定是false
syncronized锁是可以重入的,也适用于父子类继承的环境中,线程中的代码抛出异常后,会自动释放锁
避免死锁的方法:synchronized不具备这个功能,但是我们可以使用Lock类中的tryLock方法去尝试获取锁,这个方法可以指定一个超时时限,在等待超过该时限之后变回返回一个失败信息
ThreadLocal
ThreadLocal用于保存某个线程共享变量:对于同一个static ThreadLocal,不同线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量。
1、ThreadLocal.get: 获取ThreadLocal中当前线程共享变量的值。
2、ThreadLocal.set: 设置ThreadLocal中当前线程共享变量的值。
3、ThreadLocal.remove: 移除ThreadLocal中当前线程共享变量的值。
4、ThreadLocal.initialValue: ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值。
ThreadLocal使用场景为 用来解决 数据库连接、Session管理等。
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {
public Connection initialValue() {
return DriverManager.getConnection(DB_URL);
}
};
public static Connection getConnection() {
return connectionHolder.get();
}
2
3
4
5
6
7
8
9
原理

threadLocals是ThreadLocalMap类型的变量

线程共享变量缓存如下:

这种存储结构的好处:
1、线程死去的时候,线程共享变量ThreadLocalMap则销毁。
2、ThreadLocalMap<ThreadLocal,Object>键值对数量为ThreadLocal的数量,一般来说ThreadLocal数量很少,相比在ThreadLocal中用Map<Thread, Object>键值对存储线程共享变量(Thread数量一般来说比ThreadLocal数量多),性能提高很多。
关于ThreadLocalMap<ThreadLocal, Object>弱引用问题:(ThreadLocal是弱引用类型的)
当线程没有结束,但是ThreadLocal已经被回收,则可能导致线程中存在ThreadLocalMap<null, Object>的键值对,造成内存泄露。(ThreadLocal被回收,ThreadLocal关联的线程共享变量还存在)。
虽然ThreadLocal的get,set方法可以清除ThreadLocalMap中key为null的value,但是get,set方法在内存泄露后并不会必然调用,所以为了防止此类情况的出现,我们有两种手段。
1、使用完线程共享变量后,显示调用ThreadLocalMap.remove方法清除线程共享变量;
2、JDK建议ThreadLocal定义为private static,这样ThreadLocal的弱引用问题则不存在了。
# ReetrantLock:可重入的互斥锁
可以借助condition来精确唤醒进程,和syncronize一样,在await()和signal()之前都要先获得锁,await()是释放锁的,singnal()可以唤醒进程,但是不能忘记要在finally中手动释放锁lock.unlock().
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void await()
{
try
{
lock.lock();
System.out.println("await时间为:" + System.currentTimeMillis());
condition.await();
System.out.println("await等待结束");
}
catch (InterruptedException e)
{
e.printStackTrace();
}
finally
{
lock.unlock();
}
}
public void signal()
{
try
{
lock.lock();
System.out.println("signal时间为:" + System.currentTimeMillis());
condition.signal();
}
finally
{
lock.unlock();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
另一大特点是可以设置公平锁或者非公平锁,所谓公平锁就是先排队先获得锁,syncronize是非公平锁,可能导致有些进程一直抢不到锁。要实现公平锁,只要在构造的时候传入true参数就可以
private Lock lock = new ReentrantLock(true);
- getHoldCount()方法返回的是当前线程调用lock()的次数
- getQueueLength()方法用于获取正等待获取此锁定的线程估计数。注意"估计"两个字,因为此方法遍历内部数据结构的同时,线程的数据可能动态变化
- isFair()用来获取此锁是否公平锁
- hasQueuedThread(Thread thread)用来查询指定的线程是否正在等待获取指定的对象监视器
- hasQueuedThreads()用于查询是否有线程正在等待获取指定的对象监视器
- isHeldByCurrentThread()表示此对象监视器是否由当前线程保持
- isLocked()表示此对象监视器是否由任意线程保持
- tryLock()方法的作用是,在调用try()方法的时候,如果锁没有被另外一个线程持有,那么就获得该锁并返回true,否则返回false
- tryLock(long timeout, TimeUnit unit)是tryLock()另一个重要的重载方法,表示如果在指定等待时间内获得了锁,则返回true,否则返回false
- 1、getWaitQueueLength(Condition condition) 类似getQueueLength(),不过此方法的前提是condition。比如5个线程,每个线程都执行了同一个condition的await()方法,那么方法调用的返回值是5,因为5个线程都在等待获得锁
- hasWaiters(Condition condition)查询是否有线程正在等待与此锁有关的condition条件。比如5个线程,每个线程都执行了同一个condition的await()方法,那么方法调用的返回值是true,因为它们都在等待condition
- lockInterruptibly() 如果当前线程未被中断,则获取锁
- getWaitingThreads(Condition condition)返回一个collection,它包含可能正在等待与此锁相关给定条件的那些线程,因为构造结果的时候实际线程可能动态变化,因此返回的collection只是尽力的估计值
# 读写锁ReentrantReadWriteLock
ReentrantLock虽然具有完全互斥排他的效果(即同一时间只有一个线程正在执行lock后面的任务),但是效率非常低。所以在JDK中提供了一种读写锁ReentrantReadWriteLock,使用它可以加快运行效率。
读写锁表示两个锁,一个是读操作相关的锁,称为共享锁;另一个是写操作相关的锁,称为排他锁。多个Thread可以同时进行读取操作,但是同一时刻只允许一个Thread进行写入操作。
readLock().lock();
readLock().unlock();
2
sychronized 和reentrantlock都是可重入的锁

# BlockingQueue
阻塞队列所谓的"阻塞",指的是某些情况下线程会挂起(即阻塞),一旦条件满足,被挂起的线程又会自动唤醒。使用BlockingQueue,不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,这些内容BlockingQueue都已经做好了
BlockingQueue既然是Queue的子接口,必然有Queue中的方法,以下为特有的方法:
1)void put(E e) throws InterruptedException
把e添加进BlockingQueue中,如果BlockingQueue中没有空间,则调用线程被阻塞,进入等待状态,直到BlockingQueue中有空间再继续
2)void take() throws InterruptedException
取走BlockingQueue里面排在首位的对象,如果BlockingQueue为空,则调用线程被阻塞,进入等待状态,直到BlockingQueue有新的数据被加入
3)int drainTo(Collection<? super E> c, int maxElements)
一次性取走BlockingQueue中的数据到c中,可以指定取的个数。通过该方法可以提升获取数据效率,不需要多次分批加锁或释放锁
另外几种队列实现方式


另外,也不要担心非单一生产者/消费者场景下的系统假死问题,缓冲区空、缓冲区满的场景BlockingQueue都是定义了不同的Condition,所以不会唤醒自己的同类。

- 有单级关联,也有多级关联(树形),一般开发中使用单级关联
- 线程组有自动关联特性,也就是在一个线程中创建一个线程组,这个线程组自动归属于当前线程组中,也就是成为了当前线程组的子线程组
- 根线程组就是系统线程组system
- 批量停止组内线程:调用了ThreadGroup中的interrupt()方法批量中断了线程组内的线程
# 中断:
1.中断只是设置下标志位,true or false
2.标识位设不设置是别人的事情,处不处理是线程自己的事情,没有强制要求必须处理中断
# 线程池:
线程池的作用就2个:
1、减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务
2、可以根据系统的承受能力,调整线程池中工作线程的数据,防止因为消耗过多的内存导致服务器崩溃
线程池的类结构图:

ThreadPoolExecutor六个核心参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
2
3
4
5
6
7

ThreadPoolExecutor的使用很简单,前面的代码也写过例子了。通过execute(Runnable command)方法来发起一个任务的执行,通过shutDown()方法来对已经提交的任务做一个有效的关闭。尽管线程池很好,但我们要注意JDK API的一段话:
!!强烈建议程序员使用较为方便的Executors工厂方法!!
- Executors.newCachedThreadPool()(无界线程池,可以进行线程自动回收):
无界线程池,意思是不管多少任务提交进来,都直接运行。无界线程池采用了SynchronousQueue,采用这个线程池就没有workQueue容量一说了,只要添加进去的线程就会被拿去用。既然是无界线程池,那线程数肯定没上限,所以以maximumPoolSize为主了,设置为一个近似的无限大Integer.MAX_VALUE。 另外注意一下,单线程线程池和固定大小线程池线程都不会进行自动回收的,也即是说保证提交进来的任务最终都会被处理,但至于什么时候处理,就要看处理能力了。但是无界线程池是设置了回收时间的,由于corePoolSize为0,所以只要60秒没有被用到的线程都会被直接移除
- Executors.newFixedThreadPool(int)(固定大小线程池):
固定大小的线程池和单线程的线程池异曲同工,无非是让线程池中能运行的线程编程了手动指定的nThreads罢了。同样,由于是选择了LinkedBlockingQueue,因此其实第二个参数maximumPoolSize同样也是无意义的
- Executors.newSingleThreadExecutor()(单个后台线程):
单线程线程池,那么线程池中运行的线程数肯定是1。workQueue选择了无界的LinkedBlockingQueue,那么不管来多少任务都排队,前面一个任务执行完毕,再执行队列中的线程。从这个角度讲,第二个参数maximumPoolSize是没有意义的,因为maximumPoolSize描述的是排队的任务多过workQueue的容量,线程池中最多只能容纳maximumPoolSize个任务,现在workQueue是无界的,也就是说排队的任务永远不会多过workQueue的容量,那maximum其实设置多少都无所谓了

# 定时器Timer
Timer可以指定任务的执行时间
**1. Timer的schedule(TimeTask task, Date time):**在执行的日期执行一次任务,如果早于当前时间,则立即执行,但是不保证一定在这个时间执行,万一前一个任务执行时间长,就会延后执行
- 一个timmer中可以注册多个任务
2. TimerTask的cancel()方法:将自身从任务队列中清除
**3. Timer的schedule(TimerTask task, Date firstTime, long period):**在指定的日期之后,按指定的间隔周期性地无限循环地执行某一任务
private static Timer timer = new Timer();
static public class MyTask extends TimerTask
{
public void run()
{
System.out.println("运行了!时间为:" + new Date());
}
}
public static void main(String[] args) throws Exception
{
MyTask task = new MyTask();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString = "2015-10-6 12:14:00";
Date dateRef = sdf.parse(dateString);
System.out.println("字符串时间:" + dateRef.toLocaleString() + " 当前时间:" + new Date().toLocaleString());
timer.schedule(task, dateRef);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# CountDownLatch:多个任务完成后通知多个任务继续,
传统的wait和notify只能实现一个任务完成之后通知其他任务继续执行
private static class WorkThread extends Thread
{
private CountDownLatch cdl;
private int sleepSecond;
public WorkThread(String name, CountDownLatch cdl, int sleepSecond)
{
super(name);
this.cdl = cdl;
this.sleepSecond = sleepSecond;
}
public void run()
{
try
{
System.out.println(this.getName() + "启动了,时间为" + System.currentTimeMillis());
Thread.sleep(sleepSecond * 1000);
cdl.countDown();
System.out.println(this.getName() + "执行完了,时间为" + System.currentTimeMillis());
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
private static class DoneThread extends Thread
{
private CountDownLatch cdl;
public DoneThread(String name, CountDownLatch cdl)
{
super(name);
this.cdl = cdl;
}
public void run()
{
try
{
System.out.println(this.getName() + "要等待了, 时间为" + System.currentTimeMillis());
cdl.await();
System.out.println(this.getName() + "等待完了, 时间为" + System.currentTimeMillis());
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
public static void main(String[] args) throws Exception
{
CountDownLatch cdl = new CountDownLatch(3);//这边设置多少任务完成之后唤醒await的任务
DoneThread dt0 = new DoneThread("DoneThread1", cdl);
DoneThread dt1 = new DoneThread("DoneThread2", cdl);
dt0.start();
dt1.start();
WorkThread wt0 = new WorkThread("WorkThread1", cdl, 2);
WorkThread wt1 = new WorkThread("WorkThread2", cdl, 3);
WorkThread wt2 = new WorkThread("WorkThread3", cdl, 4);
wt0.start();
wt1.start();
wt2.start();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
Semaphore
并发控制器,是用于管理信号量的。构造的时候传入可供管理的信号量的数值,这个数值就是控制并发数量的,我们需要控制并发的代码,执行前先通过acquire方法获取信号,执行后通过release归还信号 。每次acquire返回成功后,Semaphore可用的信号量就会减少一个,如果没有可用的信号,acquire调用就会阻塞,等待有release调用释放信号后,acquire才会得到信号并返回。
Semaphore分为单值和多值两种:
1、单值的Semaphore管理的信号量只有1个,该信号量只能被1个,只能被一个线程所获得,意味着并发的代码只能被一个线程运行,这就相当于是一个互斥锁了
2、多值的Semaphore管理的信号量多余1个,主要用于控制并发数
public static void main(String[] args)
{
final Semaphore semaphore = new Semaphore(5);
Runnable runnable = new Runnable()
{
public void run()
{
try
{
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "获得了信号量,时间为" + System.currentTimeMillis());
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "释放了信号量,时间为" + System.currentTimeMillis());
}
catch (InterruptedException e)
{
e.printStackTrace();
}
finally
{
semaphore.release();
}
}
};
Thread[] threads = new Thread[10];
for (int i = 0; i < threads.length; i++)
threads[i] = new Thread(runnable);
for (int i = 0; i < threads.length; i++)
threads[i].start();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Semaphore可以指定公平锁还是非公平锁
acquire方法和release方法是可以有参数的,表示获取/返还的信号量个数
# Exchanger
Exchanger用于在两个线程之间进行数据交换,注意也只能在两个线程之间进行数据交换。线程会阻塞在Exchanger的exchange方法上,直到另外一个线程也到了同一个Exchanger的exchange方法时,二者进行数据交换,然后两个线程继续执行自身相关的代码。
public static class ExchangerThread extends Thread
{
private String str;
private Exchanger<String> exchanger;
private int sleepSecond;
public ExchangerThread(String str, Exchanger<String> exchanger, int sleepSecond)
{
this.str = str;
this.exchanger = exchanger;
this.sleepSecond = sleepSecond;
}
public void run()
{
try
{
System.out.println(this.getName() + "启动, 原数据为" + str + ", 时间为" + System.currentTimeMillis());
Thread.sleep(sleepSecond * 1000);
str = exchanger.exchange(str);
System.out.println(this.getName() + "交换了数据, 交换后的数据为" + str + ", 时间为" + System.currentTimeMillis());
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
public static void main(String[] args)
{
Exchanger<String> exchanger = new Exchanger<String>();
ExchangerThread et0 = new ExchangerThread("111", exchanger, 3);
ExchangerThread et1 = new ExchangerThread("222", exchanger, 2);
et0.start();
et1.start();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# CyclicBarrier
CyclicBarrier从字面理解是指循环屏障,它可以协同多个线程,让多个线程在这个屏障前等待,直到所有线程都达到了这个屏障时,再一起继续执行后面的动作。
public static class CyclicBarrierThread extends Thread
{
private CyclicBarrier cb;
private int sleepSecond;
public CyclicBarrierThread(CyclicBarrier cb, int sleepSecond)
{
this.cb = cb;
this.sleepSecond = sleepSecond;
}
public void run()
{
try
{
System.out.println(this.getName() + "运行了");
Thread.sleep(sleepSecond * 1000);
System.out.println(this.getName() + "准备等待了, 时间为" + System.currentTimeMillis());
cb.await();
System.out.println(this.getName() + "结束等待了, 时间为" + System.currentTimeMillis());
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
public static void main(String[] args)
{
Runnable runnable = new Runnable()
{
public void run()
{
System.out.println("CyclicBarrier的所有线程await()结束了,我运行了, 时间为" + System.currentTimeMillis());
}
};
CyclicBarrier cb = new CyclicBarrier(3, runnable);
CyclicBarrierThread cbt0 = new CyclicBarrierThread(cb, 3);
CyclicBarrierThread cbt1 = new CyclicBarrierThread(cb, 6);
CyclicBarrierThread cbt2 = new CyclicBarrierThread(cb, 9);
cbt0.start();
cbt1.start();
cbt2.start();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

可能有人觉得CyclicBarrier和CountDownLatch有点像,都是多个线程等待相互完成之后,再执行后面的代码。实际上,CountDownLatch和CyclicBarrier都是用于多个线程间的协调的,它们二者的几个差别是:
1、CountDownLatch是在多个线程都进行了latch.countDown()后才会触发事件,唤醒await()在latch上的线程,而执行countDown()的线程,执行完countDown()后会继续自己线程的工作;CyclicBarrier是一个栅栏,用于同步所有调用await()方法的线程,线程执行了await()方法之后并不会执行之后的代码,而只有当执行await()方法的线程数等于指定的parties之后,这些执行了await()方法的线程才会同时运行
2、CountDownLatch不能循环使用,计数器减为0就减为0了,不能被重置;CyclicBarrier提供了reset()方法,支持循环使用
3、CountDownLatch当调用countDown()方法的线程数等于指定的数量之后,可以唤起多条线程的任务;CyclicBarrier当执行await()方法的线程等于指定的数量之后,只能唤起一个BarrierAction
注意,因为使用CyclicBarrier的线程都会阻塞在await方法上,所以在线程池中使用CyclicBarrier时要特别小心,如果线程池的线程过少,那么就会发生死锁了