java多线程学习笔记(1)
1、程序|进程|线程
进程:一个计算机开了多少个程序,计算机中的任务管理器看到的进程就是典型的多进程。是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的生命周期。进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域,下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。。
线程:通常在一个进程中可以包含若干个线程,当然即使没有创建线程也有线程那就是主线程。线程可以利用进程所拥有的资源,每个线程拥有独立的运行栈和程序计数器(pc),通常把线程作为独立运行和独立调度的基本单位,由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统多个程序间并发执行的程度。
程序:程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。
2、三种创建多线程的方式
1、继承Thread
继承thread类,重写run方法,调用start开启线程
public class TestThread extends Thread{
@Override
public void run(){
for(int i = 0;i < 200;i++){
System.out.println("我在看代码~~~~");
}
}
public static void main(String[]args){
TestThread test = new TestThread();
//start开启线程
test.start();//按顺序执行
for(int i = 0;i < 200;i++){
System.out.println("我在敲代码");
}
}
}
//构造方法
public Thread() {}
public Thread(Runnable target) {}
public Thread(Runnable target, AccessControlContext acc) {}
public Thread(ThreadGroup group, Runnable target) {}
public Thread(String name) {}
public Thread(ThreadGroup group, String name) {}
public Thread(Runnable target, String name) {}
public Thread(ThreadGroup group, Runnable target, String name) {}
public Thread(ThreadGroup group, Runnable target, String name,long stackSize) {}
2、继承Runnable
public class Main implements Runnable{
String name;
public Main(String name){
this.name = name;
}
@Override
public void run(){
System.out.println(name+"在写代码~~");
}
public static void main(String[]args){
Main m1 = new Main("user1");
Main m2 = new Main("user2");
new Thread(m1).start();
new Thread(m2).start();
for(int i = 0;i < 50;i++){
System.out.println("看代码");
}
}
}
3、实现callable接口
任务执行完成之后可以获取返回结果
Callable的接口定义如下
public interface Callable<V> {
V call() throws Exception;
}
public static void main(String[] args) throws Exception {
FutureTask futureTask1 = new FutureTask(new Callable() {
@Override
public String call() throws Exception {
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName());
return "返回的Thread2的结果:"+Thread.currentThread().getName();
}
});
FutureTask futureTask2 = new FutureTask(() -> {
System.out.println(Thread.currentThread().getName());
return "返回的Thread2的结果:"+Thread.currentThread().getName();
});
new Thread(futureTask1).start();
new Thread(futureTask2).start();
System.out.println(futureTask1.get());
System.out.println(futureTask2.get());
}
3 、线程五个状态
线程一旦创建就进入到新生状态(Thread t=new Thread);当调用start()方法,线程立即进入就绪状态,但不意味着立即调度执行;线程被调度时,进入运行状态,线程才真正执行线程体的代码块;当调用sleep,wait或同步锁定时,线程进入如阻塞状态,就是代码不往下执行,阻塞事件接触后,重新进入就绪状态,等待cpu调度执行;线程中断或者结束,一旦进入死亡状态,就不能再次启动。
public enum State {
//创建
NEW,
//可运行的线程状态,包含就绪和运行状态
//处于可运行状态的线程正在 Java 虚拟机中执行,但它可能正在等待来自操作系统的其他资源,例如处理器。
RUNNABLE,
//线程阻塞等待监视器锁的线程状态。处于阻塞状态的线程正在等待监视器锁进入同步块/方法或在调用Object.wait后重新进入同步块/方法。
//JVM会把该线程放入锁池中。
BLOCKED,
//处于等待状态的线程正在等待另一个线程执行特定操作。例如,一个对对象调用Object.wait()的线程正在等待另一个线程对该对象调用Object.notify()或Object.notifyAll() ;已调用Thread.join()的线程正在等待指定线程终止。
//JVM会把该线程放入等待池中。
WAITING,
//具有指定等待时间的等待线程的线程状态。由于以指定的正等待时间调用以下方法之一,线程处于定时等待状态:Thread.sleep;Object.wait设置时间;Thread.join设置时间;LockSupport.parkNanos;LockSupport.parkUntil
TIMED_WAITING,
// 死亡状态
//线程执行完了或者因异常退出了run()方法,
TERMINATED;
}
4、线程方法
1、object的wait和notify方法
Object.wait()和 Object.wait(long) :在其他线程调用对象的notify或notifyAll方法前,导致当前线程等待。线程会释放掉它所占有的"锁标志",从而使别的线程有机会抢占该锁。
Object.notifyAll():从对象等待池中唤醒所有等待等待线程;Object.notify():则从对象等待池中唤醒其中一个线程。
wait()和notify()必须在synchronized函数或synchronized中进行调用。如果在non-synchronized函数或non-synchronized中进行调用,虽然能编译通过,但在运行时会发生IllegalMonitorStateException的异常。
2、中断线程
- 建议线程正常停止,可以利用次数
- 可以使用标志位
- 不要使用stop或者destroy等果实或者JDK不建议使用的方法
//向线程发送中断请求,线程的中断状态将会被设置为true,如果当前线程被一个sleep调用阻塞,那么将会抛出interrupedException异常。
void interrupt();
//测试当前线程(当前正在执行命令的这个线程)是否被中断。注意这是个静态方法,调用这个方法会产生一个副作用那就是它会将当前线程的中断状态重置为false。
static boolean interrupted();
//判断线程是否被中断,这个方法的调用不会产生副作用即不改变线程的当前中断状态。
boolean isInterrupted();
3、线程休眠sleep()
public static native void sleep(long millis) throws InterruptedException;
//指定当前线程阻塞的毫秒数
sleep时间达到后线程进入就绪状态
每个对象都有一个锁,sleep不会释放锁?
4、线程礼让yield()
public static native void yield();
向调度程序提示当前线程愿意放弃其当前对处理器的使用。
yield 是一种启发式尝试,旨在改善线程之间的相对进展,否则会过度使用 CPU。
让线程从运行状态到就绪状态,礼让不一定成功,只是cpu重新调度,还有可能调度到当前线程
5、 join()
join()
方法将挂起调用线程的执行,直到被调用的对象完成它的执行
其主要作用就是同步,它可以使得线程之间的并行执行变为串行执行。
public final void join() throws InterruptedException {
join(0);
}
public final synchronized void join(final long millis)
throws InterruptedException {
if (millis > 0) {
if (isAlive()) {
final long startTime = System.nanoTime();
long delay = millis;
do {
wait(delay);
} while (isAlive() && (delay = millis -
TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)) > 0);
}
} else if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
throw new IllegalArgumentException("timeout value is negative");
}
}
//join方法的原理就是调用相应线程的wait方法进行等待操作的
//例如A线程中调用了B线程的join方法,则相当于在A线程中调用了B线程的wait方法,当B线程执行完(或者到达等待时间)。
//线程退出时会notify所有持有本线程锁的线程。
public class ThreadJoinTest {
public static void main(String [] args) throws InterruptedException {
ThreadJoinTest t1 = new ThreadJoinTest("李");
ThreadJoinTest t2 = new ThreadJoinTest("张");
t1.start();
/**程序在main线程中调用t1线程的join方法,则main线程放弃cpu控制权,等待t1线程执行完毕。所以结果是t1线程执行完后,才到主线程执行,相当于在main线程中同步t1线程,t1执行完了,main线程才有执行的机会 */
t1.join();
t2.start();
}
}
public ThreadJoinTest(String name){
super(name);
}
@Override
public void run(){
for(int i=0;i<1000;i++){
System.out.println(this.getName() + ":" + i);
}
}
}
5、线程优先级
在现代操作系统中基本采用时分的形式调度运行的线程,操作系统会分出一个个时间片,线程会分配到若干时间片,当线程的时间片用完了就会发生线程调度,并等待着下一次分配。,而线程优先级就是决定线程需要多或者少分配一些处理器资源的线程属性。
Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行,优先级低只是一味获得调度的概率低,并不绝对。
默认情况下,一个线程继承它父类的优先级。线程的默认优先级为NORM_PRIORITY(在Thread类定义为5)。
线程的优先级用int一到十表示,数字越大优先级越高。使用getPriority()和setPriority(int x)分别获取和改变优先级
在设置优先级时,针对频繁阻塞(休眠或者I/O操作)的线程需要设置较高的优先级,而偏重计算(需要较多CPU时间或者运算)的线程则设置较低的优先级,这样能尽可能确保处理器不会被长久独占。
6、守护线程
线程分为用户线程和守护线程,而守护线程的唯一作用就是为其他线程提供服务。
虚拟机必须确保用户线程执行完毕
虚拟机不用等待守护线程执行完毕,如计时线程,后台记录操作日志,监控内存,GC等
//将此线程标记为守护线程或用户线程。当唯一运行的线程都是守护线程时,Java 虚拟机退出。
//此方法必须在线程启动之前调用。
//参数:on – 如果为true ,则将此线程标记为守护线程,默认为false
public final void setDaemon(boolean on) {
checkAccess();
if (isAlive()) {
throw new IllegalThreadStateException();
}
daemon = on;
}
//注意:在java虚拟机退出时Daemon线程中的finally代码块并不一定会执行