三 多线程基础类
共享数据:
- 如果每个线程执行的代码相同,可以使用同一个Runnable对象,这个Runnable对象中有那个共享数据(共享一个继承了Runnable接口的对象)
- 如果每个线程执行的代码不同,这时候需要用不同的Runnable对象,实现一个Runnable接口匿名内部类来新建线程来操作同一个对象实例。
3.1 Thread
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
public static void main(String[] args) {
Thread myThread = new MyThread();
myThread.start();
}
stop():过期方法,
interrupt():仅仅是在当前线程中打了一个停止的标记,并不是真正的停止线程。
isInterrrupt():测试线程是否已经中断。不清楚状态
interrupted():测试(当前线程)是否已经发生中断。线程的中断状态由该方法清除。如果连续两次调用该方法,则第二次将返回false。
可以参考《java多线程编程核心技术》1.7小节的例子
3.2 Runnable
public class MyRunnable implements Runnable{
@Override
public void run() {
for(int i = 0;i < 5;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
public static void main(String[] args) {
//implements Runnable
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
}
start()并不是立即执行多线程代码,使得该线程变成可运行状态,具体什么时候运行由系统决定。
注意:Thread和Runnable区别
一个类继承Thread,则不适合资源共享,但是如果实现了Runnable接口,可以实现资源共享。可以定义成员变量。
- sleep():
- yield():
- Thread.currentThread.getName(),getId()
- setDaemon(),isDaemon():用来设置线程是否成为守护线程和判断线程是否为守护线程。当进程中不存在非守护线程了,则守护线程自动销毁。典型的就是垃圾回收机制。
3.3 Callable
之前的Runnable和Thread在执行完成任务之后无法获取执行结果。
Furtask实现了Runnable和Future接口
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <= 100000; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
sum += i;
}
return sum;
}
}
public class Test {
public static void main(String[] args) {
//implements Callable
MyCallable myCallable = new MyCallable();
FutureTask<Integer> futureTask = new FutureTask<>(myCallable);
new Thread(futureTask).start();
try {
//FutureTask 类似于CountDownLatch的作用,最后执行
Integer sum = futureTask.get();
System.out.println("sum:"+sum);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
注意:可以获取结果,call()方法可以抛出异常
四 线程安全-同步容器
ArrayList->Vector,Stack 用synchronized的线程同步容器,操作差异会导致安全性,remove,add
HashMap->HashTable(key不能为空),hashtable是用synchronized
Collections.synchronizedXX(List,Set,Map),也是synchronized也是性能差
例子:
@Slf4j
@ThreadSafe
public class VectorExample1 {
// 请求总数
public static int clientTotal = 5000;
// 同时并发执行的线程数
public static int threadTotal = 200;
private static List<Integer> list = new Vector<>();
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal; i++) {
final int count = i;
executorService.execute(() -> {
try {
semaphore.acquire();
update(count);
semaphore.release();
} catch (Exception e) {
log.error("exception", e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("size:{}", list.size());
}
private static void update(int i) {
list.add(i);
}
}
@NotThreadSafe
public class VectorExample2 {
private static Vector<Integer> vector = new Vector<>();
public static void main(String[] args) {
while (true) {
for (int i = 0; i < 10; i++) {
vector.add(i);
}
Thread thread1 = new Thread() {
public void run() {
for (int i = 0; i < vector.size(); i++) {
vector.remove(i);
}
}
};
Thread thread2 = new Thread() {
public void run() {
for (int i = 0; i < vector.size(); i++) {
vector.get(i);
}
}
};
thread1.start();
thread2.start();
}
}
}
@Slf4j
@ThreadSafe
public class CollectionsExample1 {
// 请求总数
public static int clientTotal = 5000;
// 同时并发执行的线程数
public static int threadTotal = 200;
private static List<Integer> list = Collections.synchronizedList(Lists.newArrayList());
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal; i++) {
final int count = i;
executorService.execute(() -> {
try {
semaphore.acquire();
update(count);
semaphore.release();
} catch (Exception e) {
log.error("exception", e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("size:{}", list.size());
}
private static void update(int i) {
list.add(i);
}
}
五 同步容器-并发容器J.U.C
ArrayList->copyOnWriteArrayList(线程不安全)
jdk8如下:
get 没有加锁
add 加锁,复制数组,再指向新数组
读写分离
缺点:数据读取不实时 但最终一致性,适合读多写少。
HashSet、TreeSet->CopyOnWriteArraySet
ConcurrentSkipListSet(自然排序)
add,remove操作是线程安全的,addAll(),removeAll()不是线程安全的
HashMap,TreeMap->ConcurrentHashMap 高并发
ConcurrentSkipListMap key是有序的,和线程数无关
六 AQS
详见博客 https://blog.csdn.net/xi15232131135/article/details/104519436
安全共享对象策略 - 总结
- 线程限制:一个被线程限制的对象,又线程独占,并且只能被占有它的现场修改
- 共享只读:一个共享只读的对象,在没有额外同步的情况下,可以被多个线程并发访问,但是任何线程都不能修改它。
- 线程安全对象:一个线程安全的对象或容器,在内部通过同步机制来保证线程安全,所以其他线程无需额外的同步就可以通过公共接口随意访问它。
- 被守护对象:被守护对象只能通过获取特定的锁来访问。