一.程序、线程、进程
1.程序、线程、进程的关系:
程序:(指一段静态的代码)为了完成特定任务,用某种语言编写的一组指令的集合。
进程:正在执行的程序。从Windows操作系统来说,进程是操作系统进行资源分配的最小单位(从硬盘读取到内存中,资源分配)
线程:进程可进一步细化为线程,是一个进程内部的最小执行单位,是草走系统进行任务调度的最小单位,隶属于进程。
1.线程和进程的关系:
1)一个进程可以包含多个线程 ,线程属于进程,线程不能脱离进程而单独存在。
2)一个进程中至少包含一个线程(主线程)java的程序入口main()方法就是在主线程中被执行
3)主线程中可以创建并启动其他线程
4)一个进程内所有线程共享该进程所以内存资源
2.java中如何创建线程:
1)创建Thread类 重写run()方法
2)实现Runnable接口 重写run()方法 不是线程 只是一个任务 把任务丢进创建的线程中去 然后启动
3.实现和继承的区别:
继承Thread:线程代码存放Thread自雷run方法中
实现Runnable接口:线程代码存放在接口的子类的run方法中
实现Runnable的好处:避免了单继承的局限性,多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源 还可以继承其他类 修饰非静态方法 锁对象修饰的是this 就适合多个线程共享同一份资源的情况
4.创建线程的构造方法:
Thread()//创建一个新的线程
Thread(String name)//创建一个指定名称的线程
Thread(Runnable target)//利用Runnable对象创建一个线程,启动时执行该对象的run()方法
Tread(Runnable target,String name)//利用Runnable对象创建线程,并指定线程名称
二.Thread类中的方法
1.线程命名:
创建线程的时候命名
还有 通过setName()方法
2.currentThread() 获得当前正在执行的线程对象 注意返回的是对象
-
getName()获取名称
-
start()启动线程 让线程在操作系统里注册 加入到就绪队列 并不是立即执行
5.setPriority(int newPriority)设置线程的优先级
6.sleep(long millis)让当前执行的线程休眠(暂停执行)休眠时间由millis(毫秒)来指定 休眠指定的时间
7.join()等待线程结束 让其他线程阻塞
8.yield() 让步 不是阻塞 需要再进来排队
三、生命周期
什么时候创建 什么时候死亡 线程的状态
线程五种状态:
新建:当Thread类或其子类的对象被声明并创建时,新的线程对象处于新建状态
就绪:处于新建状态的线程被start()后,将进入队列等待CPU时间片,此时他已具备运行条件,只是没有分配到CPU资源
运行:当就绪的线程被调度并获得CPU资源时,变进入运行状态,run()方法定义了线程的操作和功能
阻塞:在某种情况下,被人挂起或执行输入输出操作时,让出CPU并临时终止自己的执行,进入阻塞状态
死亡:线程完成了他的全部工作或线程被提前性的终止或出现异常导致结束
四、线程分类
1.用户线程:
2.守护线程:
守护线程的作用就是为其他线程的运行提供便利服务,GC(垃圾回收器)就是一个称职的守护者
设置线程为守护者必须在启动线程之前,否则会抛出一个IllegalThreadStateException异常
设置守护线程:线程名称.setDaemon(true)
3.用户线程和守护线程的区别:
基本没有区别,唯一区别就在于虚拟机的离开(用户线程结束守护线程就没必要在执行了 自动结束 即使 死循环也会终止 )
五、多线程
就是一个程序可以实现多个任务
-
多线程的问题
多个线程访问共享资源出现问题的本质在于CPU是多内核的,在理论上可以同时执行多个线程
-
并发与并行
(1)并行:同一个时间 同时做多件事 各做个的。
(2)并发:在一段时间内 依次做同一件事 就像卖票 其实是一个一个执行
3.线程同步:
多个线程同时读一份资源时,可能引起冲突,就需要同步机制 就是加锁 实现先来后到 排队
(1)使用synchronized(锁对象){同步代码块}可以同步方法或代码块,加在代码块里同步代码块,在方法中声明就表示同步整个方法
锁对象: 锁对象可以是任何对象,但多个线程必须是同一个,在对象中,有一个叫对象头的区域,在对象头中有个标志位(锁状态) 锁有很多状态
synchronized修饰方法时,并没有显示添加标志对象 修饰非静态方法时,锁标志默认this 有几个对象就有几个锁 ;修饰静态方法所标志是Class类的对象(每个类被加载到内存中时,都会为该类创建一个Class类的对象,用于封装类的信息,一个类即使有多个对象,Class类的对象只有一个)
public static void synchronized(){ }
下面是模拟卖票案例:(两个窗口 十张票)
package feifan; public class TickDemo extends Thread { static int num=10;//一定要加static 不然不是同一份票 static String lock=new String();//一定要加static @Override public void run() { while (true){ synchronized (lock){ if(num>0){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+":"+num); num--; }else { break; } } } } }
package feifan; public class TickTest extends TickDemo { public static void main(String[] args) { TickDemo t1=new TickDemo(); TickDemo t2=new TickDemo(); t1.setName("窗口1"); t2.setName("窗口2"); t1.start(); t2.start(); } }
六、Lock(锁) 另一种锁形式
ReentrantLock实现了Lock,
他拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用,可以显示加锁,释放加锁(自带对象 synchronized还需要传一个进去 )
买票:
package feifan;
import java.util.concurrent.locks.ReentrantLock;
public class Tick implements Runnable {
int num=10;//10张票
ReentrantLock lock=new ReentrantLock();
@Override
public void run() {
while (true){
try {
lock.lock();//加锁 内部维护了一个所标志
if (num>0){
System.out.println(Thread.currentThread().getName()+":"+num);
num--;
}else{
break;
}
}finally {
lock.unlock();//释放锁 写在finally里面 怎么都会释放锁
}
}
}
}
package feifan;
public class LockTest {
public static void main(String[] args) {
Tick tick=new Tick();//出票任务
Thread t1=new Thread(tick,"窗口1");
Thread t2=new Thread(tick,"窗口2");
t1.start();
t2.start();
}
}
八、两种锁总结:
区别:synchronized是关键字,底层实现是依赖编译后的指令来实现 。是隐式锁,自动添加,自动释放(执行完 或者出现异常) 可以修饰代码块和方法
需要靠对象头中的标志记录锁的状态(即需要传进一个对象)
非公平锁(等待锁的时候不需要排队 谁抢到谁执行)
ReentrantLock是类,依赖代码实现
是显示锁 手动添加 手动释放
只能修饰代码块
内部维护锁的状态标志 不需要传对象
可以实现公平锁 内部维护了一个同步队列(ReentrantLock lock=new ReentrantLock(true)就是公平锁 没写就是默认非公平锁)
九、线程锁死
不同的线程分别占用对方的同步资源不放弃,都在等对方放弃,就形成了死锁 出现死锁后不会出现异常,不会出现提示,只是所有线程都处于阻塞状态,无法继续
或者同步代码块的死循环 锁一直释放不了
同步代码块的嵌套 不同的线程一直占有对方资源的同步锁不释放
十、线程通信
线程通信:指的是多个线程通过相互牵制,相互调度,即线程间的相互作用。
怎么牵制 涉及三个方法:(三个方法必须在同步方法或者同步代码块中)(同步代码块 synchronized)(用同步锁对象调用该方法)
wait()当前线程进入堵塞状态,释放同步监视器
notify()唤醒被wait的一个线程 要是有多个线程 就唤醒优先级高的那个
notifyAll()唤醒所有被wait的线程
两个线程交替打印1~100之间的数字:
package feifan;
public class Printshuzi extends Thread{
static Object ob=new Object();
static int num=0;
@Override
public void run() {
while (true){
synchronized (ob){
ob.notify();//唤醒
if (num<=100){
System.out.println(Thread.currentThread().getName()+":"+num);
num++;
}else {
break;
}
try {
ob.wait();//阻塞
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
Printshuzi p1=new Printshuzi();
Printshuzi p2=new Printshuzi();
p1.start();
p2.start();
}
}
生产者消费者案例:
柜台
package feifan;
public class Counter {
int num=1;
//生产者生产
public synchronized void add() throws InterruptedException {//加锁 非静态方法 对象是 this 只有一个
if (num==0){
num++;this.notify();//唤醒
System.out.println("生产者生产商品");
}else {
this.wait();//又商品 生产者等待 释放锁
}
}
//消费者消费
public synchronized void sub() throws InterruptedException {
if (num==0){
this.wait();
}else {
num--;
System.out.println("消费者拿走商品");
this.notify();
}
}
}
生产者线程:
package feifan;
public class Productor extends Thread {
//生产者线程
Counter c;//因为只有一个 所以构造方法传进去
public Productor(Counter c) {
this.c = c;
}
@Override
public void run() {
while (true){
try {
c.add();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
消费者线程:
package feifan;
public class Customer extends Thread {
//消费者线程
Counter c;
public Customer(Counter c) {
this.c = c;
}
@Override
public void run() {
while (true){
try {
Thread.sleep(1000);
c.sub();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
测试:
package feifan;
public class PCTest {
public static void main(String[] args) {
Counter c=new Counter();//柜台 只有一个
Productor p=new Productor(c);
Customer customer=new Customer(c);
p.start();
customer.start();
}
}
十一、创建线程的方式(第三种)
实现Collable接口与使用Runnable相比,Callable功能更强大些。
相比run()方法,可以有返回值
方法可以抛出异常
支持泛型的返回值
需要借助FutureTask类,获取返回结果
接收任务:FutureTask<Intege> futureTask=new FutureTask(任务)
创建线程:Thread t=new Thread(futureTask);
t.start();
Integer val=futureTask.get();//获取线程call方法的返回值
package feifan;
import java.util.concurrent.Callable;
public class CallableThread implements Callable<Integer> {//可以加泛型
@Override
public Integer call() throws Exception {
int num=0;
for (int i=0;i<100;i++){
num=num+i;
}
return num;
}
}