一.程序,进程,线程
1.程序
程序是为完成某种功能,使用计算机语言编写的一系列指令的集合。指的是静态的代码(安装在电脑上的文件)。
2.进程
进程也可以称为程序,但是是运行中的程序。进程是操作系统进行资源分配的最小单位。

3.线程
进程可以进一步细化为线程,线程就是进程中一个最小的执行单元,是cpu进行调度的最小单元。
eg:qq(进程)中的一个聊天窗口。
一个单核的cpu如何运行多个任务?
通过切换进程,但是这样开销会很大。

进程和线程之间的关系:
1.一个进程中可以包含多个线程。(一个运行中的qq可以开多个聊天窗口)
2.一个线程只能隶属于一个进程(qq聊天窗口只能属于qq的),并且线程不能脱离进程独立运行。
3.一个进程中至少包含一个线程(也就是主线程,java中的main方法就是用来启动主线程的)
4.在主线程中可以创建并启动其他的线程。
5.一个进程内所有线程共享该进程的内存资源。
二.创建线程
Thread类
创建一个类必须继承Thread类,并且去重写里面的run()方法,方法体可以是任何执行代码。
注意:Thread 类中的run()方法和start()方法的区别:run()方法只是需要重写的方法,只是一个简单的类中方法,并不是开启线程的方法,start()方法才是真正开启线程的方法,可以实现线程的单独实现。
创建线程方法1:
写一个类去继承 Thread类**重写run()**方法,线程中要执行的任务都写在run()方法中。
线程通过start()开启后,会由操作系统去调用run()方法(本地方法)。

java
package com.zyk.Thread.demo1;
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("MyThread:"+i);
}
}
}
package com.zyk.Thread.demo1;
public class ThreadTest {
public static void main(String[] args) {
MyThread myThread=new MyThread();
//调用run()方法和在一个main中没有任何区别,只是调用方法而已,不是开启线程
//myThread.run();
// start()才是真正的开启线程
myThread.start();
for (int i = 0; i < 1000; i++) {
System.out.println("main:"+i);
}
}
}
运行结果:

创建线程方法2:
通过自定义一个类去实现Runnable接口,重写接口中的run()方法,去定义执行任务。
然后通过new出来的Thread类对象,通过构造方法去创建并开启线程。
java
package com.zyk.Thread.demo2;
public class Task implements Runnable{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("自定义线程:"+i);
}
}
}
public class ThreadTest {
public static void main(String[] args) {
//定义了执行任务
Task task=new Task();
//创建线程
Thread thread=new Thread(task);
thread.start();
for (int i = 0; i < 1000; i++) {
System.out.println("main:"+i);
}
}
}
运行结果:

通过实现Runnable接口去创建线程的方法的优点:
1.因为java是单继承,一旦继承一个类就不能再继承其他类,避免了单继承的局限。
2.适合多线程来处理同一份资源时使用。
创建线程方法3:
通过实现Callable接口。
java
package com.zyk.Thread.demo9;
import java.util.concurrent.Callable;
public class SumTask implements Callable<Integer> {
@Override
public Integer call() throws Exception {
Integer sum=0;
for (int i = 0; i < 100; i++) {
sum+=i;
}
return sum;
}
}
package com.zyk.Thread.demo9;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
SumTask sumTask=new SumTask();
FutureTask<Integer> futureTask=new FutureTask<>(sumTask);
Thread thread=new Thread(futureTask);
thread.start();
Integer i = futureTask.get();
System.out.println(i);
}
}
使用此方法在开启线程时需要借助FutureTask作为中转new线程。
优点:
此方法的call()方法可以有返回值,并且可以去抛出异常。(与第二种创建线程方法的区别)
三.Thread常用方法
1.run()方法
用来定义线程中需要执行的任务代码。
2.Thread.currentThread()
获取到当前线程。( 在哪个线程中执行就拿的是哪个线程)
3.myThread.setName(" ");
对线程重新命名。
4.setPriority();
设置线程优先级。(1<=优先级<=10,作用是为操作系统中的调度算法提供的)
5.getId();
获取线程id。
6.getName();
获取线程名字。
7.getPriority();
获取线程优先级。(java中默认为5)
8.getState();
获取线程状态。
9.start();
启动线程。
10.sleep(long millis);
让线程阻塞休眠milis毫秒。
11.join();
等待调用join()方法的线程执行完毕,其他线程再执行。(让其他线程等待当前线程执行完之后再执行)
12.yield();
使当前线程主动让步重新进入就绪队列排队。
四.线程状态
新建态:刚刚创建了一个线程对象,并没有启动。
就绪态(可运行):调用start()之后,线程就进入了就绪态,进入了操作系统的调度队列中。
运行态:获得了cpu的执行权,就进入到了cpu执行。
阻塞态:例如调用了sleep(),有线程调用了join(),线程中调用了Scanner .....
死亡/销毁态:run()方法中任务执行完毕。
状态切换
新建态-->就绪态:调用start()方法。
运行态-->就绪态:1.cpu正常切换线程 2.线程调用yield()
运行态-->销毁态:1.run()正常执行完毕 2.调用了Thread中的stop()方法 3.线程调用中出现异常并没有处理、
运行态-->阻塞态:1.调用sleep() 2.调用join(),注意是除了调用join()线程之外的其他线程进入阻塞态 3.Scanner控制台输入 4.等待同步锁 5.wait()
阻塞态-->就绪态:1.sleep()时间到了 2.调用join()的线程执行完毕 3.控制台输入完毕 4.获取到同步锁 5.其他线程唤醒notify()

五.多线程
在一个程序中创建多个线程执行。
优点:
1.提高了程序的响应速度
2.提高了cpu的利用率
3.改善程序结构,例如将一个大的任务拆分成多个小任务
缺点:
1.线程多了,占用内存
2.cpu开销变大
3.线程之间同时对共享资源的访问会相互影响,如果不加以控制会导致数据错误(eg:买票 抢购 秒杀......)
如何解决此类问题呢 :
排队+锁
几个线程之间要排队,一个个对共享资源进行操作,而不是同时操作。
为了保证数据在方法中被访问的正确性,在访问时加入锁机制。
加锁方式
synchronized关键字
synchronized修饰方法时,同步锁对象不需要我们去提供
1.非静态的方法--锁对象默认为this
2.静态方法--锁对象默认为当前类的class对象(类的对象,一个类的对象只有一个)
java
synchronized(同步锁对象){
//同步代码块
}
//同步锁对象作用:用来记录有没有线程进入到同步代码块之中,如果有线程进入到同步代码块,那么其他线程就不能进入到同步代码块之中,直到上一个线程执行完同步代码块的内容,释放锁之后,其他线程才能进入。
//同步锁对象要求:同步锁对象必须是要唯一的,可以是任意类的对象,多个线程对应一个同步锁对象,因此必须是要用static修饰的.
java
public synchronized void print(){
}
eg:
public class TicketThread extends Thread{
//票数
static int num=10;
static Object obj=new Object();
@Override
public void run() {
this.print();
}
public static synchronized void print(){
while(true){
if(num<=0){
break;
}
System.out.println(Thread.currentThread().getName()+ "抢到了第" + num + "张票");
num--;
}
}
}
使用类继承Thred类:
java
package com.zyk.Thread.demo4;
public class TicketTest {
public static void main(String[] args) {
TicketThread t1=new TicketThread();
t1.setName("窗口1");
t1.start();
TicketThread t2=new TicketThread();
t2.setName("窗口2");
t2.start();
}
}
package com.zyk.Thread.demo4;
public class TicketThread extends Thread{
//票数
static int num=10;
static Object obj=new Object();
@Override
public void run() {
this.print();
}
public static synchronized void print(){
while(true){
if(num<=0){
break;
}
System.out.println(Thread.currentThread().getName()+ "抢到了第" + num + "张票");
num--;
}
}
/* @Override
public void run() {
while (true){
//参数为同步锁对象
synchronized (obj){
if (num > 0) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "抢到了第" + num + "张票");
num--;
} else {
break;
}
}
}
}*/
}
实用类实现Runnable接口:
java
package com.zyk.Thread.demo5;
public class TicketTest {
public static void main(String[] args) {
TicketTask ticketTask=new TicketTask();
Thread t1=new Thread(ticketTask);
t1.setName("窗口1");
t1.start();
Thread t2=new Thread(ticketTask);
t2.setName("窗口2");
t2.start();
}
}
package com.zyk.Thread.demo5;
public class TicketTask implements Runnable {
int num = 10;
@Override
public void run() {
synchronized (this) {
while (true) {
if (num > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "抢到了第" + num + "张票");
num--;
} else {
break;
}
}
}
}
}
ReentrantLock类
ReentrantLock是一个java的类
他只能修饰代码块,并且加锁释放锁都是由java代码块实现的。
一般加锁和释放锁需要在 try--finally发代码块中执行,因为如果加锁之后的代码中出现了异常,这个时候锁就必须要关闭。
java
package com.zyk.Thread.demo6;
public class TicketTest {
public static void main(String[] args) {
TicketThread t1=new TicketThread();
t1.setName("窗口1");
t1.start();
TicketThread t2=new TicketThread();
t2.setName("窗口2");
t2.start();
}
}
package com.zyk.Thread.demo6;
import java.util.concurrent.locks.ReentrantLock;
public class TicketThread extends Thread {
//票数
static int num = 10;
static ReentrantLock reentrantLock = new ReentrantLock();
@Override
public void run() {
try {
reentrantLock.lock();
while (true) {
if (num > 0) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "抢到了第" + num + "张票");
num--;
} else {
break;
}
}
} finally {
reentrantLock.unlock();
}
}
}
两种加锁方式的区别
1.synchronized 是一个关键字;ReentrantLock是一个类。
2.synchronized既可以修饰代码块,有可以修饰方法。
ReentrantLock只能修饰代码块。
3.synchronized的加锁和释放锁由底层编译后的指令去实现,自动执行,出现异常会自动释放锁。(synchronized属于非公平锁)
ReentrantLock的加锁和释放锁由java代码实现(底层是同步队列,属于公平锁),需要手动操作,并且一般使用try--finally,一旦出现异常保证能释放锁。
线程通信
线程通讯指的是多个线程通过相互牵制,相互调度,既线程间的相互作用。
涉及三个方法:
1.wait(),执行此方法,当前线程进入阻塞状态,并释放同步锁对象。
2.notify(),唤醒线程
3.notifyAll()
wait()、notify()、notifyAll()方法被调用在synchronized 代码块中,且必须使用锁对象调用,才能实现线程之间的相互牵制相互调度。
java
package com.zyk.Thread.demo7;
public class PrintNumTest {
public static void main(String[] args) {
PrintNumThread p1=new PrintNumThread();
p1.start();
PrintNumThread p2=new PrintNumThread();
p2.start();
}
}
package com.zyk.Thread.demo7;
public class PrintNumThread extends Thread{
static int num=1;
static String obj=new String();
@Override
public void run() {
synchronized (obj){
while (num<=100){
obj.notify();
System.out.println(Thread.currentThread().getName()+":"+num);
num++;
try {
obj.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
}

生产消费者问题

java
package com.zyk.Thread.demo8;
//消费者线程
public class CustomerThread extends Thread{
Counter counter;
public CustomerThread(Counter counter) {
this.counter=counter;
}
@Override
public void run() {
try {
counter.sub();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
package com.zyk.Thread.demo8;
//生产者线程
public class ProductThread extends Thread{
Counter counter;
public ProductThread(Counter counter) {
this.counter=counter;
}
@Override
public void run() {
try {
counter.add();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
package com.zyk.Thread.demo8;
//柜台类
public class Counter {
int num=1;
/**
* 生产者调用
* 每次进来的时候都只能是一个线程
*/
public synchronized void add() throws InterruptedException {
while (true){
if(num==1){
this.wait();
}else{
//先唤醒消费者线程
this.notify();
num=1;
System.out.println("生产者生产了一件产品");
Thread.sleep(1000);
}
}
}
/**
* 消费者调用
*/
public synchronized void sub() throws InterruptedException {
while (true){
if(num==0){
this.wait();
}else {
this.notify();
num=0;
System.out.println("消费者消费了一件产品");
Thread.sleep(1000);
}
}
}
}
package com.zyk.Thread.demo8;
//测试类
public class test {
public static void main(String[] args) {
Counter counter=new Counter();
ProductThread productThread=new ProductThread(counter);
productThread.start();
CustomerThread customerThread=new CustomerThread(counter);
customerThread.start();
}
}

wait()和sleep()方法的区别
wait():
1.是Object类中的方法,用于同步锁对象调用。
2.需要其他的线程进来唤醒(notifiy() notifiyAll())。
3.wait()会自动释放锁。
sleep():
1.是Thread类中的方法。
2.线程在等待休眠时间过之后会自动唤醒。
3.sleep()不会自动释放锁。