一、进程与线程
1.概念
进程:操作系统中一个程序的执行周期
线程:进程中的一个任务。一个进程中可以包含多个线程
2.进程和线程的区别
Ⅰ.每个进程用于自己的一整套变量,进程是操作系统中资源分配的最小单位。线程依托于进程存在,多个线程共享进程的资源,线程是操作系统中任务调度的基本单位
Ⅱ.启动、撤销一个进程的开销要比启动、撤销一个线程大的多(这里突出线程是轻量级的)
Ⅲ.没有进程就没有线程,进程一旦终止,其内的线程全部撤销
二、Java的多线程实现
java实现多线程有三种方式,第一种是继承Thread类;第二种是实现Runnable接口;第三种是实现Callable<V>接口
1.继承Thread类实现多线程
Thread类在java.lang包中,Thread类是线程操作的核心类,JDK1.0提供
继承Thread类实现多线程的具体方式是直接继承Thread类而后重写类中的run()方法(这个run()方法相当于主方法)。
class myThread extends Thread{
@Override
public void run() {
System.out.println();
}
}
class Test{
public static void main(String[] args) {
myThread myThread = new myThread();
Thread thread = new Thread(myThread);
thread.start();
}
}
2.Runnable接口实现多线程
实现Runnable接口并重写run()方法
class myThread implements Runnable{
@Override
public void run() {
System.out.println();
}
}
class Test{
public static void main(String[] args) {
myThread myThread = new myThread();
Thread thread = new Thread(myThread);
thread.start();
}
}
Ⅰ.Java中多线程的处理就是一个典型的代理设计模式。其中Thread类完成资源调度、系统分配辅助线程业务类;自定义的线程业务类负责真实业务实现。
Ⅱ.使用Runnable接口实现的多线程程序类可以更好地描述资源共享
3.Callable<V>接口实现多线程——唯一一个有返回值的线程实现方法
Callable<V>在java.util.concurrent包中,java.util.concurrent包简称juc包,又叫高并发程序包,实现多线程需要实现Callable接口并重写Callable接口的call()方法。
class myThread implements Callable <String>{
@Override
public String call() throws Exception {
return "Callable接口实现多线程";
}
}
class Test{
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> futureTask = new FutureTask(new myThread());
new Thread(futureTask).start();
System.out.println(futureTask.get());
}
}
其中多了一个FutureTask的类,为什么要这样写呢?
我们可以看一下前两个实现多线程的方式都是给Thread类的构造方法传进去了一个对象,当然Thread并不是什么对象都能接收,我们可以看一下Thread类都能接收什么对象
可以看到Thread类并不能接收一个Callable<V>对象,我们看一下FutureTask类的源码,
public class FutureTask<V> implements RunnableFuture<V>
FutureTask类实现了RunnableFuture接口,那么RunnableFuture接口又是什么,继续找源码,
public interface RunnableFuture<V> extends Runnable, Future<V> {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
}
原来RunnableFuture接口继承了Runnable接口,那么Thread能接收Runnable对象,就可以接收FutureTask对象。其中FutureTask类中的get()方法可以获取到Callable接口call方法的返回值。当线程需要返回值时只能采用Callable接口实现多线程。
注意:无论哪种方式实现多线程,线程的启动一律调用Thread类提供的start()方法。
start()方法解析:
start()方法源码
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
首先检查线程状态是否为0(线程默认状态为0表示线程未启动),如果线程已经启动再次调用start方法会抛出IllegalThreadStateException,即一个线程的start()方法只能调用一次。如果线程状态等于0,调用start0方法,通过start0()真正将线程启动。
private native void start0();
start0方法是一个本地方法。JVM会调用start0方法进行资源分配与系统调度,准备好资源启动线程后回调run()来执行线程的具体任务。
三、多线程常用操作方法
1.线程的命名与取得
1.通过构造方法命名
public Thread(String name)
public Thread(Runnable target,String name)
2.设置线程名称
ublic final synchronized void setName(String name)
3.取得线程名称
public final String getName()
4.取得当前正在执行的线程对象(重要)
public static native Thread currentThread();
class Test{
public static void main(String[] args) {
new Thread(()->{
System.out.println("线程");
}).start();
Thread.currentThread().setName("A");
System.out.println(Thread.currentThread().getName());
}
}
java中的main方法实际上是一个主线程(名字为 main)
四、多线程其他操作方法
1.线程休眠(sleep)——从运行状态到阻塞状态,结束后回到就绪状态
线程休眠:指的是让线程暂缓执行,等到了预计时间再恢复执行
线程休眠会立即交出CPU,让CPU去执行其他任务。线程休眠不会释放对象锁
public static native void sleep(long millis) throws InterruptedException;
long millis单位是毫秒
2.线程让步(yield)——从运行状态返回就绪状态
public static native void yield();
暂停当前正在执行的线程对象,并执行其他线程
yield()会让当前线程交出CPU,但不一定立即交出。yield()交出CPU后只能让拥有相同优先级的线程有获得CPU的机会。yield()不会释放对象锁。
3.join()方法——从运行态到阻塞态
public final void join() throws InterruptedException
等待该线程终止。join()方法会释放锁。如果在主线程中调用该方法会让主线程休眠,让调用该方法的线程先执行完毕后再恢复执行主线程。join()只是对Object类wait()方法做了一层包装
4.线程停止(三种方式)
1)手工设置标记位,让线程在满足条件后退出
class myThread implements Runnable{
private boolean flag = true;
@Override
public void run() {
int i = 0;
while (flag) {
System.out.println(Thread.currentThread().getName() + "线程" + i++);
}
}
public void setFlag(boolean flag){
this.flag = flag;
}
}
public class Test{
public static void main(String[] args) {
myThread mt = new myThread();
Thread thread = new Thread(mt,"A");
thread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
mt.setFlag(false);
}
}
2)使用stop()方法强制让线程退出,但是该方法不安全,已经被@Deprecated
class myThread implements Runnable{
private boolean flag = true;
@Override
public void run() {
int x = 0;
int y = 0;
while (flag) {
System.out.println(x++);
System.out.println(y++);
}
}
}
public class Test{
public static void main(String[] args) {
myThread mt = new myThread();
Thread thread = new Thread(mt,"A");
thread.start();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.stop();
}
}
我们多次运行这段代码就会发现有时候最后一个y没有输出,y其实也没有进行自增操作,那么在我们想让x和y变化一致的情况下,这段代码就是不安全的。即使进入到了循环中,调用stop方法也会立即停止,不会等循环执行完。
stop会解除由线程获取的所有锁定,当在一个线程对象上调用stop()方法时,这个线程对象所运行的线程就会立即停止,假如一个线程正在执行:synchronized void { x = 3; y = 4;} 由于方法是同步的,多个线程访问时总能保证x,y被同时赋值,而如果一个线程正在执行到x = 3;时,被调用了 stop()方法,即使在同步块中,它也会马上stop了,这样就产生了不完整的残废数据。
3)使用Thread类提供的interrupt()中断线程(就是系统设置了一个标记位,直接使用即可)
interrupt方法只是将线程的状态改为中断状态而已,它不会中断一个正在运行的线程,这一方法实际完成的是,给受阻塞的线
程发出一个中断信号,这样受阻线程就得以退出阻塞的状态。
如果线程调用了wait、sleep()、join()方法进入阻塞态,调用该进程的interrupt()会抛出InterruptedException,并且将线程interrupt重置为false。调用线程类的interrupted方法,其本质只是设置该线程的中断标志,将中断标志设置为true,并根据线程状态决定是否抛出异常。
class MyThread implements Runnable {
private boolean flag = true;
@Override
public void run() {
int i = 1;
while (flag) {
try {
/**
* 这里阻塞之后,线程被调用了interrupte()方法,
* 清除中断标志,就会抛出一个异常
* java.lang.InterruptedException
*/
Thread.sleep(1000);
boolean bool = Thread.currentThread().isInterrupted();
if (bool) {
System.out.println("非阻塞情况下执行该操作。。。线程状态" + bool);
break;
}
System.out.println("第"+i+"次执行,线程名称为:"+Thread.currentThread().getName());
i++;
} catch (InterruptedException e) {
System.out.println("退出了");
/**
* 这里退出阻塞状态,且中断标志被系统会自动清除,
* 并且重新设置为false,所以此处bool为false
*/
boolean bool = Thread.currentThread().isInterrupted();
System.out.println(bool);
//退出run方法,中断进程
return;
}
}
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
Thread thread1 = new Thread(myThread, "子线程A");
thread1.start();
Thread.sleep(3000);
thread1.interrupt();
System.out.println("代码结束");
}
}
5.线程优先级(比较鸡肋)(范围[1 - 10])
线程的优先级是指,优先级越高越有可能先执行而已,仅仅是有可能
设置优先级
public final void setPriority(int newPriority)
取得优先级
public final int getPriority()
Thread提供的优先级
Thread.MAX_PRIORITY = 10;
Thread.NORM_PRIORITY = 5;
Thread.MIN_PRIORITY = 1;
main线程的优先级是5
线程的继承性:优先级可以继承
在A线程中启动B线程,则A和B的优先级一样
五、守护线程(后台线程)
守护线程是一种特殊的线程,属于陪伴线程。java中一共两种线程,一种是用户线程,一种是守护线程
isDaemon()判断是否是守护线程
典型的守护线程:垃圾回收线程
只要当前JVM进程中存在任何一个用户线程没有结束,守护线程就一直在工作;只有当最后一个用户线程停止后,守护线程会随着JVM进程一同停止。
setDaemon()将当前线程设置成为守护线程
java中启动线程默认为用户线程,包括我们启动的主线程