线程
程序(program)是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码。
进程((process)就是正在执行的程序,从Windows角度讲,进程是含有内存和资源并安置线程的地方.
线程(thread)进程可进一步细化为线程,是一个进程内部的最小执行单元(执行任务).
线程和进程的关系
-
一个进程可以包含多个线程,一个线程只能属于一个进程,线程不能脱离进程而独立运行;
-
每一个进程至少包含一个线程(称为主线程);在主线程中开始执行程序,java程序的入口main()方法就是在主线程中被执行的。
-
在主线程中可以创建并启动其它的线程;
-
一个进程内的所有线程共享该进程的内存资源
package day1.Demo1;
/*
* 线程类:
* 方式一:继承Thread类
* 重写run方法
* */
public class ThreadDemo extends Thread {
/*
在run方法中编写需要独立的代码
*/
@Override
public void run() {
for (int i = 0; i <1000 ; i++) {
System.out.println("ThreadDemo"+i);
}
}
}
package day1.Demo1;
public class Test {
public static void main(String[] args) {
/*
在main主线程中创建一个独立的线程,并启动运行这个线程
*/
ThreadDemo threadDemo=new ThreadDemo();
//threadDemo.run(); // 调用方法就不是起动新的线程.而就是一个普通方法调用
threadDemo.start();//启动一个线程 cpu可以加载执行,与main线程有相同执行权,交替执行
}
}
多线程的概念
多线程是指程序中包含多个执行单元,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务
何时需要多线程
-
程序需要同时执行两个或多个任务。
-
程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
-
需要一些后台运行的程序时。
多线程的好处
-
提高程序的响应.
-
提高CPU的利用率.
-
改善程序结构,将复杂任务分为多个线程,独立运行.
多线程的不利方面
-
线程也是程序,所以线程需要占用内存,线程越多占用内存也越多;
-
多线程需要协调和管理,所以需要CPU时间跟踪线程;
-
线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题;
创建线程
- 继承Thread类的方式
package day1;
public class ThreadDemo extends Thread {
@Override
public void run() {
for (int i = 0; i <1000 ; i++) {
System.out.println(getName()+""+i);
}
}
}
package day1;
public class Text {
public static void main(String[] args) {
for (int i = 0; i <100 ; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
if (i==20){
new ThreadDemo().start();
}
}
}
}
- 实现Runnable接口的方式
package day1;
public class RunThreadDemo implements Runnable {
private int i;
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
package day1;
public class Text2 {
public static void main(String[] args) {
for (int i = 0; i <100 ; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
if (i==20){
RunThreadDemo rund=new RunThreadDemo();
new Thread(rund,"新线程1").start();
new Thread(rund,"新线程2").start();
}
}
}
}
继承方式和实现方式的联系与区别
继承Thread: 线程代码存放Thread子类run方法中。
实现Runnable: 线程代码存在接口的子类的run方法。
实现Runnable的好处
-
避免了单继承的局限性
2.多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源。
Thread类构造方法
构 造 方 法 | 说 明 |
---|---|
Thread() | 创建一个新的线程 |
Thread(String name) | 创建一个指定名称的线程 |
Thread(Runnable target) | 利用Runnable对象创建一个线程,启动时将执行该对象的run方法 |
Thread(Runnable target, String name) | 利用Runnable对象创建一个线程,并指定该线程的名称 |
常用方法
方 法 原 型 | 说 明 |
---|---|
void start() | 启动线程 |
final void setName(String name) | 设置线程的名称 |
final String getName() | 返回线程的名称 |
final void setPriority(int newPriority) | 设置线程的优先级 |
final int getPriority() | 返回线程的优先级 |
final void join() throws InterruptedException | 等待线程终止 |
static Thread currentThread() | 返回对当前正在执行的线程对象的引用 |
static void sleep(long millis) throws InterruptedException | 让当前正在执行的线程休眠(暂停执行),休眠时间由millis(毫秒)指定 |
线程优先级
-
事实上,计算机只有一个CPU,各个线程轮流获得CPU的使用权,才能执行任务;
-
优先级较高的线程有更多获得CPU的机会,反之亦然;
-
优先级用整数表示,取值范围是1~10,一般情况下,线程的默认优先级都是5,但是也可以通过setPriority和getPriority方法来设置或返回优先级;
调度策略
-
时间片:队列 排队
-
抢占式:高优先级的线程抢占CPU
Java的调度方法
-
同优先级线程组成先进先出队列(先到先服务),使用时间片策略
-
对高优先级,使用优先调度的抢占式策略
Thread类有如下3个静态常量来表示优先级
-
MAX_PRIORITY:取值为10,表示最高优先级。
-
MIN_PRIORITY:取值为1,表示最底优先级。
-
NORM_PRIORITY:取值为5,表示默认的优先级。
线程状态
线程在它的生命周期中会处于不同的状态
线程的状态:
-
新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
-
**就绪:**处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
-
**运行:**当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能
-
**阻塞:**在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态
-
**死亡:**线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束
线程的分类
Java中的线程分为两类:用户线程和守护线程
通俗的将就是任何一个守护线程都是整个JVM中所有非守护线程的保姆:
只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;
只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。
用户线程和守护线程两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果
用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。 因为没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了
注意:设置线程为守护线程必须在启动线程之前,否则会跑出一个IllegalThreadStateException异常。
线程同步
- 并发与并行
-
并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
-
并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀商品、多个人做同一件事。
2.多线程同步
多个线程同时读写同一份共享资源时,可能会引起冲突。所以引入线程“同步”机制,即各线程间要有先来后到
3.同步就是排队加锁
-
几个线程之间要排队,一个个对共享资源进行操作,而不是同时进行操作;
-
为了保证数据在方法中被访问时的正确性,在访问时加入锁机制
模拟买票:
两个窗口分别售票,票数为10张
package day4.Demo2;
import java.util.concurrent.Callable;
/*
* 实现Callable接口 重写call() 可以抛出异常,可以有返回值
* */
public class PrintNumTread implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int num=0;
for (int i = 0; i <100 ; i++) {
num+=i;
}
return num;
}
}
package day4.Demo2;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Test {
public static void main(String[] args) {
PrintNumTread p=new PrintNumTread();
FutureTask<Integer> f=new FutureTask(p);
Thread t=new Thread(f);
t.start();
try {
int s =f.get();
System.out.println(s);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
线程死锁
概念:
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步 资源,就形成了线程的死锁.
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续.
避免死锁:
让程序每次至多只能获得一个锁。当然,在多线程环境下,这种情况通常并不现实。
设计时考虑清楚锁的顺序,尽量减少嵌在的加锁交互数量。
package day2.demo1;
import java.util.Random;
public class DeiDemo extends Thread {
static Object objectA=new Random();
static Object objectB=new Random();
boolean flag;
public DeiDemo(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if (flag){
synchronized (objectA){
System.out.println("if oA");
synchronized (objectB){
System.out.println("if oB");
}
}
}else {
synchronized (objectB){
System.out.println("else oB");
synchronized (objectA){
System.out.println("else oA");
}
}
}
}
}
Lock(锁)
java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
synchronized和Lock的区别
-
Lock是显式锁(手动开启和关闭锁,别忘记关闭锁), synchronized是隐式锁,出了作用域自动释放
-
Lock只有代码块锁,synchronized有代码块锁和方法锁,使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
-
线程同步优先使用顺序:Lock >同步代码块(已经进入了方法体,分配了相应资源)>同步方法
package day3.demo2;
public class PrintNumThread extends Thread {
static int num=1;
static Object obj=new Object();
@Override
public void run() {
while (true){
synchronized (obj){
obj.notify();//唤醒等待的线程 notifyAll()唤醒所有等待的线程
if (num<=100){
System.out.println(Thread.currentThread().getName()+ num++);
}else {
break;
}
try {
obj.wait();//线程等待(阻塞状态),并释放锁,必须通过notify()或者notifyAll() 唤醒 必须在同步代码块中使用
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
package day3.demo2;
public class Test {
public static void main(String[] args) {
PrintNumThread p1=new PrintNumThread();
p1.setName("线程1:");
PrintNumThread p2=new PrintNumThread();
p2.setName("线程2:");
p1.start();
p2.start();
}
}
e.printStackTrace();
}
}
}
}
}
```java
package day3.demo2;
public class Test {
public static void main(String[] args) {
PrintNumThread p1=new PrintNumThread();
p1.setName("线程1:");
PrintNumThread p2=new PrintNumThread();
p2.setName("线程2:");
p1.start();
p2.start();
}
}