线程与进程 1 线程:进程中负责程序执行的执行单元 线程本身依靠程序进行运行 线程是程序中的顺序控制流,只能使用分配给程序的资源和环境
2 进程:执行中的程序 一个进程至少包含一个线程
3 单线程:程序中只存在一个线程,实际上主方法就是一个主线程
4 多线程:在一个程序中运行多个任务 目的是更好地使用CPU资源
线程的实现 继承Thread类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class Test { public static void main (String[] args) { System.out.println("主线程ID:" +Thread.currentThread().getId()); MyThread thread1 = new MyThread("thread1" ); thread1.start(); MyThread thread2 = new MyThread("thread2" ); thread2.run(); } } class MyThread extends Thread { private String name; public MyThread (String name) { this .name = name; } @Override public void run () { System.out.println("name:" +name+" 子线程ID:" +Thread.currentThread().getId()); } }
实现Runnable接口 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class Test { public static void main (String[] args) { System.out.println("主线程ID:" +Thread.currentThread().getId()); MyRunnable runnable = new MyRunnable(); Thread thread = new Thread(runnable); thread.start(); } } class MyRunnable implements Runnable { public MyRunnable () { } @Override public void run () { System.out.println("子线程ID:" +Thread.currentThread().getId()); } }
静态方法 1.currentThread() 1 2 3 4 5 6 public class Test2 { public static void main (String[] args) { System.out.println("主线程ID:" +Thread.currentThread().getId()); System.out.println("主线程名字:" +Thread.currentThread().getName()); } }
2.sleep() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public class Test2 { private int i = 10 ; private Object object = new Object(); public static void main (String[] args) throws IOException { Test2 test = new Test2(); MyThread thread1 = test.new MyThread(); MyThread thread2 = test.new MyThread(); thread1.start(); thread2.start(); } class MyThread extends Thread { @Override public void run () { synchronized (object) { i++; System.out.println(Thread.currentThread().getName()+"===> i:" +i); try { System.out.println("线程" +Thread.currentThread().getName()+"进入睡眠状态" ); Thread.currentThread().sleep(5000 ); } catch (InterruptedException e) { } System.out.println("线程" +Thread.currentThread().getName()+"睡眠结束" ); i++; System.out.println("i:" +i); } } } }
1 2 3 4 5 6 7 8 9 10 11 Thread-0 ===> i:11 线程Thread-0 进入睡眠状态 线程Thread-0 睡眠结束 i:12 Thread-1 ===> i:13 线程Thread-1 进入睡眠状态 线程Thread-1 睡眠结束 i:14
3.yield
调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程
但是yield不能控制具体的交出CPU的时间,另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。
注意,调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class MyThread extends Thread { @Override public void run () { long beginTime=System.currentTimeMillis(); int count=0 ; for (int i=0 ;i<50000000 ;i++){ count=count+(i+1 ); } long endTime=System.currentTimeMillis(); System.out.println("用时:" +(endTime-beginTime)+" 毫秒!" ); } } public class Run { public static void main (String[] args) { MyThread t= new MyThread(); t.start(); } }
1 2 注释======>用时:23毫秒! 取消注释==>用时:4890 毫秒!
对象方法 start() start()用来启动一个线程,当调用start方法后,系统才会开启一个新的线程来执行用户定义的子任务,在这个过程中,会为相应的线程分配需要的资源。
run() run()方法是不需要用户来调用的,当通过start方法启动一个线程之后,当线程获得了CPU执行时间,便进入run方法体去执行具体的任务。注意,继承Thread类必须重写run方法,在run方法中定义具体要执行的任务。
getId() 1 2 3 4 5 6 7 public class Run { public static void main (String[] args) { Thread thread = Thread.currentThread(); System.out.println(thread.getName()); System.out.println(thread.getId()); } }
isAlive() 方法isAlive()的作用是测试线程是否偶处于活动状态。什么是活动状态呢?活动状态就是线程已经启动且尚未终止。线程处于正在运行或准备开始运行的状态,就认为线程是“存活”的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class Test { public static void main (String[] args) throws InterruptedException { MyThread thread2 = new MyThread("_2" ); System.out.println(thread2.isAlive()); thread2.start(); Thread.sleep(1000 ); System.out.println(thread2.isAlive()); } } class MyThread extends Thread { private String name; public MyThread (String name) { this .name = name; } @Override public void run () { System.out.println(this .isAlive()); } }
join() 在很多情况下,主线程创建并启动了线程,如果子线程中药进行大量耗时运算,主线程往往将早于子线程结束之前结束。这时,如果主线程想等待子线程执行完成之后再结束,比如子线程处理一个数据,主线程要取得这个数据中的值,就要用到join()方法了。方法join()的作用是等待线程对象销毁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public class Thread4 extends Thread { public Thread4 (String name) { super (name); } public void run () { for (int i = 0 ; i < 5 ; i++) { System.out.println(getName() + " " + i); } } public static void main (String[] args) throws InterruptedException { new Thread4("new thread" ).start(); for (int i = 0 ; i < 10 ; i++) { if (i == 5 ) { Thread4 th = new Thread4("joined thread" ); th.start(); th.join(); } System.out.println(Thread.currentThread().getName() + " " + i); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 有join方法: main 0 main 1 main 2 main 3 main 4 new thread 0 new thread 1 new thread 2 new thread 3 new thread 4 joined thread 0 joined thread 1 joined thread 2 joined thread 3 joined thread 4 main 5 main 6 main 7 main 8 main 9 没有join方法: main 0 main 1 main 2 main 3 main 4 main 5 main 6 main 7 main 8 main 9 new thread 0 joined thread 0 joined thread 1 joined thread 2 joined thread 3 joined thread 4 new thread 1 new thread 2 new thread 3 new thread 4
getName和setName() 用来得到或者设置线程名称。
getPriority和setPriority() 用来获取和设置线程优先级。
setDaemon和isDaemon() 用来设置线程是否成为守护线程和判断线程是否是守护线程。
停止线程 停止线程是在多线程开发时很重要的技术点,掌握此技术可以对线程的停止进行有效的处理。
停止一个线程可以使用Thread.stop()方法,但最好不用它。该方法是不安全的,已被弃用。 在Java中有以下3种方法可以终止正在运行的线程:
使用退出标志,使线程正常退出,也就是当run方法完成后线程终止
使用stop方法强行终止线程,但是不推荐使用这个方法,因为stop和suspend及resume一样,都是作废过期的方法,使用他们可能产生不可预料的结果。
使用interrupt方法中断线程,但这个不会终止一个正在运行的线程,还需要加入一个判断才可以完成线程的停止。
暂停线程 interrupt()方法
线程的优先级 JDK中使用3个常量来预置定义优先级的值,代码如下:
1 2 3 public final static int MIN_PRIORITY = 1 ;public final static int NORM_PRIORITY = 5 ;public final static int MAX_PRIORITY = 10 ;
线程优先级特性:
继承性 比如A线程启动B线程,则B线程的优先级与A是一样的。
规则性 高优先级的线程总是大部分先执行完,但不代表高优先级线程全部先执行完。
随机性 优先级较高的线程不一定每一次都先执行完。
守护线程 在Java线程中有两种线程,一种是User Thread(用户线程),另一种是Daemon Thread(守护线程)。 Daemon的作用是为其他线程的运行提供服务,比如说GC线程。其实User Thread线程和Daemon Thread守护线程本质上来说去没啥区别的,唯一的区别之处就在虚拟机的离开:如果User Thread全部撤离,那么Daemon Thread也就没啥线程好服务的了,所以虚拟机也就退出了。
守护线程并非虚拟机内部可以提供,用户也可以自行的设定守护线程,方法:public final void setDaemon(boolean on) ;但是有几点需要注意:
thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。 (备注:这点与守护进程有着明显的区别,守护进程是创建后,让进程摆脱原会话的控制+让进程摆脱原进程组的控制+让进程摆脱原控制终端的控制;所以说寄托于虚拟机的语言机制跟系统级语言有着本质上面的区别)
在Daemon线程中产生的新线程也是Daemon的。 (这一点又是有着本质的区别了:守护进程fork()出来的子进程不再是守护进程,尽管它把父进程的进程相关信息复制过去了,但是子进程的进程的父进程不是init进程,所谓的守护进程本质上说就是“父进程挂掉,init收养,然后文件0,1,2都是/dev/null,当前目录到/”)
不是所有的应用都可以分配给Daemon线程来进行服务,比如读写操作或者计算逻辑。因为在Daemon Thread还没来的及进行操作时,虚拟机可能已经退出了。
同步与死锁
同步代码块 在代码块上加上”synchronized”关键字,则此代码块就称为同步代码块
同步代码块格式
1 2 3 synchronized (同步对象){ 需要同步的代码块; }
同步方法 除了代码块可以同步,方法也是可以同步的
方法同步格式
1 synchronized void 方法名称(){}