Java多线程
- 一个进程最多可以创建1000个线程,开一个线程大概要消耗1M的内存,超过这个线程数字将会出现线程泄漏。
- 时间片轮转机制
java中的Thread是对线程对象的唯一抽象,Runnable Callable 是对任务的抽象,一个线程里面可以执行很多的任务,例如UI线程 workthread线程 就是一个线程执行的多个任务。
Start方法只能调用一次,不能同时调用两次,会报错(IllegalThreadStateException)。而run就是一个成员函数,可以调用无数次。
/**使用
扩展Thread 类,复写其run方法。
*/
private static class UserThread extends Thread {
@Override
public void run() {
System.out.println("TestThread");
}
}
/**
* 实现Runnable任务对象,但是没有接口
*/
private static class UseRunnable implements Runnable{
@Override
public void run() {
System.out.println(" this is Runnable");
}
}
/*实现Callable 泛型接口 ,允许有返回值*/
private static class UseCallable implements Callable<String>{
@Override
public String call() throws Exception {
return " this is a UseCallable";
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
UserThread testThread = new UserThread();
testThread.start();
UseRunnable useRunnable = new UseRunnable();
new Thread(useRunnable).start();
FutureTask<String> futureTask = new FutureTask(new UseCallable());
new Thread(futureTask).start();
System.out.println(" UseCallable futureTask.get(); " + futureTask.get());
}
创建 thread 对象,传入的是一个Runnable接口,所以实现这个接口的对象都可以作为参数。
public Thread(Runnable target) {}
FutureTask extends Runnable, Future<V>{}
构造函数:
public FutureTask(Callable<V> callable) {}
public FutureTask(Runnable runnable, V result) {}
public interface Future<V> {
boolean cancel(boolean var1);
boolean isCancelled();
boolean isDone();
//获取线程的返回值
V get() throws InterruptedException, ExecutionException;
V get(long var1, TimeUnit var3) throws InterruptedException, ExecutionException, TimeoutException;
}
注意下面的run方法是在主线程中执行,run和一般的成员函数回调是一样的。
新线程的创建是在Start()方法后创建。run()会立即执行.
private static class UserThread extends Thread {
@Override
public void run() {
System.out.println("I am :" + Thread.currentThread() + " thread");
}
}
public static void main(String[] args) {
UserThread testThread = new UserThread();
testThread.run();
}
执行结果:I am ��Thread[main,5,main] thread
线程的优先级特别不靠谱,所以在架构的时候一般不使用线程的优先级来保证线程的执行顺序。
而是用jion() 方法来执行。
wait 和notify notifyAll 。
注意 wait 是主动将自己持有的锁释放的。
notify 不能去指定唤醒某一个线程。
一般的格式是:
syn(对象){
while (条件不满足) {
对象. wait();
}
执行业务代码
}
syn (对象) {
修改上面的条件
对象.notify /notifyAll;
}
syn 是内置锁,一旦某个线程开始获取锁的时候,但是这个锁被别的线程持有的时候,它将处于等待状态,线程获取锁的过程是不能被中断的,他会一直处于等待状态。所以时间长了会出现ANR
显示锁—Lock锁
获取锁的过程可以被中断
//他是一个接口:
public interface Lock {
ThreadLocal 讲解
每个线程里面都保存有一个ThreadLocalMap类型的变量。
每个线程不同,线程中包含的ThreadLocalMap对象不同
这样才是线程安全的。都是单独隔离出来的。
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
每个线程都会创建一个新的ThreadLocalMap对象赋值给每个线程的threadLocals 对象。就是说每个线程中都会拥有一个
ThreadLocalMap类型的变量,这个变量保存有给此threadLocal 设定的值(Value)。Key值为threadLocal对象。每个线程的Key值是一样的,Value值不同。
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
在Thread类中包含有:
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocalMap 类中就包含有Entry[] table;
每个Entry[]数组里面的值就是(ThreadLocal<?> k, Object v)键值对。
所以出现在同一个线程中会出现
ThreadLocal1=value1
ThreadLocal2=value2 同一个线程都互不干扰
可重入锁ReentrantLock
Re—entrant ----Lock (可以重复—进入–锁 )
synchronized 内部自己实现了可重入锁机制,这样当出现递归循环调用的时候,可以重复拿到锁,不会出现异常。可重入的锁可以防止自己拿自己的锁 出现死锁。synchronized 是非公平锁
public synchronized void inc(){
count++;
if (count == 10) {
return;
}
inc();
}
// 如果是true 就是公平锁,
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
ReentrantReadWriteLock 可重入的读写锁
ReentrantReadWriteLock 里面有两把锁,读的时候可以有多线程同时读,但是不能有线程来写。
在写的时候,不允许有别的线程来读和别的线程来写。
线程的6种状态
- 初始状态
- 运行状态(其中包含 就绪状态 / 运行状态, 如果当前线程被分配了时间片,他拿到了CPU 它就处于运行状态。如果它的时间片用完 或者被操作系统剥夺了 就会处于就绪状态)
- 等待状态, 如果在运行状态调用wait() 进入 等待状态,如果唤醒 从等待切换到运行状态
- 运行超时状态, 如果在运行状态下调用wait(long) 就是进入等待超时状态,时间到了就会自动回到运行状态
- 阻塞状态, 当前的线程调用了synchronized修饰的代码块,如果没有拿到锁,就会进入到阻塞状态,只有重新拿到锁,就会再次处于运行状态。
- 线程执行完就会终止状态
出现死锁的时候程序没有什么异常日志出现,程序会卡住不动,只有将程序退出才会恢复。
死锁的条件:
7. 互斥条件
8. 请求保持
9. 不剥夺
10. 环路等待
破坏锁的条件是1. 破坏锁的拿锁顺序
2. 拿到资源以后允许放手
活锁:
两个线程一直在拿锁 释放锁,但是两个一直都在这样的过程中操作
ThreadLocal 是线程本地变量,也叫线程本地存储。 ThreadLocal 可以让每个线程拥有一个属于自己的变量的副本,不会和其他线程的变量副本冲突,实现了线程的数据隔离。
一个类里面的静态内部类和外部类没有任何关系
static 变量在虚拟机就一份
.