一.简介
1.进程
程序:为了完成特定任务,用某种语言编写的一组指令集合,就是一段静态的代码。
进程: 在操作系统中独立运行的程序,每一个应用程序就对应一个进程。
动态的过程:产生,存在,消亡。 运行中QQ,运行中酷狗
一个程序可以同时开启多个进程。 任务管理器qq就有多个对应的进程
java代码打开一个进程/运行一个软件
2.线程
线程: 是进程内部的执行单元,用来执行应用程序中的 某一个功能。
多线程:每一个进程可以同时开启多个任务,每个任务就是一个线程。迅雷: 同时下载多个任务
特点:
* 一个进程可以包含多个线程
* 一个进程的多个线程可以共享该进程中的所有资源
3.CPU时间片
对于单核UPU,在某一个时间点,只能处理一个程序。
CPU分配给各个程序的时间,称为时间片。进程可以运行的时间(时间很短)
* 从表面上各个程序是同时运行的,实际上CPU在同一个时间点只能执行一个程序。
* 只是CPU在很短的时间内,在不同的程序间进行切换,轮流执行每个程序,执行速度很快,感觉是在同时运行。
二.创建线程(Thread)
主线程: JVM启动时会创建的一个线程,用来执行main方法
* 实际,java程序运行时至少有两个线程存在,main线程 垃圾回收线程gc
* 想要更多的线程,需要自己创建线程
创建线程的方法:1.继承Thread类 2.实现Runnable接口
方法一:继承Thread类
1. 定义一个类,继承Thred类,重写run方法
2. 创建自己的线程对象
3. 开启线程-- start()方法 ------ 不能直接调用run()
public class Thread_lots extends Thread{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
super.run();
}
Thread_lots(){
}
// 通过构造方法给他名字 需要写含一个参数的构造函数 则无参的构造函数也要写
Thread_lots(String name){
// this.setName(name); 父类里面有这个语句
super(name);
}
}
public static void main(String[] args) {
//主线程的语句 获取当前线程的名字
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
Thread_lots th1 = new Thread_lots();
th1.setName("线程1");
// th1.run();没有开启任何线程 任然输出为main0
th1.start();
// 通过构造方法直接给名字
Thread_lots th2=new Thread_lots("线程2");
th2.start();
}
注意:run方法只是调用了一个普通方法用来处理线程对应的业务逻辑,并没有启动另一个线程,程序还是会按照顺序执行相应的代码,必须使用start方法启动一个线程。
方法二:实现Runnable接口
1. 定义一个类,实现接口,重写run方法
2. 创建线程类的实例
3. 创建一个Thread类的对象,将上一步中的对象传入
4. 调用start()方法
class Mythread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
}
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
Mythread m = new Mythread();
Thread t = new Thread(m);
t.setName("方法二 接口实现建立线程");
t.start();
}
两种方法比较
继承Thread类:
* 单一继承的特点,不能再继承其他类
* 如果没static修饰变量,多个线程时没有共享一个变量
实现Runnable接口:
* 可以继续继承其他类,避免了单继承的局限性
* 适合多个线程去处理同一个资源的情况
//接口50苹果 没按照顺序输出 执行吃苹果操作 还没输出打印结果时 另一个线程抢占cpu 只new一次
class Mythread2 implements Runnable{
int num=50;
@Override
public void run() {
while(num>0){
System.out.println(Thread.currentThread().getName()+"吃了1个苹果 还剩"+(--num));
}
}
}
//继承 150个苹果 每次new变量分配50个
class Thread2 extends Thread{
static int num=50;//注意这边设置为静态的才是公用的 否则new三次 则分配150个苹果了
@Override
public void run() {
while(num>0){ //继承了Thread 则不需要Thread.currentThread()
System.out.println(this.getName()+"吃了1个苹果 还剩"+(--num));
}
}
public Thread2(String name) {
super(name);
}
public Thread2() {
}
}
public static void main(String[] args) {
Mythread2 m2 = new Mythread2();
Thread t1 = new Thread(m2, "COCO");//有这种构造方法
Thread t2 = new Thread(m2, "JACK");
Thread t3 = new Thread(m2, "NANCY");
t1.start();
t2.start();
t3.start();
Thread2 t4 = new Thread2("SOON");
Thread2 t5 = new Thread2("LATER");
Thread t6 = new Thread2("MOMENT");
t4.start();
t5.start();
t6.start();
}
三.生命周期
静态方法直接 类名.方法名 Thread.sleep() Thread.yeild()
方法名 | 作用 | 说明 |
start | 启动线程,进入就绪状态 | |
sleep | 休眠线程,线程由执行状态就入阻塞状态 | 静态方法 |
join | 暂停执行的线程,等待另一个线程执行完成后再执行 | |
yield | 暂停执行线程,线程从执行状态进入就绪状态,只是一瞬间的事情,其他的线程不一定能够执行 | 静态方法 |
interrupt | 中断线程的休眠或是等待状态 |
1.sleep
public static void main(String[] args) {
Mythread m = new Mythread();
m.start();
}
class Mythread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
try {
Thread.sleep(1000); //静态方法
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
2.join
也有可能别的线程先抢占到cpu 不是绝对的;
哪个线程调用join代表哪个线程插队先执行;
如果main()抢到了 则到5就让一下 没有就算;
public static void main(String[] args) {
Mythread1 m2 = new Mythread1();
m2.start();
for (int i = 0; i < 10; i++) {
if(i==5){
try {
m2.join();
} catch (InterruptedException e) {
System.out.println("暂停执行的线程,等待另一个线程执行完成后再执行");
}
}
System.out.println(Thread.currentThread().getName()+i);
}
}
class Mythread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(this.getName()+i);
}
}
}
3.yield
也有可能别的线程先抢占到cpu 不是绝对的,暂停执行线程,线程从执行状态进入就绪状态,只是一瞬间的事情,其他的线程不一定能够执行。
public static void main(String[] args) {
Mythread1 m3 = new Mythread1();
m3.start();
for (int i = 0; i < 10; i++) {
if(i==5){
Thread.yield(); //静态方法 暂停当前线程 让出cpu
}
System.out.println(Thread.currentThread().getName()+i);
}
}
class Mythread3 extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(this.getName()+i);
}
}
}
4.interrupt
表示中断这个线程,中断线程的休眠或是等待状态,若m4先抢占到cpu 就睡觉 main开始执行完还没到三秒,将m4喊醒,报异常try_catch。
public static void main(String[] args) {
Mythread4 m4 = new Mythread4();
m4.start();
m4.setPriority(Thread.MAX_PRIORITY);//设置优先级 抢占到cpu的几率更大 即常量
for (int i = 0; i < 10; i++) {
System.out.println("main"+i);
}
m4.interrupt();
}
class Mythread4 extends Thread{//休眠过程被中断会抛异常
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(this.getName()+i);
try {
Thread.sleep(3000); //静态方法
} catch (InterruptedException e) {
System.out.println("线程被中断啦");
}
}
}
}
四.线程安全问题
1.同步和异步
(1)同步线程:是指有多个线程在同时执行时,一个线程要等待上一个线程执行完成后,才开始执行当前线程 ---- 排队 --- 效率低 -- 比较安全。
(2)异步线程:一个线程执行时,另一个线程不用等待,可以直接抢占cpu资源 --- 效率高 -- 不安全。
2.线程安全问题
多个线程在同时访问共享的数据时可能出现问题--- 称为线程的安全问题。
(1)问题
当线程同时访问数据,CPU切换,导致一个线程还没有完成,执行了一部分代码;此时另一个线程加入进来,导致共享数据发生异常。
(2)解决: 线程的同步机制 synchronized+ 锁
(a)同步方法 : 被synchronized修饰的方法
(b)同步代码块:被synchronized包围的代码块
(3)语法: synchronized(对象锁){}
锁-- 对象锁,每一个对象都自带一个锁(标识),且不同对象锁是不一样的
(4)执行过程
(a)当线程执行同步代码块或者同步方法时,必须要有特定对象的锁才行
(b)一旦对象的锁被获取了,则该对象不再拥有锁,直到当前线程执行完同步方法或者同步代码块时,才会释放对象的锁
(c)如果无法获得特定对象的锁,线程会进入该对象的锁池中等待,直到锁被释放,此时需要该锁的线程会进行竞争
(5)线程同步的有缺点
(a)优点:解决了线程的安全问题,是线程在某一时间只能一个线程方法
(b)缺点:由于需要判断锁,消耗资源,效率低
public class 线程安全 {
public static void main(String[] args) {
TicketThread m = new TicketThread();
Thread t1 = new Thread(m,"线程1");
Thread t2 = new Thread(m,"线程2");
Thread t3 = new Thread(m,"线程3");
t1.start();t2.start();t3.start();
}
}
class TicketThread implements Runnable{
int num=100;
static int num2=100;
@Override
public void run() {
//产生错误:num=1 1进入if 开始睡眠 此时num=1 2抢占cpu进入if语句输出 此时num=0 而后1睡醒了再输出num=-1
//synchronized不能放在while外面 否则变成单线程 1进入输出直至while结束为止 3个窗口都要进行售票
while(true){
synchronized (this) { //从一开始进来就要卡人 同步代码块
//this当前对象的锁 标识里面代码是否被执行完 一旦执行完 别的窗口开始抢占cpu
if(num>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" "+num--);
}
else {
break;
}
}
// sellTiket();
// if(num<=0)break;
}
}
// 同步方法默认使用this锁
public synchronized void sellTiket(){
if(num>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" "+num--);
}
}
// 静态的同步方法使用的是 当前类的Class对象锁 当前正在运行的字节码文件 即class TicketThread
public static synchronized void sellTiket2(){
if(num2>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" "+num2--);
}
}
}
五.线程的通信
1.锁池和等待池
锁池:
(1)当线程执行synchronized块时,无法获得特定对象的锁,会进入该对象的锁池中等待。
(2)锁被释放,归还给对象时,锁池中多个线程竞争获取该对象的锁
等待池:
(1)当线程获得锁后,可以调用wait()放弃锁,线程就入该对象的等待池
(2)当其他线程调用该对象的notify和notifyAll方法时。等待池中的线程会被唤醒,进入该对象的锁池
(3)当线程获取对象锁后,将从上次调用wait()方法的位置开始继续运行
2.方法
方法 | 作用 | 说明 |
wait | 放弃对象锁,进入等待池 | 可以设置等待时间,超时后自动唤醒 |
notify | 随机唤醒等待池中的一个线程,线程进入锁池 | |
notifyAll | 唤醒等待池中的所有线程 |
(1)这个三个方法只能在synchronized块中使用,只有获得了锁的线程才能调用
(2)等待和唤醒必须使用的是同一个对象
eg:等待和唤醒必须使用的是同一个对象,否则无法唤醒,如下示例使用同一个obj;
public static void main(String[] args) {
Object obj = new Object();
Wait1 wait = new Wait1(obj);
Notify1 notify1 = new Notify1(obj);
wait.start();
notify1.start();
}
class Wait1 extends Thread {
private Object o;
public Wait1(Object obj) {
this.o=obj;
}
@Override
public void run() {
synchronized (o) {
System.out.println("111");
try {
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("222");
}
}
}
class Notify1 extends Thread{
private Object o;
public Notify1(Object obj) {
this.o=obj;
}
@Override
public void run() {
synchronized (o) {
o.notifyAll();
System.out.println("开启唤醒服务啦");
}
}
}
输出:
111
开启唤醒服务啦
222
六.生产者和消费者的问题
1.简介
多线程之间的协作---生产线程和 消费线程
生产:
(1)生产的商品 放入 缓冲区
(2)缓冲区到达最大值,放弃生产
消费:
(1)从缓冲区获取商品
(2)当缓冲区为空的时候,不再取出
2.思路
一个仓库类 用来缓存信息;一个消费者线程 ,一个生产者线程,由于需要将他们放在一个等待池中,所以声明 private Warehouse w;
但此类没有new出来,不能调用其方法,会报空指针异常;
需要在test类中创建 传参 再构造函数 实现功能共享一个等待池;
睡眠长度不同则实现的效率不同;
public static void main(String[] args) {
Warehouse w= new Warehouse();
Produce p1= new Produce(w);
Produce p2 = new Produce(w);
Produce p3 = new Produce(w);
Consumer c = new Consumer(w);
p1.start();
p2.start();
p3.start();
c.start();
}
//仓库的属性
public class Warehouse {
int num=0;
public final static int MAX_VALUE=10;
public void in() {
num++;
}
public void out() {
num--;
}
public int getNum() {
return num;
}
public boolean isFull() {
return num==MAX_VALUE;
}
public boolean isEmpty() {
return num==0;
}
}
public class Produce extends Thread{
private Warehouse w;
public Produce(Warehouse w2) {
this.w=w2;
}
public void run() {
while(true) {
synchronized (w) {
if(w.isFull()) {//商品满了 进入等待池
try {
w.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
else {
w.in();
System.out.println(this.getName()+"生产一样物品 还剩"+w.getNum());
w.notifyAll();//若之前消费线程进入等待 则需要唤醒
}
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Consumer extends Thread{
private Warehouse w;
public Consumer(Warehouse w2) {
this.w=w2;
}
public void run() {
while(true) {
synchronized (w) {
if(w.isEmpty()) {
try {
w.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
else {
w.out();
System.out.println(this.getName()+"消费一样物品 还剩"+w.getNum());
w.notifyAll();//若之前生产线程进入等待 则需要唤醒
}
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
七.线程的单例
1.简介
为每一个线程提供一个实例
(1)同一个线程获取的是一个实例
(2)不同线程获取的是不同实例
2.解决线程单列
java中提供了ThreadLocal,直接解决线程单列的解决方案
(1)用于管理对象
(2)同一个线程放入的对象,将获取到同一个对象实例(地址一样)
自己通过代码实现:
//泛型类 可能会传不同的类进来
public class MyThread<T> {
HashMap<Thread,T> map=new HashMap();
public void set(T t) {//将当前对象作为key
map.put(Thread.currentThread(),t);
}
public T get() {//当前线程对象
return map.get(Thread.currentThread());
}
public void getAddr() {
System.out.println(Thread.currentThread());
}
}
public static void main(String[] args) {
MyThread<User> myThread = new MyThread<User>();
// ThreadLocal<User> local=new ThreadLocal<User>();
User u1 = new User("nancy");
User u2 = new User("Jack");
//若key值相同 则会覆盖
myThread.set(u1);
myThread.set(u2);
System.out.println(myThread.get().getName());
System.out.println(myThread.get().getName());
myThread.getAddr();
myThread.getAddr();
// local.set(u1);
// local.set(u2);
// System.out.println(local.get());
new Thread(new Runnable() {
public void run() {
User u3 = new User("Jack");
myThread.set(u3);
myThread.getAddr();
// local.set(u3); 或者 local.set(new User("jack"));
// System.out.println(local.get());
}
}).start();
}
基础
class MyThread implements Runnable(){public void run(){}}
MyThread myThread=new MyThread();
Thread thread=new Thread(myThread,"me");
thread.start();
合并
class MyThread implements Runnable(){public void run(){}}
Thread thread=new Thread(new MyThread(),"me");
thread.start();
匿名内部类
new Thread(new Runnable(){public void run(){}}).start();
这是一边看视频一边学习的笔记-借用了部分老师的笔记内容-如有侵权,通知删除!
新手小白学习之路~~~