java多线程基础学习笔记
关于线程与进程
1.线程包含在进程里,一个进程包含多个线程,至少包含一个。
2.在Java中使用抢占式调度
实现多线程的三种方式
1.继承Thread类
通过继承Thread,声明为Thread的子类来实现多线程,在该子类中需要重写Thread中的run方法,
然后创建该子类的实现类的实例,调用start()方法来创建并且开启一个线程。
class TestThread extends Thread {
TestThreadd() {
}
public void run() {
//需要处理的事件
}
}
//创建实例调用start()启动多线程
TestThread p = new TestThread();
p.start();
2.声明实现Runnable接口
声明实现Runnable接口,实现其中的run()方法,之后实例化该类,再创建Thread作为其参数来传递并启动,可以创建并启动一个线程。
class TestThread implements Runnable {
TestThreadd() {
}
public void run() {
//需要处理的事件
}
}
//创建实例作为Thread的参数通过调用start()启动多线程
TestThread p = new TestThread();
new Thread(p).start();
3.声明实现Callable接口
class TestCallable implements Callable<E e>{
public TestCallable(){
}
public E call(){
//需要处理的的事件
}
}
//1.创建执行服务
TestCallable t = new TestCallable();
ExecutorService ser = Executors.newFixedThreadPool(nThread: 线程池的大小);
//2.提交执行
Future<E> result=ser.submit(t);
//3.获取结果
E r=result.get();
//4.关闭服务,关闭线程池
ser.shutdownNow();
静态代理模式
真实对象和代理对象都要实现同一个接口
- public class Thread extends Object implements Runnable
代理对象需要有真实的对象
- new Thread(p).start();
代理对象可以做真实对象所做不了的事情,真实对象可以专注于做自己的事情
线程的状态
- 创建状态 new Thread
- 就绪状态 start() 等待cpu调度
- 运行状态 真正的被执行状态
- 阻塞状态 sleep,wait或者同步锁定状态,阻塞状态解除之后重新进入就绪状态,等待cpu调度执行
- 死亡状态 线程中断或者结束
1.线程停止
-
建议线程正常停止---->利用次数,不建议使用死循环
-
推荐使用标志位---->设置一个标志位
class TestStop implements Runnable{ private boolean flag=ture; @Override public void run(){ while(flag){ //需要处理的事件 } } } public void stop(){ this.flag=false; } TestStop testStop = new TestStop(); new Thread(testStop).start(); //设置一个停止的条件,调用stop()方法 testStop.stop();
2.线程休眠
-
每个对象都有一把锁,sleep()不会释放锁对象
-
可用来模拟网络延时
-
会抛出InterruptedException异常
Thread.sleep(millis) //以毫秒为单位 Thread.currentThread() //获取当前线程对象 System.currentTimeMillis() //获取当前系统时间
3.线程礼让
- 将线程转为就绪状态,不一定礼让成功
Thread.yield();
4.线程强制执行
- 合并该线程,待该线程执行完再执行其他线程,其他线程进入阻塞状。
thread.join();
5.测试线程的状态
-
六个状态
NEW
至今尚未启动的线程处于这种状态。
RUNNABLE
正在 Java 虚拟机中执行的线程处于这种状态。
BLOCKED
受阻塞并等待某个监视器锁的线程处于这种状态。
WAITING
无限期地等待另一个线程来执行某一特定操作的线程处于这种状态。
TIMED_WAITING
等待另一个线程来执行取决于指定等待时间的操作的线程处于这种状态。
TERMINATED
已退出的线程处于这种状态。
Thread.State state = thread.getState();
6.线程优先级
- 1最低 10最高 优先级高并不一定先执行
thread.getPriority() //获得当前线程的优先级
thread.setPriority() //设置当前线程的优先级
7.守护线程(daemon)
-
线程分为用户线程和守护线程
虚拟机必须确保用户线程执行完毕,不必确保守护线程执行完毕。
thread.setDaemon(true); //默认为false,表示是用户线程
线程同步
-
在处理多线时,多个线程访问同一个对象不安全,为了解决这个问题,需要线程同步机制,线程同步机制就是一个等待机制(排队),多个需要访问同一个对象的线程会进入对象的等待池,依次使用这个对象。
-
为了保证访问的正确性,在访问时加入了锁机制,关键词:synchronized
-
锁机制解决了线程安全问题但是也导致了其他问题
1.一个线程持有锁会导致其他需要该锁的线程被挂起。
2.在多线程竞争下,加锁释放锁会导致比较多的上下文切换引起的性能问题。
3.如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒挂,引起性能问题。
1.同步方法:synchronized
用synchronized修饰,默认锁对象即为this或者是其class属性。
2.同步块:synchronized(object o){}
object o称之为同步监视器,可以是任何o对象,但是推荐使用共享资源(增删改的对象)作为同步监视器。
3.死锁
-
两个或两个以上线程都在等待对方释放资源,从而都停止执行的情形。
-
某一个线程同时拥有“两个以上对象的锁时”,就可能回发生“死锁”的问题。
产生死锁的必要条件:
1.互斥条件:一个资源每次只能被一个进程使用
2.请求与保持条件:一个进程因为请求资源而阻塞时,对以获得的资源保持不放
3.不剥夺条件:进程以获得的资源,未使用完之前不能强行剥夺
4.循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
四个必要条件只要破其中的一个或多个条件即可解决
4.显式定义的同步锁 Lock(接口)
- ReentrantLock:可重入锁
- Lock只有代码块锁,JVM将花费较少的时间来调度线程,性能更好,扩展性高
ReentrantLock lock = new ReentrantLock(); //定义锁
lock.lock();
try(){
//不安全的代码块
}catch(Exception e){
//处理异常
}finally{
lock.unlock();//保证锁能够被正常释放
}
线程协作
线程通信,生产消费问题
-
java提供了几种解决线程之间通信的方法:
Object类包含了:wait(), wait(long timeout),notify(),notifyAll() ,这四种方法都只能在同步方法或同步代码块中使用。
管程法---->利用缓冲区解决
设立一个缓冲区存储资源,生产者在资源状态充足时等待,在资源状态不充足时生产,
消费者在资源充足时消费,在不充足时等待。缓冲区的状态即为生产资和消费者通信的桥梁。
信号灯法---->标志位解决
设置一个标志,根据标志状态的不同,生产者和消费者有不同的行为并对标志进行改变。
使用线程池
提前创建多个线程,使用时直接获取,避免重复销毁创建线程导致性能消耗。
class MyThread implements Runnnable{
public void run(){
//要处理的时间
}
}
ExecutorService sr = Executors.newFixedThreadPool(nThreads);
sr.execute(new MyThread); //没有返回值
sr.shutdown; //关闭线程池
ExecutorService:真正的线程池接口,使用Executors该工具类,线程池工厂类创建并返回不同类型的线程池。
void execute(Runnable command); //执行任务没有返回值
<T> Future<T> submit(Callable<T> task); //有返回值