多线程及Thread类、Runnable接口

时间:2022-03-06 17:31:22

进程与线程

进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.

线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.

1.一个程序至少有一个进程,一个进程至少有一个线程。

2.线程的划分尺度小于进程,使得多线程程序的并发性高。

3.进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。

4.线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

5.从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。

每个线程都有一个优先级,高优先级线程的执行优先于低优先级线程。1-10 默认5

守护线程与非守护线程

守护线程是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分。因 此,当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。

关于守护线程的说法,正确的是:

A.所有非守护线程终止,即使存在守护线程,进程运行终止
B.所有守护线程终止,即使存在非守护线程,进程运行终止
C.只要有守护线程或者非守护线程其中之一存在,进程就不会终止
D.只要所有的守护线程和非守护线程终止运行之后,进程才会终止

守护线程与非守护线程并不是固定的,Thread类setDaemo属性可控制。daemo是Thread的私有属性,默认为false(非守护线程)。setDaemo必须在Thread.start()之前设置,否则会抛出一个IllegalThreadStateException异常,

public class Thread implements Runnable {
	private boolean daemon = false;

  

当 Java 虚拟机启动时,通常都会有单个非守护线程(它通常会调用某个指定类的 main 方法)。Java 虚拟机会继续执行线程,直到下列任一情况出现时为止:

  • 调用了 Runtime 类的 exit 方法,并且安全管理器允许退出操作发生。
  • 非守护线程的所有线程都已停止运行,无论是通过从对 run 方法的调用中返回,还是通过抛出一个传播到 run 方法之外的异常。

创建新执行线程有两种方法:

1.一种方法是将类声明为 Thread 的子类,该子类应重写 Thread 类的 run 方法。接下来可以分配并启动该子类的实例。

   class PrimeThread extends Thread {public void run() {
              . . .
         }
     }
    //启动线程
    PrimeThread p = new PrimeThread(143);
     p.start();

 需要注意:一个Thread对象只能调用一次start方法,之后调用报错IllegalThreadStateException。主要因为执行一次后线程状态改变

2.另一种方法是声明实现 Runnable 接口的类,该类然后实现 run 方法。然后可以分配该类的实例,在创建 Thread 时作为一个参数来传递并启动

class PrimeRun implements Runnable {public void run() {
            . . .
         }
     }
     //启动线程
     PrimeRun p = new PrimeRun(143);
     new Thread(p).start();
   new Thread(p).start();

每个线程都有一个标识名,多个线程可以同名p,同一对象启动多个线程,实现资源共享。如果线程创建时没有指定标识名,就会为其生成一个新名称,也就是新的对象,资源不共享。

两种方法的区别

1.java不允许多继承,所以实现接口Runnable比继承Thread类有明显优势,避免了单继承的局限性。

2.Runnable更容易实现资源共享,能多个线程同时处理一个资源。

Thread实现了接口Runnable,调用Start方法中会调用run方法,Runnable没有start方法所以需要Thread实例开启线程。

线程状态

 New:新创建

Runnable:可运行

Blocked:阻塞

Waiting:等待

Timed Waiting:计时等待

Terminated:被终止

线程有上述6种状态,调用getState方法可确定当前状态。

线程锁:synchronized&lock的区别

1.synchronized是一个关键字,而lock是一个类.

2.synchronized待修饰代码结束会自动释放锁,而lock需要代码释放,lock.unlock().

3.sysnchronized锁的状态无法判断,lock用方法trylock()可判断锁的状态

监视器

1.只包含有私有域的类

2.每个监视器的类都有一个相关的锁

3.使用该锁对所有方法加锁,

4.该锁可以有任意相关条件

关键字volatile

volatile为实例域的同步访问提供了一种免锁机制。volatile不能确保原子性。所有在对volatile变量进行操作的时候要保证其操作是原子性,否则就要加锁来保证原子性.

使用volatile修饰的变量,编译器会保证其有序性,确保当前线程对volatile变量的修改,能即时的写回到共享主内存中,并被其他线程所见。

private boolean done;
public synchronized boolean isDone(){ return done};
public synchronized boolean setDone(){ done = true};

private volitile boolean done;
public boolean isDone(){ return done};
public boolean setDone(){ done = true};

 

关键字threadLocal

主要作用:局部变量对当前线程可见,避免共享变量的风险。

public static final SimpleDateformat df = new SimpleDateformat("yyyy-MM-dd");
String dataStamp = df.format(new Date());
String dataStamp1 = df.format(new Date());//结果可能会混乱

public Static final ThreadLocal<SimpleDateformat > df =  new ThreadLocal<SimpleDateformat > {
  protected SimpleDateformat initualValue(){
        return new SimpleDateformat("yyyy-MM-dd");
  }
}

线程安全集合

ConcurrentHashMap,ConcurrentSkipListMap,ConcurrentSkipListSet,ConcurrentLinkedQueue

Vector,Stack,Hashtable,Enumeration

常用的HashMap与ArrayList不是线程安全的,可用Collections.synchronized**变成线程安全的

List<E> synchArrayList = Collections.synchronizedList(new ArrayList<E>());
Map<K,V> synchHashMap = Collections.synchronizedMap(new HashMap<K,V>());

Thread类的常用方法

start();//启动线程

run();//线程实现逻辑,启动线程需要调用start,run方法单线程的

getId();//获得线程ID

getName();//获得线程名字

getPriority();//获得优先权

isAlive();//判断线程是否活动

isDaemon();//判断是否守护线程

getState();//获得线程状态

sleep(long mill);//休眠线程

join();//,同步主线程,等待线程结束

yield();//暂停当前线程,让其他线程先执行

interrupt();//中断线程

currentThread();//获得正在执行的线程对象

 

线程池ThreadPool

线程池是指在初始化一个多线程应用程序过程中创建一个线程集合,然后在需要执行新的任务时重用这些线程而不是新建一个线程。

带来的好处:

1.由于创建线程、销毁线程比线程执行更消耗资源。线程池避免了重复的创建线程、销毁线程所带来的消耗,提高了效率。

2.线程池减少了线程数量,可控制线程最大数。防止线程过多,导致阻塞。

3.线程池提供了很多属性,可以对线程进行的简单地管理,定时延时等。

ThreadPoolExecutor类

JDK中线程池的实现。构造方法最多有七个参数配置。

 public ThreadPoolExecutor(int corePoolSize,//线程池最大核心线程数 int maximumPoolSize,//线程池最大线程数 long keepAliveTime,//非核心线程闲置超时时间 TimeUnit unit,//keepAliveTime的单位 BlockingQueue<Runnable> workQueue,//线程等待队列 ThreadFactory threadFactory,//工厂模式建立线程 RejectedExecutionHandler handler){...}//异常处理

threadPoolExecutor.execute(Runnable);//执行

ThreadPoolExecutor创建线程

1.线程数量未达到corePoolSize,新建核心线程

2.线程数量达到corePoolSize,进入等待队列workQueue

3.队列已满,新建非核心线程,执行任务

4.队列已满,maximumPoolSize也已达到,RejectedExecutionHandler进行抛错。

常用的线程池:

CachedThreadPool():可缓存线程池,作线程的创建数量几乎没有限制,可灵活回收空闲线程,使用CachedThreadPool时,一定要注意控制任务的数量,否则,由于大量线程同时运行,很有会造成系统瘫痪。

ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
cachedThreadPool.execute(
new Runnable() {   public void run() {   ...   } 
});

 

FixedThreadPool():定长的线程池,每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。但是线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
fixedThreadPool.execute(new Runnable() {
               public void run() {
                    ...
                }
            });

 

ScheduledThreadPool():定长的线程池,而且支持定时的以及周期性的任务执行,支持定时及周期性任务执行。

ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
scheduledThreadPool .execute(new Runnable() {
               public void run() {
                    ...
                }
            });

 

SingleThreadExecutor():单线程化的Executor,即只创建唯一的工作者线程来执行任务,单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。

ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
singleThreadExecutor .execute(new Runnable() {
               public void run() {
                    ...
                }
            });