程序
为实现某些功能,使用计算机语言编写的一系列指令(代码)的集合
特指静态的,安装在硬盘中的代码集合
进程
运行中的程序(被加载到内存中),是操作系统进行资源分配的最小单位
线程
进程可以进一步细化为线程,是进程内一个最小执行单元(具体要做的事情)
是cpu进行任务调度的最小单位
线程属于进程
例如:运行的qq就是一个进程,操作系统会为这个进程分配内存资源
一个聊天窗口就认为是一个线程,多个聊天窗口可以同时被cpu执行,这些聊天窗口也属于进程
早期没有线程时,CPU是以进程为单位执行的
但进程单位还是比较大的,当一个进程运行时,其他的进程就不能执行
所以后来将进程中的多个任务细化为线程,cpu执行单位,也从进程转为更小的线程
main方法Java中的主线程,按从上到下的顺序执行
进程和线程的关系
一个进程可以包含多个线程(一个QQ可以有多个聊天窗口)
一个线程只能隶属于一个进程,线程不能脱离进程存在(QQ聊天窗口只能属于QQ)
一个进程至少有一个一个线程(主线程,java中的main方法就是用来启动主线程的)
在主线程中可以创建并启动其他线程
所有线程都共享该进程的内存资源
需求
想要在Java程序中有几件不相关的事情同时有机会执行
可以在Java中创建线程,把一些要执行的任务放在线程中执行
这样的话,这些任务都拥有让cpu执行的权力
Java中创建线程
方式1
一个类继承java.lang.Thread 重写run()
public class MyThread extends Thread{
//run方法是线程执行任务的方法
@Override
public void run() {
this.test();
}
public void test(){
for (int i = 0; i < 100; i++) {
System.out.println("MyThread"+i);
}
}
}
创建并启动线程
public class Test {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
for (int i = 0; i < 100; i++) {
System.out.println("main"+i);
}
}
}
注意:myThread.run()并不是启动线程,而是普通的方法调用,还是单线程模式的;start()才是启动线程的
方式2
只先创建线程要执行的任务,创建一个类,实现Runnable接口 重写任务执行的run()
public class Task implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("自定义线程"+i);
}
}
}
创建并启动线程
public class ThreadTest1 {
public static void main(String[] args) {
//创建任务
Task task = new Task();
//创建线程,并为线程指定执行任务
Thread thread = new Thread(task);
thread.start();
for (int i = 0; i < 100; i++) {
System.out.println("main"+i);
}
}
}
实现Runnable接口创建的优点:
1.因为java是单继承,一旦继承一个类就不能再继承其他类,避免单继承的局限性
2.适合多线程来处理同一份资源时使用
方式3
实现Callable接口,重写call()方法,call()可以有返回值,可以抛出异常,还可以自定义返回值结果的类型
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;
}
}
创建并启动线程
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 i = futureTask.get();//获取到线程的执行结果
System.out.println(i);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
常用方法
run();
用来定义线程要执行的任务代码
start();
启动线程
currentThread();
获取当前线程
System.out.println(Thread.currentThread());
//输出Thread[Thread-0,5,main]
getId();
Thread.currentThread().getId();获取线程id
getName();
Thread.currentThread().getName();获取线程名字
setName();
myThread.setName("线程1"); 为线程设置名字
getPriority();
Thread.currentThread().getPriority();获取优先级
setPriority();
myThread.setPriority(10);
设置线程优先级 优先级为1-10 默认是5 作用:为操作系统调度算法提供的
getState();
Thread.currentThread().getState();获取线程状态
sleep(long milis);
让线程阻塞、休眠指定的时间
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
yield();
当前线程主动礼让,退出cpu,让下一个线程执行
for (int i = 0; i < 100; i++) {
Thread thread = Thread.currentThread();//获取当前正在执行的线程
if(i%10==0){
Thread.yield();
}
System.out.println(thread.getName()+":"+i);
}
join();
等待调用了join()线程执行完毕,其他线程再执行
public class Test {
public static void main(String[] args){
MyThread myThread1 = new MyThread();
myThread1.start();
myThread1.join();//myThread1执行完毕,myThread2才能执行
MyThread myThread2 = new MyThread();
myThread2.start();
}
}
wait();
让线程等待,自动释放锁,必须要其他线程唤醒
notify();
唤醒等待中的线程(调用了wait()的线程),如果有多个等待,唤醒优先级高的
notifyAll();
唤醒所有等待的线程
这三个方法都是Object类中定义的方法,必须在同步代码块中使用,必须通过锁的对象调用
while (true){
synchronized (obj){
obj.notify();//唤醒等待中的线程
if(num<100){
num++;
System.out.println(Thread.currentThread().getName()+num);
} else {
break;
}
try {
obj.wait();
//让线程等待,同时释放了锁,等待的线程不能自己醒来,必须让另一个线程唤醒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
wait()与sleep()的区别
wait()是Object类中的方法,必须在同步代码块中使用,wait()后会释放锁,要用锁对象调用,必须等待其他线程唤醒(notify()或notifyAll())
sleep()是Thread类中的方法,在任何地方都能使用,sleep()不会释放锁,在休眠指定时间后,就会恢复
线程生命周期(生老病死 创建--销毁)
线程状态:
新建:刚刚创建了一个线程对象,并没有启动
就绪(可运行):调用start()后,线程就进入到了就绪状态,进入到了操作系统的调度队列
运行状态:获得了cpu执行权,进入到cpu执行
阻塞状态:例如调用了sleep(),有线程调用join(),线程中进行Scanner输入……
死亡/销毁:run()中的任务执行完毕了
多线程
在一个程序中可以创建多个线程执行
多线程优点:
1.提高程序执行效率 多个任务可以在不同的线程中同时执行
2.提高了cpu的利用率
3.改善程序结构,例如将一个的任务拆分成若干个小任务
多线程缺点:
1.线程多了占用内存
2.cpu开销就变大了(扩充内存,升级cpu)
3.多个线程访问操作同一个共享的数据(例如买票,抢购等),会出现线程安全问题
如何解决多线程操作共享资源
排队+锁 在关键的步骤,多个线程只能一个一个的执行
加锁方式1
使用synchronized关键字修饰代码块和方法
修饰代码块
同步对象要求:多个线程用到的必须是同一个对象,可以是java中任何类的对象
作用:用来记录有没有线程进入到同步代码块中,如果有线程进入到同步代码块,那么其他线程就不能进入直到上一个线程执行完同步代码块的内容,释放完锁之后,其他线程才能进入
synchronized(同步对象/同步锁){
同步代码块,一次只允许一个线程进入
}
1.继承Thread
public class TicketThread extends Thread{
int num = 10;//模拟有十张票
static String obj = new String();//同步锁对象
@Override
public synchronized void run() {
while (true){
synchronized (obj){
if(num>0){
System.out.println(Thread.currentThread().getName()+"买到了第"+num+"票");
num--;
} else {
break;
}
}
}
}
}
2.实现Runnable接口
public class TicketThread extends Thread{
int num = 10;
@Override
public synchronized void run() {
while (true){
synchronized (this){//this只有一个
if(num>0){
System.out.println(Thread.currentThread().getName()+"买到了第"+num--+"张票");
} else {
break;
}
}
}
}
}
修饰方法
1.锁不需要我们提供,会默认提供锁对象
2.synchronized如果修饰的是非静态的方法,锁对象是this
public class Ticket implements Runnable{
int num = 10;
@Override
public void run() {
while (true){
if(num>0){
this.printfTicket();
}
else break;
}
}
public synchronized void printfTicket(){
if(num>0){
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"买到了第"+num--+"张票");
}
}
}
synchronized如果修饰的是静态的方法,锁对象是类的Class对象
一个类只有一个Class对象
public class TicketThread extends Thread{
static int num = 10;
@Override
public void run() {
while (true){
if(num<=0){
break;
}
TicketThread.printf();
}
}
public static synchronized void printf(){
if(num>0){
System.out.println(Thread.currentThread().getName()+"买到了第"+num--+"张票");
}
}
}
加锁方式2
使用jdk中提供的ReentrantLock类实现加锁
ReentrantLock只能对某一段代码块加锁,不能对整个方法加锁
reentrantLock.lock(); 加锁
reentrantLock.unlock(); 释放锁
import java.util.concurrent.locks.ReentrantLock;
public class TicketTask implements Runnable{
int t = 10;
ReentrantLock reentrantLock = new ReentrantLock();
@Override
public void run() {
while (true){
try {
reentrantLock.lock();//加锁
if(t>0){
System.out.println(Thread.currentThread().getName()+"买到了第"+t--+"张票");
}
else {
break;
}
}finally {//可能出现异常,因此在finally块中保证锁释放
reentrantLock.unlock();//释放锁
}
}
}
}
public class Test {
public static void main(String[] args) {
TicketTask ticket = new TicketTask();
Thread t1 = new Thread(ticket,"窗口1");
Thread t2 = new Thread(ticket,"窗口2");
t1.start();
t2.start();
}
}
synchronized 和 ReentrantLock区别
相同点:都实现了加锁的功能
不同点:
synchronized是一个关键字,ReentrantLock是一个类
synchronized修饰代码块和方法,ReentrantLock只能修饰代码块
synchronized可以隐式的加锁和释放锁,运行过程中如果出现异常可以自动释放
ReentrantLock需要手动的添加锁和释放锁,建议在finally代码块中释放锁,一旦出现异常,保证锁能释放
线程间的通信(在同步代码块的基础上,使用wait,notify对线程进行控制)