目录
synchronized 与 ReentrantLock区别
程序--进程--线程
程序:
为解决某种问题,通过计算机语言编写的一系列指令(代码)的集合。
线程中的程序特指的是静态的,安装在硬盘上的代码集合。
进程:
运行中的程序(被加载到内存中),是操作系统进行资源分配的最小单位。
线程:
进程可以进一步细化为线程,是进程内最小的执行单位(具体要做的事情),是cpu进行任务调度的最小单位 。
运行中的QQ就是一个进程,操作系统会为这个进程分配内存资源,一个聊天窗口就认为是一个线程,这多个聊天窗口可以同时被cpu执行,但是这些聊天窗口属于进程,线程是属于进程的。
早期没有线程,早期cpu在执行时是以进程为执行单位的,进程单位还是比较大的,当一个进程运行时,其他的进程就不能执行,所以后来,将进程中的多个任务,细化为线程。cpu执行单位,也从进程转为更小的线程。
进程和线程的关系
1、一个进程可以包含多个线程;
2、一个线程只能隶属于一个进程,线程不能脱离进程存在;
3、一个进行更中至少有一个线程(即主线程)java中的main方法,就是用来启动主线程;
4、在主线程可以创建并启动其他线程;
5、所以线程都共享进程的内存资源。
创建线程
单线程模式
代码
package com.ffyc.javathread.demo1;
public class Demo {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
System.out.println("循环1:" + i);
}
for (int i = 0; i < 10; i++) {
System.out.println("循环2:" + i);
}
}
}
图解
运行
代码
package com.ffyc.javathread.demo1;
public class Demo2 {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
System.out.println("循环1:" + i);
}
test();
System.out.println("最后的代码");
}
public static void test(){
for (int i = 0; i < 10; i++) {
System.out.println("循环2:" + i);
}
}
}
运行
需求:想在java程序中有几件不相关的事情同时有机会进行进行。
可以在java中创建线程,把一些要执行的任务放在线程中执行,这样的话,都拥有让cpu执行的权力
创建线程
方式一
MyThread 继承 Thread(线程)
重写Thread类中run()方法,在run()方法中来编写我们需要执行的任务代码
调用start()启动线程
代码
package com.ffyc.javathread.demo1;
public class MyThread extends Thread{
//run方法是线程执行任务的方法
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println("MyThread"+i);
}
}
}
package com.ffyc.javathread.demo1;
public class Test {
public static void main(String[] args) {
MyThread myThread = new MyThread();
/*//切记不能直接调用run(),普通方法调用,并不是启动线程
myThread.run();*/
//启动线程,启动线程后,并不是会立即执行的,需要操作系统的调度
myThread.start();
for (int i = 0; i < 10000; i++) {
System.out.println("Test"+i);
}
}
}
运行
方式二
创建一个任务,实现Runnable接口,把这个类不能称为线程,是一个任务类
重写Runnable接口中的run方法
代码
package com.ffyc.javathread.demo2;
public class MyTask implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println("MyTask:"+i);
}
}
}
main方法是用来启动java主线程的
package com.ffyc.javathread.demo2;
public class Test {
public static void main(String[] args) {
//创建一个任务的对象
MyTask myTask = new MyTask();
//真正意义上的创建了一个线程
Thread thread = new Thread(myTask);
//启动线程,去执行mytask任务
thread.start();
for (int i = 0; i < 10000; i++) {
System.out.println("main:"+i);
}
}
}
运行
总结:
两种方法后期使用率上,第二种相对更高一些
1、避免了单一继承的局限性,因为java是单继承的 ,继承了Thread类,就不能继承其他类
2、更适合多线程共享同一份资源的场景
Thread类中常用的方法
Thread类表示线程,提供了很多的方法,来对线程进行控制。
1、
run();线程要执行的任务在run方法中进行定义
start();启动java线程的
构造方法
new Thread(Runnable runnable);接收一个任务对象
代码
package com.ffyc.javathread.demo1;
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println("MyThread"+i);
}
}
}
package com.ffyc.javathread.demo1;
public class Test {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
for (int i = 0; i < 10000; i++) {
System.out.println("Test"+i);
}
}
}
运行
2、
new Thread(Runnable runnable,String name);接收一个任务对象,并为线程设置名字
setName("我的线程1");为线程设置名字
String getName();获得线程的名字
Thread.currentThread();在任务中获得当前正在执行的线程
代码
package com.ffyc.javathread.demo2;
public class MyTask_Name implements Runnable{
@Override
public void run() {
//在任务中,我想知道当前是那个线程正在执行
//在任务中,通过currentThread()获得当前正在执行的线程
Thread thread = Thread.currentThread();
System.out.println(thread.getName());
}
}
package com.ffyc.javathread.demo2;
public class Test_Name {
public static void main(String[] args) {
MyTask_Name myTask_name = new MyTask_Name();
Thread thread1 = new Thread(myTask_name,"线程1");
thread1.setName("我的线程1");
thread1.start();
//又创建了一个线程,把同一个任务交给线程去执行
Thread thread2 = new Thread(myTask_name);
thread2.start();
}
}
运行
3、
setPriority(int p);设置优先级 1-10之间 默认是5
getPriority();获得优先级
代码
package com.ffyc.javathread.demo3;
public class MyTask implements Runnable{
@Override
public void run() {
//获得当前正在执行对象
Thread thread = Thread.currentThread();
//获得线程优先级
System.out.println(thread.getName()+":"+thread.getPriority());
}
}
package com.ffyc.javathread.demo3;
public class Test {
public static void main(String[] args) {
MyTask myTask = new MyTask();
Thread t1 = new Thread(myTask,"线程1");
Thread t2 = new Thread(myTask,"线程2");
//设置线程优先级 1-10之间 默认是5
t1.setPriority(8);
t2.setPriority(2);
t1.start();
t2.start();
System.out.println(Thread.currentThread().getName()+":"+Thread.currentThread().getPriority());
}
}
运行
4、
sleep(long m);让线程休眠指定的时间 毫秒单位
代码
package com.ffyc.javathread.demo3;
public class MyTask implements Runnable{
@Override
public void run() {
//获得当前正在执行对象
Thread thread = Thread.currentThread();
if (thread.getName().equals("线程1")) {
try {
//让线程休眠 1000毫秒
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//获得线程优先级
System.out.println(thread.getName() + ":" + thread.getPriority());
}
}
package com.ffyc.javathread.demo3;
public class Test {
public static void main(String[] args) {
MyTask myTask = new MyTask();
Thread t1 = new Thread(myTask,"线程1");
Thread t2 = new Thread(myTask,"线程2");
//设置线程优先级 1-10之间 默认是5
t1.setPriority(8);
t2.setPriority(2);
t1.start();
t2.start();
System.out.println(Thread.currentThread().getName()+":"+Thread.currentThread().getPriority());
}
}
运行
5、
join();让其他线程等待当前线程结束
代码
package com.ffyc.javathread.demo3;
public class MyTask implements Runnable{
@Override
public void run() {
//获得当前正在执行对象
Thread thread = Thread.currentThread();
//获得线程优先级
System.out.println(thread.getName() + ":" + thread.getPriority());
}
}
package com.ffyc.javathread.demo3;
public class Test {
public static void main(String[] args) throws InterruptedException {
MyTask myTask = new MyTask();
Thread t1 = new Thread(myTask,"线程1");
Thread t2 = new Thread(myTask,"线程2");
t2.start();
//让其他线程等待当前线程结束后再执行
t2.join();
t1.start();
System.out.println(Thread.currentThread().getName()+":"+Thread.currentThread().getPriority());
}
}
运行
线程优先级
事实上,计算机只有一个CPU,各个线程轮流获得CPU的使用权,才能 执行任务;
优先级较高的线程有更多获得CPU的机会,反之亦然;
优先级用整数表示,取值范围是1~10,一般情况下,线程的默认优先级 都是5,但是也可以通过setPriority和getPriority方法来设置或返回优 先级;
操作系统线程任务调度算法
先来先服务(FCFS)调度算法
短作业优先(SJF)调度算法
优先级调度算法
时间片轮转调度算法
高响应比优先级调度算法
多级反馈队列调度算法(集合了前几种算法的优点)
线程状态
线程再它的生命周期中会处于不同的状态;
线程生命周期,线程从创建到销毁,期间经历5个状态:
新建
new Thread();处于新建状态,此状态还不能被执行。
调用start()启动线程,让线程进入到就绪状态,
就绪
获得到cpu执行权后,线程进入到cpu执行
运行
运行中的线程可以被切换,又回到就绪状态,也可能因为休眠等原因进入到阻塞状态
阻塞
线程休眠时间到了 回到就绪状态
死亡
当线程中所有的任务执行完了,线程也就自动销毁
守护线程
守护线程也是线程中的一种,区别在于他的结束,如何一个线程是守护线程,那么它会等java中其他线程任务结束后,自动终止。
守护线程是为其他线程提供服务的,例如jvm中的垃圾回收线程,就是一个守护线程
代码
package com.ffyc.javathread.demo4;
public class Task implements Runnable{
@Override
public void run() {
while (true){
System.out.println("我是守护线程,我为大家服务");
}
}
}
package com.ffyc.javathread.demo4;
public class Test {
public static void main(String[] args) {
Task task = new Task();
Thread thread = new Thread(task);
//设置线程为守护线程,设置守护线程必须在启动前设置。
thread.setDaemon(true);
thread.start();
for (int i = 0; i < 10000; i++) {
System.out.println("main"+i);
}
}
}
运行
多线程的概念
多线程是指程序中包含多个执行单元,即在一个程序中可以同时运行多 个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行 执行的线程来完成各自的任务。
何时需要多线程
程序需要同时执行两个或多个任务。
程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、 网络操作、搜索等。
多线程的优点
提高程序的处理能力,效率提高了.
提高CPU的利用率.
改善程序结构,将复杂任务分为多个线程,独立运行
多线程的缺点
线程也是程序,所以线程需要占用内存,线程越多占用内存也越多(小问题 可提升硬件设备);
多线程需要协调和管理,所以需要跟踪管理线程,使得cpu开销变大;
线程之间同时对共享资源的访问会相互影响,如果不加以控制会导致数据 出错.
线程同步
确保一个时间点只有一个线程访问共享资源。可以给共享资源加一把锁,哪个 线程获取了这把锁,才有权利访问该共享资源。
多线程同步
多个线程同时读写同一份共享资源时,可能会引起冲突。所以引入线程“同步”机制, 即各线程间要有先来后到;
同步 = 排队 + 锁 一次只能由一个线程访问共享资源
加锁方式1:
多个线程访问同一个共享的数据,如果不加以控制,在理论上就会出现问题
Thread类
使用synchronized关键字
修饰代码块
同步对象要求:
锁对象,必须多个线程对应的是同一个对象,可以是java中任何类的对象
作用:可以记录有没有线程进入到同步代码块中
synchronized (锁对象){
使用锁对象的对象头中的一块空间来记录锁的状态
同步代码块
}
代码
package com.ffyc.javathread.demo5;
public class TicketThread extends Thread{
String name;
//静态变量,在内存中只有一份,两个线程对象共用同一个
static int num = 10;
//必须确保多个线程对应的是同一个对象即可
static String s = new String();
@Override
public void run() {
while (true){
synchronized (s){
if(num > 0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"买到了:"+num--);
}else{
break;
}
}
}
}
}
package com.ffyc.javathread.demo5;
public class Test {
public static void main(String[] args) {
TicketThread t1 = new TicketThread();
t1.setName("窗口1");
TicketThread t2 = new TicketThread();
t2.setName("窗口2");
t1.start();
t2.start();
}
}
运行
修饰方法
1、锁不需要我们提供了,会默认提供锁对象
2、synchronized如果修饰的是非静态的方法,锁对象是this
synchronized如果修饰的是静态方法,锁对象是类的Class对象
一个类只有一个Class类对象
代码
package com.ffyc.javathread.demo5;
public class TicketThread extends Thread{
String name;
//静态变量,在内存中只有一份,两个线程对象共用同一个
static int num = 10;
@Override
public void run() {
while (true){
if(num <= 0){
break;
}
TicketThread.printTicket();
}
}
public static synchronized void printTicket(){
if(num > 0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"买到了:"+num--);
}
}
}
package com.ffyc.javathread.demo5;
public class Test {
public static void main(String[] args) {
TicketThread t1 = new TicketThread();
t1.setName("窗口1");
TicketThread t2 = new TicketThread();
t2.setName("窗口2");
t1.start();
t2.start();
}
}
运行
Runnable类
修饰代码块
代码
package com.ffyc.javathread.demo6;
import com.ffyc.javathread.demo5.TicketThread;
public class Ticket implements Runnable{
//对于多个线程来说,票数只有一份
int num = 10;
@Override
public void run() {
while (true){
//this只有一个
synchronized (this){
if(num > 0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"买到了:"+num--);
}else{
break;
}
}
}
}
}
package com.ffyc.javathread.demo6;
public class Test {
public static void main(String[] args) {
//创建了一个出票的任务
Ticket ticket = new Ticket();
Thread t1 = new Thread(ticket,"窗口1");
Thread t2 = new Thread(ticket,"窗口2");
t1.start();
t2.start();
}
}
运行
修饰方法
代码
package com.ffyc.javathread.demo6;
import com.ffyc.javathread.demo5.TicketThread;
public class Ticket implements Runnable{
//对于多个线程来说,票数只有一份
int num = 10;
@Override
public void run() {
while (true){
if(num <= 0){
break;
}
printTicket();
}
}
public synchronized void printTicket(){
if(num > 0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"买到了:"+num--);
}
}
}
package com.ffyc.javathread.demo6;
public class Test {
public static void main(String[] args) {
//创建了一个出票的任务
Ticket ticket = new Ticket();
Thread t1 = new Thread(ticket,"窗口1");
Thread t2 = new Thread(ticket,"窗口2");
t1.start();
t2.start();
}
}
运行
加锁方式2:
使用jdk中提供的ReentrantLock类实现加锁
ReentrantLock只能对某一段代码块加锁,不能对整个方法加锁
代码
package com.ffyc.javathread.demo7;
import java.util.concurrent.locks.ReentrantLock;
public class TicketTask implements Runnable{
int num = 10;
//提供一个实现加锁的对象
ReentrantLock reentrantLock = new ReentrantLock();
@Override
public void run() {
while (true){
//加锁
reentrantLock.lock();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
if(num > 0){
System.out.println(Thread.currentThread().getName()+":买到了"+num--);
}else {
break;
}
}finally {//在finally块中保证锁必须释放
//释放锁
reentrantLock.unlock();
}
}
}
}
package com.ffyc.javathread.demo7;
public class Test {
public static void main(String[] args) {
TicketTask ticketTask = new TicketTask();
Thread t1 = new Thread(ticketTask,"窗口1");
Thread t2 = new Thread(ticketTask,"窗口2");
t1.start();
t2.start();
}
}
运行
synchronized 与 ReentrantLock区别
相同点:
都实现了加锁的功能
不同点:
1、synchronized是一个关键字,ReentrantLock是一个类
2、synchronized修饰代码块和方法ReentrantLock只能修饰代码块
3、synchronized可以隐式的加锁和释放锁,运行过程中如果出现了异常可以自动释放
ReentrantLock需要手动的添加锁和释放锁,建议在finally代码块中释放锁。
线程通信
线程间的通信(在同步代码块的基础上,使用wait,notify来对线程进行控制)
wait(); notify(); notifyAll();
这三个方法都是Object类中定义的方法
这三个方法必须在同步代码块中使用
这三个方法必须通过为锁的对象调用
代码
package com.ffyc.javathread.demo8;
public class PrintNumThread extends Thread{
static int num = 0;
static Object o = new Object();
@Override
public void run() {
while (true){
synchronized (o){
//唤醒等待中的线程
o.notify();
if(num < 100){
System.out.println(Thread.currentThread().getName()+":"+ ++num);
}else {
break;
}
try {
//让线程等待,同时释放锁,等待的线程不能自己醒来,必须让另一个线程唤醒
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
package com.ffyc.javathread.demo8;
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();
}
}
运行
......
新增创建线程方式
实现Callable接口,重写call()方法
call()可以有返回值,可以抛出异常,还可以自定义返回结果的类型
代码
package com.ffyc.javathread.demo10;
import java.util.concurrent.Callable;
public class SumTask<T> implements Callable<T> {
@Override
public T call() throws Exception {
Integer i = 0;
for (int j = 0; j < 10; j++) {
i+=j;
}
return (T)i;
}
}
package com.ffyc.javathread.demo10;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Test {
public static void main(String[] args) {
SumTask<Integer> sumTask = new SumTask<>();
FutureTask<Integer> futureTask = new FutureTask(sumTask);
Thread thread = new Thread(futureTask);
thread.start();
try {
//获取到线程执行结果
Integer integer = futureTask.get();
System.out.println(integer);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}