1.基础多线程
进程:程序是静止,其真正运行时的程序才称之为进程
- 单核CPU在任何时间点上
- 只运行一个进程
- 宏观并行,微观串行
线程:轻量级进程(Light Weight Process)
- 程序中的一个顺序控制流程,同时也是CPU的基本调度单位
- 进程是多个线程的组成,完成不同工作时,交替执行
进程与线程区别:
- 前者时OS资源分配的基本单位,后者是CPU的基本调度单位
- 一个程序运行后至少有一个进程
- 一个进程可以包含多个线程,但至少需要一个线程
- 进程间不能共享数据段地址,但同进程的线程之间可以
线程的组成:
- CPU时间片:OS为每个线程分配执行时间
- 运行数据:堆空间;栈空间
- 线程的逻辑代码
创建线程(***)
主要的两种方式:
- 继承Thread类
- 实现Runnable接口
A--继承Thread类方法
步骤:1.编写类、继承Thread 2. 重写run方法 3.创建线程对象 4.调用start方法启动线程
MyThread类
public class MyThread extends Thread{
public MyThread(){
}
public MyThread(String name){
super(name);
}
@Override
public void run(){
for(int i=0;i<10;i++){
System.out.println("子线程"+i);
}
}
}
TestThread类
public class TestThread{
public static void main(String[] args){
MyThread myThread=new MyThread(); //创建线程对象
myThread.start(); //run()
MyThread myThread2=new MyThread();
myThread2.start();
//主线程执行
for(int i=0;i<50;i++){
System.out.println("主线程"+i);
}
}
}
获取线程名称:
- getName()
- Thread.current.getName()
public void run(){
for(int i=0;i<10;i++){
System.out.println("id:"+Thread.currentThread().getId()+"NAME:"+Thread.currentThread().getName()+"子线程"+i);
}
}
public static void main(String[] args){
MyThread myThread=new MyThread();
myThread.start();
MyThread myThread2=new MyThread();
myThread2.start();
//主线程执行
for(int i=0;i<50;i++){
System.out.println("主线程"+i);
}
}
经典问题:多个窗口共卖/各卖多少张票(后续关于此问题单写一篇)
线程基本状态:新建、就绪、运行、终止
常见方法:
public static void sleep(long millis) 当前线程主动休眠
public static void yield() 当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片
public final void join() 允许其他线程加入到当前线程中
public void setPriority(int) 线程优先级:1-10 默认:5 优先级越高,CPU的获取机会越多
public void setDaemon(boolean) 设置守护线程
线程状态——等待
线程安全
线程不安全:
- 当多线程并发访问临界资源时,如果破坏原子操作,可能会造成数据不一致
- 临界资源:共享资源,一次允许一个 线程使用,才可保证其正确性
- 原子操作:不可分割的多步操作,被视作一个整体,其顺序贺步骤不可打乱或缺省
public class ThreadSafe {
private static int index=0;
public static void main(String[] args) {
//创建数组
String[] s=new String[5];
//创建两个操作
Runnable runnableA=new Runnable() {
@Override
public void run() {
//同步代码块
synchronized (s){
s[index]="hello";
index++;
}
}
};
Runnable runnableB=new Runnable() {
@Override
public void run() {
synchronized (s){
s[index]="LiHua";
index++;
}
}
};
//创建两个线程对象
Thread a=new Thread(runnableA,"A");
Thread b=new Thread(runnableB,"B");
a.start();
b.start();
a.join(); //加入线程
b.join();
System.out.println(Arrays.toString(s));
}
}
同步代码块
synchronized(对象){
}
注意:
- 每个对象都有一个互斥锁标记,要弄来分配给线程
- 只有拥有对象互斥锁标记的线程,才能进入对该对象加锁的同步代码块。
- 线程退出同步代码块时,会释放相应的互斥锁标记
package com.qf.servlet;
public class Ticket implements Runnable {
private int ticket=100;
@Override
public void run() {
while (true){
synchronized (this) {
if (ticket <= 0) {
break;
}
System.out.println(Thread.currentThread().getName()+"sale"+ticket+"票");
ticket--;
}
}
}
}
线程状态——阻塞
同步方法、规则
synchronized 返回值类型 方法名 {
加锁
}
注意:
- 只有拥有对象互斥锁标记的线程,才能进入到该对象加锁的同步方法中
- 线程退出同步方法时,会释放相应的互斥锁标记
- 方法为静态,锁时类型.class
死锁
- 当第一个线程拥有A对象锁标记,并等待B对象锁标记,同时第二个线程拥有B对象锁标记,并等待A对象锁标记,产生死锁
- 一个线程可同时拥有多个对象的所标记,当线程阻塞时,不会释放已经拥有的锁标记,由此造成死锁。
破坏死锁的条件:
- 破坏“循环等待”条件
当一个已经持有了一些资源的进程在提出新的资源请求没有得到满足时,它必须释放已经保持的所有资源,待以后需要使用 的时候再重新申请。这就意味着进程已占有的资源会被短暂地释放或者说是被抢占了。该种方法实现起来比较复杂,且代价也比 较大。释放已经保持的资源很有可能会导致进程之前的工作实效等,反复的申请和释放资源会导致进程的执行被无限的推迟,这 不仅会延长进程的周转周期,还会影响系统的吞吐量。
- 破坏“不可抢占”条件
可以通过定义资源类型的线性顺序来预防,可将每个资源编号,当一个进程占有编号为i的资源时,那么它下一次申请资源只 能申请编号大于i的资源。
其他的没什么好说的,直接上案例
public class MyLock{
//定义两个锁
public static Object a=new Object();
public static Object b=new Object();
}
public class UserA extends Thread {
@Override
public void run(){
synchronized (MyLock.a){
System.out.println("用户一拿到a");
synchronized (MyLock.b){
System.out.println("用户一拿到b");
System.out.println("用户一开始工作。。。");
}
}
}
}
public class UserB extends Thread {
@Override
public void run(){
synchronized (MyLock.b){
System.out.println("用户二拿到a");
synchronized (MyLock.a){
System.out.println("用户二拿到b");
System.out.println("用户二开始工作。。。");
}
}
}
}
public class TestDeadLock {
public static void main(String[] args) {
UserA A=new UserA();
UserB B=new UserB();
B.start();
try {
Thread.sleep(300);
}catch (InterruptedException e){
e.printStackTrace();
}
A.start();
}
}
线程通信
方法:
publuc final void wiat() 释放锁,进入等待队列
publuc final void wait (long timeout) 超过指定的时间前,释放锁,进入等待队列
publuc final void notify() 随即唤醒,通知线程
publuc final void notifyAll() 唤醒所有线程
经典问题:生产者、消费者(后续单独写)