目录
1、线程概述
程序:静态代码,安装到硬盘上
进程:运行中的程序,是操作系统分配资源的最小单位
线程:线程是进程(程序)中最小的执行单元,是CPU进行调度的最小单位
一个进程中可以包含多个线程,线程不能脱离进程而存在
一个线程只能属于一个进程
一个进程内的所有线程共享同一个进程的内存资源
2、创建线程
方式一:继承Thread类
/*
用于创建线程,操作线程的类
*/
public class ThreadDemo extends Thread{
/*
java中要在线程中执行的任务,写在run()
*/
@Override
public void run(){
for (int i = 0;i<1000;i++){
System.out.println("ThreadDemo:"+i);
}
}
}
public class Test {
/*
main 启动java主线程
*/
public static void main(String[] args) {
//在主线程中创建线程,并启动线程
ThreadDemo td = new ThreadDemo();
//td.run();千万切记不能调用run方法启动线程,这只是一个普通方法调用,不是启动线程
td.start();//启动线程,告诉操作系统开始线程,开始调度
for (int i = 0;i<1000;i++){
System.out.println("main:"+i);
}
}
}
方式二:让类实现Runnable接口
/*
让类实现Runnable接口
*/
public class MyThread implements Runnable{
@Override
public void run() {
for (int i = 0;i < 1000;i++){
System.out.println("自定义线程:"+i);
System.out.println(Thread.currentThread().getName()+":"+i );
}
}
}
public class Test {
/*
Thread类中方法
run(); 用于执行任务
start();启动线程
Thread(myth,"窗口1") 构造方法,分配任务,定义线程名称
Thread.currentThread();获得当前线程的引用
getName(); 获得线程的名字
th2.setName(String name); 设置线程名字
*/
public static void main(String[] args) {
//创建了一个线程要执行的任务
MyThread myth = new MyThread();
/*Thread th1 = new Thread(myth);//创建了一个线程,并为其分配了一个任务*/
Thread th1 = new Thread(myth,"窗口1:");
th1.start();
Thread th2 = new Thread(myth);
th2.setName("窗口2:");
th2.start();
for (int i = 0;i < 1000;i++){
System.out.println("main:"+i);
}
}
}
区别:继承Thread类:线程代码存放Tread子类Run方法中
实现Runnable接口:线程代码存在接口的子类的run方法中
实现Runnable接口的好处:避免了单继承的局限性
多个线程可以共享一个接口实现类的对象,非常适合多个相同线程来处理同一份资源
3、线程优先级
计算机只有一个CPU,各个线程轮流获得CPU的使用权,才能执行任务
优先级较高的线程有更多获得CPU的机会,反之亦然
优先级用整数表示,取值范围是1-10,一般线程默认优先级是5,也可以通过setPriority和getPriority方法来设置或返回优先级
public class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+Thread.currentThread().getPriority());
}
}
public class Test {
public static void main(String[] args) {
//创建了一个线程要执行的任务
MyThread myth = new MyThread();
/*Thread th1 = new Thread(myth);//创建了一个线程,并为其分配了一个任务*/
Thread th1 = new Thread(myth,"窗口1:");
th1.start();
Thread th2 = new Thread(myth);
th2.setName("窗口2:");
th2.start();
for( int i = 0;i < 1000;i++){
System.out.println(Thread.currentThread().getName()+i);
}
Thread.currentThread().setPriority(10);//java中的线程是支持设置优先级的,默认优先级是5,最低是1,最高是10
th1.setPriority(1);
th2.setPriority(2);
System.out.println("main:"+Thread.currentThread().getPriority());
}
}
调度策略:时间片
抢占式:高优先级的线程抢占CPU
java的调度方式(法):同优先级线程组成先进先出队列,使用时间片策略
对高优先级使用优先调度的抢占式celue
4、线程状态
线程在它的声明周期中会处于不同的状态:
新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它具备了运行的条件,只是没分配到CPU资源
运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能
阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时终止自己的执行,进入阻塞状态
死亡:线程完成了它的全部工作或线程被提前强制性地终止或出现异常导致结束
public class ThreadDemo extends Thread {
@Override
public void run(){
for (int i = 1;i<10;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("threaddemo:"+i);
}
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
ThreadDemo threadDemo = new ThreadDemo();//新建状态,此时并不能被操作系统调度
threadDemo.start();//启动线程,不是启动后就立刻执行的,进入到就绪状态,操作系统就可以调度
threadDemo.join();//让此线程加入到当前线程,等待此线程死亡
System.out.println("main结束 ");
}
}
5、线程分类
用户线程和守护线程
任何一个守护线程都是整个KVM中所有非守护线程的保姆
只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作,只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作
守护线程的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是GC(垃圾回收器),他就是一个很称职的守护者
用户线程和守护线程几乎没有区别,唯一不同的之处就在于虚拟机的离开,如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了,因为没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了。
注:设置线程为守护线程必须在启动线程之前,否则会跑出一个IllegaThreadStateException异常
public class ThreadDemo1 extends Thread{
@Override
public void run(){
int i = 0;
while (true){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("守护线程 一直默默守护 "+(++i));
}
}
}
public class ThreadDemo2 extends Thread{
@Override
public void run(){
for (int i = 0; i < 30; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("用户线程:"+i);
}
}
}
public class Test {
public static void main(String[] args) {
ThreadDemo1 th1 = new ThreadDemo1();
th1.setDaemon(true);//设置线程为守护线程,必须在线程启动前设置
th1.start();
ThreadDemo2 th2 = new ThreadDemo2();
th2.start();
}
}
6、多线程
a、概念
程序需要同时执行两个或多个任务
程序需要实现一些等待的任务时,如用户输入,文件读写操作,网络操作,搜索等
需要一些后台运行的程序时
b、多线程的优点
提高程序的响应
提高CPU的利用率
改善程序结构,将复杂任务分为多个线程,独立运行
c、多线程的缺点
线程也是程序,所以线程需要占用CPU内存,线程越多占用内存也越多
多线程需要协调和管理,所以需要CPU时间跟踪线程
线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题
多线程的安全问题:多个线程同时访问一个资源 解决----->线程同步 加锁
7、线程同步
并发与并行:
并发:多个CPU同时执行多个任务,比如:多个人同时做不同的事
并行:一个CPU(采用时间片)同时执行多个任务。比如:秒杀 多个人做同一件事
多线程同步:多个线程同时读写同一份共享资源时,可能会引起冲突。所以引入线程“同步”机制,即各线程间要有先来后到
线程同步:确保一个时间点只有一个线程访问共享资源。可以给共享资源加一把锁。哪个线程获取了这把锁,才有权利访问该资源共享
同步就是排队+锁
几个线程之间要排队,一个个对共享资源进行操作,而不是同时进行操作
为了保证数据在方法中被访问时的正确性,在访问是加入锁机制
在java代码中实现同步:
使用synchronized(同步监视器)关键字代码块
//创建线程方式一:
public class TicketThread extends Thread{
//static 修饰的变量是类变量,在内存中只有一份
static int num = 10;//票数 共享资源
static Object obj = new Object();//保证只有一个
@Override
public void run() {
while (true){
//synchronized(同步锁,就是一个对象,但是要求对象只有一个)
synchronized (obj) {
if (num > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "售出票:" + num);
num--;
} else {
break;
}
}//同步代码块,执行完成后,同步对象(锁)会自动释放
}
}
}
//创建线程方式二
int num = 10;
@Override
public void run() {
while (true){
//添加同步对象,这种方式可以使用this来表示锁
synchronized (this){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (num>0){
System.out.println(Thread.currentThread().getName()+":"+num);
num--;
}else{
break;
}
}
}
}
//测试
public class Test {
public static void main(String[] args) {
TicketThread t1 = new TicketThread();
t1.setName("窗口1");
t1.start();
TicketThread t2 = new TicketThread();
t2.setName("窗口2");
t2.start();
}
synchronized还可以放在方法声明中,表示整个方法为同步方法
//创建线程方式一
public class TicketThread1 extends Thread{
//static 修饰的变量是类变量,在内存中只有一份
static int num = 10;//票数 共享资源
static Object obj = new Object();//保证只有一个
@Override
public void run() {
while (true){
if (num>0){
print();
}else{
break;
}
}
}
/*
synchronized在修饰方法时,同步对象(锁)默认是this,一旦创建多个线程对象(只针对继承Thread类的方式)
static修饰后,同步对象变为线程类(类只有一个)
*/
public static synchronized void print(){
if (num>0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"售出票"+num);
num--;
}
}
}
//创建线程方式二
int num = 10;
@Override
public void run() {
while (true) {
//添加同步对象,这种方式可以使用this来表示锁
if (num > 0) {
print();
} else {
break;
}
}
}
//synchronized 默认同步锁对象是this
public synchronized void print(){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (num>0){
System.out.println(Thread.currentThread().getName()+":"+num);
num--;
}
}
}
//测试
public class Test1 {
public static void main(String[] args) {
TicketThread1 ticketThread1 = new TicketThread1();
ticketThread1.setName("窗口1");
ticketThread1.start();
TicketThread1 ticketThread2 = new TicketThread1();
ticketThread2.setName("窗口2");
ticketThread2.start();
}
}
同步监视器:同步监视器可以是任何对象,但必须唯一,保证多个线程获得的是同一个对象(锁)
同步监视器的过程(执行):
第一个线程访问,锁定同步监视器,执行其中代码
第二个线程访问,发现同步监视器被锁定,无法访问
第一个线程访问完毕,解锁同步监视器
第二个线程访问,发现同步监视器没有锁,然后锁定并访问
一个线程持有锁会导致其他所需要此锁的线程挂起;在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,因其性能问题
8、LOCK(锁)
从JDK5.0开始,Java提供了更强大的线程同步机制—通过显示定义同步锁对象来实现同步,同步锁使用LOCK对象充当
java.util.concurrent.locks.lock接口是控制多个线程对共享资源进行访问的工具,锁提供了对共享资源的独占访问,每次只能有一个线程对LOCK对象加锁,线程开始访问共享资源之前应先获得LOCK对象
ReentrantLock类实现了LOCK,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁,释放锁
synchronize 释放锁,隐式加锁,可以锁定代码块,也可以锁定方法
LOCK 可以显示的加锁,释放锁,只能锁定代码块
public class TicketThread implements Runnable {
int num = 10;//共享变量
Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
//添加同步对象,这种方式可以使用this来表示锁
if (num > 0) {
print();
} else {
break;
}
}
}
public void print(){
//System.out.println("ssss");
try {
lock.lock();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (num > 0) {
System.out.println(Thread.currentThread().getName() + ":" + num);
num--;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();//在finally中释放 锁
}
//System.out.println("dddd");
}
}
public class Test {
public static void main(String[] args) {
//创建出票任务
TicketThread ticketThread = new TicketThread();
//两个线程执行的是一个任务,num只有一个
Thread t1 = new Thread(ticketThread,"窗口1");
Thread t2 = new Thread(ticketThread,"窗口2");
t1.start();
t2.start();
}
}
9、线程死锁
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继承
设计时考虑清除锁的顺序,尽量减少嵌套的加锁交互数量
public class DieLock extends Thread {
static Object objA = new Object();
static Object objB = new Object();
boolean flag;
public DieLock(boolean flag){
this.flag = flag;
}
@Override
public void run() {
if (flag){//线程1
synchronized (objA){
System.out.println("ifobjA");
synchronized (objB){
System.out.println("ifobjB");
}
}
}else{//线程2
synchronized (objB){
System.out.println("elseobjB");
synchronized (objA){
System.out.println("elseobjA");
}
}
}
}
}
public class Test {
public static void main(String[] args) {
DieLock dieLock1 = new DieLock(true);
DieLock dieLock2 = new DieLock(false);
dieLock1.start();
dieLock2.start();
}
}
10、线程通信
线程通讯指的是多个线程通过消息传递实现相互牵制,相互调度,即线程间的相互作用。涉及三个方法:
.wait 一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
.notify 一旦执行此方法,就会唤醒被wait的一个线程,如果有多个线程被wait,就唤醒优先级高的那个
.notifyAll 一旦执行此方法,就会唤醒所有被wait的线程
说明:wait(),notify(),notifyAll()这三个方法必须使用在同步代码块或同步方法中,且这三个方法是定义在java.lang.Object类中
public class PrintNumThread extends Thread {
static int num = 0;
static Object obj = new Object();
@Override
public void run() {
while (true){
synchronized (obj){
obj.notify();//唤醒等待的线程,由于已经有线程有锁了,也是不能进入同步代码快
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (num<=100){
num++;
System.out.println(Thread.currentThread().getName()+":"+num);
}else {
break;
}
try {
obj.wait();//让线程等待,释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public class Test {
public static void main(String[] args) {
PrintNumThread p1 = new PrintNumThread();
PrintNumThread p2 = new PrintNumThread();
p1.start();
p2.start();
}
}
11、新增创建线程方式
实现Callable接口与使用Runnable相比,Callable功能更强大些
相比run()方法,可以有返回值
方法可以抛出异常
支持泛型的返回值
需要借助FutureTask类,获取返回值
public class MyThread implements Callable<Integer> {
/*
可以有返回值,可以泛型定义
可以抛出异常
*/
@Override
public Integer call() throws Exception {
int num = 0;
num=10;
for (int i = 0; i <10 ; i++) {
num+=i;
}
return num;
}
}
public class Test {
public static void main(String[] args) {
MyThread myThread = new MyThread();
FutureTask<Integer> futureTask = new FutureTask(myThread);//添加 接收任务
Thread thread = new Thread(futureTask); //创建线程
thread.start();
try {
Integer res = futureTask.get(); //获得线程call方法的返回值
System.out.println(res);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}